Error Handling

Making errors actionable on the client and server


Apollo Server v4 introduced a regression where providing invalid variables yields a 200 status code instead of 400. To mitigate this regression, provide the status400ForVariableCoercionErrors: true option to your ApolloServer constructor. For more information, see the migration guide.

Whenever Apollo Server encounters errors while processing a GraphQL operation, its response to the client includes an errors array containing each error that occurred. Each error in the array has an extensions field that provides additional useful information, including an error code and (while in development mode) a stacktrace.

Here's an example error response caused by misspelling the __typename field in a query:

Click to expand
JSON
1{
2  "errors": [
3    {
4      "message": "Cannot query field \"__typenam\" on type \"Query\".",
5      "locations": [
6        {
7          "line": 1,
8          "column": 2
9        }
10      ],
11      "extensions": {
12        "code": "GRAPHQL_VALIDATION_FAILED",
13        "stacktrace": [
14          "GraphQLError: Cannot query field \"__typenam\" on type \"Query\".",
15          "    at Object.Field (/my_project/node_modules/graphql/validation/rules/FieldsOnCorrectTypeRule.js:48:31)",
16          "    ...additional lines..."
17        ]
18      }
19    }
20  ]
21}

To help with debugging, Apollo Server provides an ApolloServerErrorCode enum, which you can use to check if your error is one of the different types produced by Apollo Server.

You can check an error's code to determine why an error occurred and also add logic to respond to different types of errors, like so:

TypeScript
1import { ApolloServerErrorCode } from '@apollo/server/errors';
2
3if (error.extensions?.code === ApolloServerErrorCode.GRAPHQL_PARSE_FAILED) {
4  // respond to the syntax error
5} else if (error.extensions?.code === "MY_CUSTOM_CODE") {
6  // do something else
7}

Apollo Server's variety of error codes enables requesting clients to respond differently to different error types. You can also create your own custom errors and codes.

Built-in error codes

Code Description
GRAPHQL_PARSE_FAILED
The GraphQL operation string contains a syntax error.
GRAPHQL_VALIDATION_FAILED
The GraphQL operation is not valid against the server's schema.
BAD_USER_INPUT
The GraphQL operation includes an invalid value for a field argument.
PERSISTED_QUERY_NOT_FOUND
A client sent the hash of a query string to execute via automatic persisted queries, but the query was not in the APQ cache.
PERSISTED_QUERY_NOT_SUPPORTED
A client sent the hash of a query string to execute via automatic persisted queries, but the server has disabled APQ.
OPERATION_RESOLUTION_FAILURE
The request was parsed successfully and is valid against the server's schema, but the server couldn't resolve which operation to run.This occurs when a request containing multiple named operations doesn't specify which operation to run (i.e.,operationName), or if the named operation isn't included in the request.
BAD_REQUEST
An error occurred before your server could attempt to parse the given GraphQL operation.
INTERNAL_SERVER_ERROR
An unspecified error occurred.When Apollo Server formats an error in a response, it sets the code extension to this value if no other code is set.

Custom errors

You can create a custom errors and codes using the graphql package's GraphQLError class, like so:

TypeScript
1import { GraphQLError } from 'graphql';
2
3throw new GraphQLError(message, {
4  extensions: { code: 'YOUR_ERROR_CODE', myCustomExtensions },
5});

Custom errors can provide additional context, enabling your clients to understand why an error is happening. We recommend making clear errors for common cases, for example, when a user isn't logged in (UNAUTHENTICATED), or someone is forbidden from performing an action:

TypeScript
1import { GraphQLError } from 'graphql';
2
3throw new GraphQLError('You are not authorized to perform this action.', {
4  extensions: {
5    code: 'FORBIDDEN',
6  },
7});

Throwing errors

Apollo Server throws errors automatically when applicable. For example, it throws a GRAPHQL_VALIDATION_FAILED error whenever an incoming operation isn't valid against the server's schema.

Your resolvers can also throw errors in situations where Apollo Server doesn't do so automatically.

For example, this resolver throws a custom error if the integer value provided for a user's ID is less than 1:

Click to expand
TypeScript
1import { GraphQLError } from 'graphql';
2
3const typeDefs = `#graphql
4  type Query {
5    userWithID(id: ID!): User
6  }
7
8  type User {
9    id: ID!
10    name: String!
11  }
12`;
13
14const resolvers = {
15  Query: {
16    userWithID: (_, args) => {
17      if (args.id < 1) {
18        throw new GraphQLError('Invalid argument value', {
19          extensions: {
20            code: 'BAD_USER_INPUT',
21          },
22        });
23      }
24      // ...fetch correct user...
25    },
26  },
27};

If a resolver throws a generic error that is not a GraphQLError instance, that error is still thrown with an extensions field that includes a stacktrace and code (specifically INTERNAL_SERVER_ERROR), along with any other relevant error details.

Including custom error details

Whenever you throw a GraphQLError, you can add arbitrary fields to the error's extensions object to provide additional context to the client. You specify these fields in an object you provide to the error's constructor.

This example builds on the one above by adding the name of the GraphQL argument that was invalid:

Click to expand
TypeScript
1import { GraphQLError } from 'graphql';
2
3const typeDefs = `#graphql
4  type Query {
5    userWithID(id: ID!): User
6  }
7
8  type User {
9    id: ID!
10    name: String!
11  }
12`;
13
14const resolvers = {
15  Query: {
16    userWithID: (_, args) => {
17      if (args.id < 1) {
18        throw new GraphQLError('Invalid argument value', {
19          extensions: {
20            code: 'BAD_USER_INPUT',
21            argumentName: 'id',
22          },
23        });
24      }
25      // ...fetch correct user...
26    },
27  },
28};

