Lesson 4
Best Practices for Error Handling in GraphQL
Introduction

Welcome to the lesson on Best Practices for Error Handling in GraphQL. In this lesson, we will explore how to handle errors effectively in your GraphQL API using Apollo Server and TypeScript. Proper error handling is crucial for building reliable and user-friendly applications.

How GraphQL Handles Errors and Common Error Types

GraphQL treats errors as part of the response format. If any field in a query fails, it includes an errors array in the response. Common error types include:

  • User Input Errors
  • Authentication Errors
  • Validation Errors
  • System Errors
Implementing Basic Error Handling in Resolvers

Apollo Server provides built-in error classes like UserInputError to help you handle common errors. Let's start with handling missing data and validating user inputs.

Here's how you can throw a UserInputError if a book is not found:

TypeScript
1import { ApolloServer, gql, UserInputError } from 'apollo-server'; 2 3const books = [ 4 { id: '1', title: 'The Hobbit', author: 'J.R.R. Tolkien' }, 5 { id: '2', title: 'Harry Potter', author: 'J.K. Rowling' } 6]; 7 8const resolvers = { 9 Query: { 10 book: (_: any, { id }: { id: string }) => { 11 const book = books.find(book => book.id === id); 12 if (!book) { 13 throw new UserInputError('Book not found'); 14 } 15 return book; 16 }, 17 } 18};

In this code, when a book with the specified ID is not found, a UserInputError is thrown with the message "Book not found."

Example: Validating User Inputs

You can also throw an error if the required inputs are missing:

TypeScript
1const resolvers = { 2 Mutation: { 3 addBook: (_: any, { title, author }: { title: string; author: string }) => { 4 if (!title || !author) { 5 throw new UserInputError('Title and Author are required'); 6 } 7 const newBook = { id: String(books.length + 1), title, author }; 8 books.push(newBook); 9 return newBook; 10 }, 11 } 12};

In this code, if either title or author is missing, a UserInputError is thrown with a relevant message.

Advanced Error Handling Techniques

For more complex cases, you might want to create custom error classes:

TypeScript
1class MyCustomError extends Error { 2 constructor(message: string) { 3 super(message); 4 this.name = 'MyCustomError'; 5 } 6}
Handling Multiple Errors

You can handle multiple errors by creating a list of errors and throwing them as needed:

TypeScript
1const errors = []; 2if (!title) errors.push('Title is required'); 3if (!author) errors.push('Author is required'); 4if (errors.length > 0) throw new UserInputError(errors.join(', '));
Example: Error Handling in a Complete Application

Now, let's put it all together in a complete example. We'll use the provided outcome code:

Here is our server code:

TypeScript
1import { ApolloServer, gql, UserInputError } from 'apollo-server'; 2 3const typeDefs = gql` 4 type Book { 5 id: ID! 6 title: String! 7 author: String! 8 } 9 10 type Query { 11 book(id: ID!): Book 12 } 13 14 type Mutation { 15 addBook(title: String!, author: String!): Book 16 } 17`; 18 19const books = [ 20 { id: '1', title: 'The Hobbit', author: 'J.R.R. Tolkien' }, 21 { id: '2', title: 'Harry Potter', author: 'J.K. Rowling' } 22]; 23 24const resolvers = { 25 Query: { 26 book: (_: any, { id }: { id: string }) => { 27 const book = books.find(book => book.id === id); 28 if (!book) { 29 throw new UserInputError('Book not found'); 30 } 31 return book; 32 }, 33 }, 34 Mutation: { 35 addBook: (_: any, { title, author }: { title: string; author: string }) => { 36 if (!title || !author) { 37 throw new UserInputError('Title and Author are required'); 38 } 39 const newBook = { id: String(books.length + 1), title, author }; 40 books.push(newBook); 41 return newBook; 42 }, 43 } 44}; 45 46const server = new ApolloServer({ typeDefs, resolvers }); 47 48server.listen().then(({ url }) => { 49 console.log(`🚀 Server ready at ${url}`); 50});

And here is how we make queries for the server:

TypeScript
1import fetch from 'node-fetch'; 2 3const url = 'http://localhost:4000/'; 4 5const fetchBook = async (id: string) => { 6 const query = ` 7 query { 8 book(id: "${id}") { 9 title 10 author 11 } 12 } 13 `; 14 15 try { 16 const response = await fetch(url, { 17 method: 'POST', 18 headers: { 19 'Content-Type': 'application/json', 20 }, 21 body: JSON.stringify({ 22 query, 23 }), 24 }); 25 26 const data = await response.json(); 27 28 if (data.errors) { 29 console.error('Errors:', data.errors); 30 } else { 31 console.log('Data:', data); 32 } 33 } catch (error) { 34 console.error('Network Error:', error); 35 } 36}; 37 38const addNewBook = async (title: string, author: string) => { 39 const mutation = ` 40 mutation { 41 addBook(title: "${title}", author: "${author}") { 42 id 43 title 44 author 45 } 46 } 47 `; 48 49 try { 50 const response = await fetch(url, { 51 method: 'POST', 52 headers: { 53 'Content-Type': 'application/json', 54 }, 55 body: JSON.stringify({ 56 query: mutation, 57 }), 58 }); 59 60 const data = await response.json(); 61 62 if (data.errors) { 63 console.error('Errors:', data.errors); 64 } else { 65 console.log('Data:', data); 66 } 67 } catch (error) { 68 console.error('Network Error:', error); 69 } 70}; 71 72// Test the functions 73fetchBook('1'); 74addNewBook('1984', 'George Orwell');

In this code, errors from the server response are logged to the console using console.error. Additionally, any network errors during the fetch request are caught and logged as "Network Error".

When you run this code, you will set up a server that handles both queries and mutations with proper error handling.

Lesson Summary

To summarize, in this lesson, you learned the importance of error handling in GraphQL, how to implement basic and advanced error handling techniques, and saw how to apply them in a complete application.

As you move on to the practice exercises, apply these techniques to solidify your understanding. Congratulations on completing this lesson and the course! You've gained essential skills to develop secure and resilient GraphQL APIs. Keep practicing and exploring more advanced topics to enhance your expertise.

Enjoy this lesson? Now it's time to practice with Cosmo!
Practice is how you turn knowledge into actual skills.