Handling GraphQL Errors
1. Throwing Errors in Resolvers
Example: Throw GraphQLError
import { GraphQLError } from "graphql";
throw new GraphQLError("User not found", {
extensions: { code: "NOT_FOUND", http: { status: 404 } }
});
2. Using GraphQLError Class
| Option | Detail |
|---|---|
| message | Human-readable description |
| extensions.code | Machine-readable error code |
| extensions.http.status | Override HTTP status (Yoga/v4) |
| originalError | Underlying error to wrap |
| path / locations | Set automatically |
3. Adding Error Extensions
Example: Rich extensions
{
"errors": [{
"message": "Validation failed",
"extensions": {
"code": "BAD_USER_INPUT",
"field": "email",
"constraint": "format"
}
}]
}
4. Formatting Error Responses
Example: formatError hook
const server = new ApolloServer({
schema,
formatError: (formatted, error) => {
if (formatted.extensions?.code === "INTERNAL_SERVER_ERROR") {
return { message: "Internal error", extensions: { code: "INTERNAL_SERVER_ERROR" } };
}
return formatted;
}
});
5. Handling Validation Errors
| Type | Code |
|---|---|
| Schema validation | GRAPHQL_VALIDATION_FAILED |
| Input validation | BAD_USER_INPUT |
| Variable type | GRAPHQL_VALIDATION_FAILED |
6. Handling Authentication Errors
Example: 401-style error
throw new GraphQLError("Authentication required", {
extensions: { code: "UNAUTHENTICATED", http: { status: 401 } }
});
7. Handling Authorization Errors
Example: 403-style error
throw new GraphQLError("Insufficient permissions", {
extensions: { code: "FORBIDDEN", http: { status: 403 }, requiredRole: "ADMIN" }
});
8. Handling Not Found Errors
| Approach | When |
|---|---|
| Return null | Lookup-style query (preferred) |
| Throw NOT_FOUND | Mutation targeting nonexistent record |
| Error union | When typed errors are part of the API |
9. Masking Internal Errors
| Risk | Mitigation |
|---|---|
| Stack trace leak | Disable in production formatError |
| DB error messages | Catch and re-throw generic message |
| Path leak | Strip path from masked errors |
10. Logging Errors
| Field | Log |
|---|---|
| requestId | Correlate to request |
| operationName | Identify query |
| path | Where in tree |
| originalError.stack | Server-only |
| user.id | For auditing |
11. Returning Partial Data
| Behavior | Detail |
|---|---|
| Failed nullable field → null | Other fields succeed |
| Errors array populated | One entry per failed field with path |
| HTTP 200 | Even with errors (per spec) |
12. Using Error Codes
| Code | Meaning |
|---|---|
| UNAUTHENTICATED | Missing/invalid credentials |
| FORBIDDEN | Authenticated but not allowed |
| BAD_USER_INPUT | Invalid input |
| NOT_FOUND | Resource missing |
| CONFLICT | State conflict (duplicate, version mismatch) |
| RATE_LIMITED | Too many requests |
| INTERNAL_SERVER_ERROR | Unexpected failure |
| GRAPHQL_VALIDATION_FAILED | Query invalid against schema |
| PERSISTED_QUERY_NOT_FOUND | APQ miss |