This results in a response like the following:

Click to expand
JSON
1{
2  "errors": [
3    {
4      "message": "Invalid argument value",
5      "locations": [
6        {
7          "line": 2,
8          "column": 3
9        }
10      ],
11      "path": ["userWithID"],
12      "extensions": {
13        "code": "BAD_USER_INPUT",
14        "argumentName": "id",
15        "stacktrace": [
16          "GraphQLError: Invalid argument value",
17          "    at userWithID (/my-project/index.js:25:13)",
18          "    ...more lines..."
19        ]
20      }
21    }
22  ]
23}

Omitting or including stacktrace

The stacktrace error field is useful while developing and debugging your server, but you probably don't want to expose it to clients in production.

By default, Apollo Server omits the stacktrace field if the NODE_ENV environment variable is set to either production or test.

You can override this default behavior by passing the includeStacktraceInErrorResponses option to the constructor of ApolloServer. If includeStacktraceInErrorResponses is true, stacktrace is always included. If it's false, stacktrace is always omitted.

Note that when stacktrace is omitted, it's also unavailable to your application. To log error stacktraces without including them in responses to clients, see Masking and logging errors.

Masking and logging errors

You can edit Apollo Server error details before they're passed to a client or reported to Apollo Studio. This enables you to omit sensitive or irrelevant data.

For client responses

In the examples below, we use top-level await calls to start our server asynchronously. Check out our Getting Started guide to see how we configured our project to support this.

The ApolloServer constructor accepts a formatError hook that is run on each error before it's passed back to the client. You can use this function to log or mask particular errors.

The formatError hook receives two arguments: the first is the error formatted as a JSON object (to be sent with the response), and the second is the original error (wrapped in GraphQLError if thrown by a resolver).

The formatError function does not modify errors that are sent to Apollo Studio as part of usage reporting. See For Apollo Studio reporting.

The below example returns a user-friendly message whenever Apollo Server throws a GRAPHQL_VALIDATION_FAILED error:

TypeScript
1import { ApolloServer } from '@apollo/server';
2import { startStandaloneServer } from '@apollo/server/standalone';
3import { ApolloServerErrorCode } from '@apollo/server/errors';
4
5const server = new ApolloServer({
6  typeDefs,
7  resolvers,
8  formatError: (formattedError, error) => {
9    // Return a different error message
10    if (
11      formattedError.extensions.code ===
12      ApolloServerErrorCode.GRAPHQL_VALIDATION_FAILED
13    ) {
14      return {
15        ...formattedError,
16        message: "Your query doesn't match the schema. Try double-checking it!",
17      };
18    }
19
20    // Otherwise return the formatted error. This error can also
21    // be manipulated in other ways, as long as it's returned.
22    return formattedError;
23  },
24});
25
26const { url } = await startStandaloneServer(server);
27console.log(`🚀 Server listening at: ${url}`);

As another example, here we return a more generic error whenever the original error's message begins with Database Error: :

TypeScript
1formatError: (formattedError, error) => {
2  if (formattedError.message.startsWith('Database Error: ')) {
3    return { message: 'Internal server error' };
4  }
5
6  // Otherwise return the formatted error.
7  return formattedError;
8},

If you want to access the originally thrown error (without the JSON formatting), you can use formatError's second argument.

For example, if you are using a database package in your app and you'd like to do something when your server throws a specific type of database error:

TypeScript
1 formatError: (formattedError, error) => {
2    if (error instanceof CustomDBError) {
3      // do something specific
4    }
5  },

Note, if a resolver throws the error, a GraphQLError is wrapped around the initially thrown error. This GraphQLError neatly formats the error and contains useful fields, such as the path where the error occurred.

If you want to remove the outer GraphQLError to access the originally thrown error you can use unwrapResolverError from @apollo/server/errors. The unwrapResolverError function can remove the GraphQLError wrapping from a resolver error or return the error unaltered if it isn't from a resolver.

So, we can rewrite the above code snippet to work for errors thrown in and outside of resolvers, like so:

TypeScript
1import { unwrapResolverError } from '@apollo/server/errors';
2
3new ApolloServer({
4  formatError: (formattedError, error) => {
5    // unwrapResolverError removes the outer GraphQLError wrapping from
6    // errors thrown in resolvers, enabling us to check the instance of
7    // the original error
8    if (unwrapResolverError(error) instanceof CustomDBError) {
9      return { message: 'Internal server error' };
10    }
11  },
12});

To make context-specific adjustments to the error received by formatError (such as localization or personalization), consider creating a plugin that uses the didEncounterErrors lifecycle event to attach additional properties to the error. These properties can be accessed from formatError.

For Apollo Studio reporting

New in Apollo Server 4: error details are not included in traces by default. Instead, <masked> replaces each error's message, and the maskedBy error extension replaces all other extensions. The maskedBy extension includes the name of the plugin that performed the masking (ApolloServerPluginUsageReporting or ApolloServerPluginInlineTrace).

You can use Apollo Studio to analyze your server's error rates. By default, the operations sent to Studio as detailed traces don't contain error details.

If you do want error information sent to Studio, you can send every error, or you can modify or redact specific errors before they're transmitted.

To send all errors to Studio you can pass { unmodified: true } to sendErrors, like so:

TypeScript