Handling Mutation Errors: Throwing and Returning GraphQL Errors
Learn how to implement effective error handling in GraphQL mutations. Discover how to throw errors for invalid inputs and return meaningful messages to clients.

Previously in this course, we covered Managing Mutation State: Updating Arrays in GraphQL Resolvers. Now that we can successfully modify our data, we need to ensure those modifications are safe. This lesson adds robust error handling to your mutations, ensuring your API communicates failures clearly rather than failing silently or crashing.
Why Error Handling Matters
In a standard REST API, you rely on HTTP status codes like 400 Bad Request or 422 Unprocessable Entity to signal problems. Because GraphQL operates over a single endpoint—typically returning a 200 OK status even when a request fails—you must use the GraphQL errors array to communicate issues to the client.
If a user attempts to update an item that doesn't exist or provides invalid input, your resolver shouldn't just return null or throw a generic server exception. It should explicitly inform the client why the operation failed.
Throwing Errors from Resolvers
In Apollo Server, you can import the GraphQLError class. When you throw this error inside a mutation, the server catches it automatically and formats it into the standard GraphQL response structure.
Here is a concrete example of a mutation that updates a user's email. We want to prevent the mutation if the email is already taken or if the format is invalid.
JAVASCRIPTimport { GraphQLError } from CE9178">'graphql'; const resolvers = { Mutation: { updateUserEmail: (_, { id, newEmail }, { users }) => { const user = users.find(u => u.id === id); if (!user) { throw new GraphQLError(CE9178">'User not found', { extensions: { code: CE9178">'USER_NOT_FOUND', http: { status: 404 }, }, }); } if (!newEmail.includes(CE9178">'@')) { throw new GraphQLError(CE9178">'Invalid email format', { extensions: { code: CE9178">'BAD_USER_INPUT', http: { status: 400 }, }, }); } user.email = newEmail; return user; }, }, };
Anatomy of a GraphQL Error
Notice the extensions object in the example above. This is where the magic happens. While the message field provides a human-readable description for the developer, the extensions object allows you to pass structured metadata:
code: A machine-readable string (e.g.,USER_NOT_FOUND) that your frontend can use to trigger specific UI logic, like showing a localized error message.http: While optional, providing anhttpobject helps if you are logging errors through middleware that inspects status codes.
Hands-on Exercise
Open your current project and locate your "add" or "update" mutation resolver.
- Add a guard clause to check if the incoming arguments are valid (e.g., ensure a string isn't empty).
- If the validation fails,
throwa newGraphQLError. - Include a
codefield in theextensionsobject such asINVALID_INPUT. - Restart your server and trigger that mutation in Apollo Sandbox with invalid data. Observe how the
errorsarray appears in the response pane.
Common Pitfalls
- Throwing raw JavaScript Errors: Avoid throwing standard
Errorobjects. While they will be caught, they often expose stack traces to the client, which is a significant security risk. Always useGraphQLError. - Over-sharing details: Don't leak database connection errors or internal system paths in your error messages. Keep messages strictly related to the business logic failure.
- Silent Failures: Don't return
nullfor a failed mutation unless your schema explicitly allows nullability for that return type. If the mutation must return an object, an error is the only correct way to stop execution.
FAQ
Q: Should I handle all validation inside the resolver? A: For simple logic, yes. For complex validation, you might want to move this to a dedicated validation service layer to keep your resolvers thin.
Q: Can I return data and an error at the same time? A: Yes. If a field in a list fails, GraphQL can return partial data for the successful items and an error object for the failed one. However, for mutations, it's standard practice to halt execution if a critical business rule is violated.
Q: Does every error need an HTTP status code?
A: Not strictly, as GraphQL defaults to 200. However, adding status codes via the extensions object is a best practice if you are using tools that monitor HTTP traffic logs.
Recap
By implementing proper error handling, you ensure your API is predictable and debuggable. We use GraphQLError to signal issues, provide machine-readable codes for frontend logic, and avoid leaking implementation details. This follows the principles of robust API development, similar to Handling API Errors: A Type-Safe Approach in TypeScript, where clear error communication is the foundation of a reliable system.
Up next: We will look at Designing for Deletion to safely remove resources from our API.
Work with me

Laravel REST API Development
Clean, secure, well-documented Laravel REST APIs — the backend engine for your app, mobile client, or SaaS. Built by an API specialist.

FilamentPHP Admin Panel & Dashboard Development
A powerful admin panel for your Laravel app — built with FilamentPHP so you can manage everything without touching the database.


