Apollo Server 3 is officially end-of-life as of 22 October 2024.

Learn more about upgrading.

Error handling

Making errors actionable on the client and server


Whenever Apollo Server encounters errors while processing a GraphQL operation, its response to the client includes an errors array that contains 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) an exception.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        "exception":{
14          "stacktrace":[
15            "GraphQLError: Cannot query field \"__typenam\" on type \"Query\".",
16            "    at Object.Field (/my_project/node_modules/graphql/validation/rules/FieldsOnCorrectTypeRule.js:48:31)",
17            "    ...additional lines...",
18          ]
19        }
20      }
21    }
22  ]
23}

To help with debugging, Apollo Server defines error subclasses that represent different types of errors that can occur while handling a GraphQL operation (such as SyntaxError and ValidationError). These subclasses each return a different error code, which enables requesting clients to respond differently to different error types.

These built-in error subclasses inherit from the generic ApolloError class, and they're all defined in the apollo-server-errors package. You can also create your own custom errors and codes.

Error codes

Code /
Subclass
Description
GRAPHQL_PARSE_FAILED
SyntaxError
The GraphQL operation string contains a syntax error.
GRAPHQL_VALIDATION_FAILED
ValidationError
The GraphQL operation is not valid against the server's schema.
BAD_USER_INPUT
UserInputError
The GraphQL operation includes an invalid value for a field argument.
UNAUTHENTICATED
AuthenticationError
The server failed to authenticate with a required data source, such as a REST API.
FORBIDDEN
ForbiddenError
The server was unauthorized to access a required data source, such as a REST API.
PERSISTED_QUERY_NOT_FOUND
PersistedQueryNotFoundError
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
PersistedQueryNotSupportedError
A client sent the hash of a query string to execute via automatic persisted queries, but the server has disabled APQ.
INTERNAL_SERVER_ERROR
None
An unspecified error occurred.This is the default error code returned by any ApolloError instance that doesn't specify a different code.

Throwing errors

Apollo Server throws errors of most built-in types automatically when applicable. For example, it throws a ValidationError 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 UserInputError if the integer value provided for a user's ID is less than 1:

Click to expand
JavaScript
1const {
2  ApolloServer,
3  gql,
4  UserInputError
5} = require('apollo-server');
6
7const typeDefs = gql`
8  type Query {
9    userWithID(id: ID!): User
10  }
11
12  type User {
13    id: ID!
14    name: String!
15  }
16`;
17
18const resolvers = {
19  Query: {
20    userWithID: (parent, args, context) => {
21      if (args.id < 1) {
22        throw new UserInputError('Invalid argument value');
23      }
24      // ...fetch correct user...
25    },
26  },
27};

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

Including custom error details

Whenever you throw an ApolloError, 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
JavaScript
1const {
2  ApolloServer,
3  gql,
4  UserInputError
5} = require('apollo-server');
6
7const typeDefs = gql`
8  type Query {
9    userWithID(id: ID!): User
10  }
11
12  type User {
13    id: ID!
14    name: String!
15  }
16`;
17
18const resolvers = {
19  Query: {
20    userWithID: (parent, args, context) => {
21      if (args.id < 1) {
22        throw new UserInputError('Invalid argument value', {
23          argumentName: 'id'
24        });
25      }
26      // ...fetch correct user...
27    },
28  },
29};

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": [
12        "userWithID"
13      ],
14      "extensions": {
15        "argumentName": "id",
16        "code": "BAD_USER_INPUT",
17        "exception": {
18          "stacktrace": [
19            "UserInputError: Invalid argument value",
20            "    at userWithID (/my-project/index.js:25:13)",
21            "    ...more lines...",
22          ]
23        }
24      }
25    }
26  ]
27}

Custom errors

You can create a custom error by defining your own subclass of ApolloError, or by initializing an ApolloError object directly:

Subclass with custom error code

TypeScript
1import { ApolloError } from 'apollo-server-errors';
2
3export class MyError extends ApolloError {
4  constructor(message: string) {
5    super(message, 'MY_ERROR_CODE');
6
7    Object.defineProperty(this, 'name', { value: 'MyError' });
8  }
9}
10
11throw new MyError('My error message')

Direct initialization

TypeScript
1import { ApolloError } from 'apollo-server-errors';
2
3throw new ApolloError('My error message', 'MY_ERROR_CODE', myCustomExtensions);

Omitting or including stacktrace

The exception.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 exception.stacktrace field if the NODE_ENV environment variable is set to either production or test.

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

Note that when exception.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

The ApolloServer constructor accepts a formatError function that is run on each error before it's passed back to the client. You can use this function to mask particular errors, as well as for logging.

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

This example returns a more generic error whenever the original error's message begins with Database Error: :

JavaScript
1const server = new ApolloServer({
2  typeDefs,
3  resolvers,
4  csrfPrevention: true,
5  cache: "bounded",
6  plugins: [
7    ApolloServerPluginLandingPageLocalDefault({ embed: true }),
8  ],
9  formatError: (err) => {
10    // Don't give the specific errors to the client.
11    if (err.message.startsWith('Database Error: ')) {
12      return new Error('Internal server error');
13    }
14
15    // Otherwise return the original error. The error can also
16    // be manipulated in other ways, as long as it's returned.
17    return err;
18  },
19});
20
21server.listen().then(({ url }) => {
22  console.log(`🚀 Server ready at ${url}`);
23});

The error instance received by formatError (a GraphQLError) contains an originalError property, which represents the original error thrown in the resolver. You can use this property to obtain the instanceof the error class, such as AuthenticationError or ValidationError:

JavaScript
1  formatError(err) {
2    if (err.originalError instanceof AuthenticationError) {
3      return new Error('Different authentication error message!');
4    }
5  },

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

You can use Apollo Studio to analyze your server's error rates. If you connect Apollo Server to Studio, all errors are sent to Studio by default. If you don't want certain error information to be sent to Studio (either because the error is unimportant or because certain information is confidential), you can modify or redact errors entirely before they're transmitted.

To accomplish this, you can provide a rewriteError function to the usage reporting plugin.

The usage reporting plugin is installed automatically with its default configuration if you provide an Apollo API key to Apollo Server. To define a custom rewriteError function, you need to install the plugin explicitly with a custom configuration, as shown in examples below.

Your rewriteError function is called for each error (a GraphQLError or an ApolloError) to be reported to Studio. The error is provided as the function's first argument. The function can either:

  • Return a modified form of the error (e.g., by changing the err.message to remove potentially sensitive information)

  • Return null to prevent the error from being reported entirely

For federated graphs, instead define rewriteError in each subgraph's inline trace plugin. Do not define it in the gateway.

Example: Ignoring common low-severity errors

Let's say our server is throwing an AuthenticationError whenever an incorrect password is provided. We can avoid reporting these errors to Apollo Studio by defining rewriteError, like so:

JavaScript
1const { ApolloServer, AuthenticationError } = require("apollo-server");
2const {
3  ApolloServerPluginUsageReporting,
4  ApolloServerPluginLandingPageLocalDefault,
5} = require('apollo-server-core');
6const server = new ApolloServer({
7  typeDefs,
8  resolvers,
9  csrfPrevention: true,
10  cache: "bounded",
11  plugins: [
12    ApolloServerPluginUsageReporting({
13      rewriteError(err) {
14        // Return `null` to avoid reporting `AuthenticationError`s
15        if (err instanceof AuthenticationError) {
16          return null;
17        }
18
19        // All other errors will be reported.
20        return err;
21      }
22    }),
23    ApolloServerPluginLandingPageLocalDefault({ embed: true }),
24  ],
25});

This example configuration ensures that any AuthenticationError that's thrown within a resolver is only reported to the client, and never sent to Apollo Studio. All other errors are transmitted to Studio normally.

Example: Filtering errors based on other properties

When generating an error (e.g., new ApolloError("Failure!")), the error's message is the most common property (in this case it's Failure!). However, any number of properties can be attached to the error (such as a code property).

We can check these properties when determining whether an error should be reported to Apollo Studio using the rewriteError function as follows:

JavaScript
1const { ApolloServer } = require("apollo-server");
2const {
3  ApolloServerPluginUsageReporting,
4  ApolloServerPluginLandingPageLocalDefault,
5} = require('apollo-server-core');
6const server = new ApolloServer({
7  typeDefs,
8  resolvers,
9  csrfPrevention: true,
10  cache: "bounded",
11  plugins: [
12    ApolloServerPluginUsageReporting({
13      rewriteError(err) {
14        // Using a more stable, known error property (e.g. `err.code`) would be
15        // more defensive, however checking the `message` might serve most needs!
16        if (err.message && err.message.startsWith("Known error message")) {
17          return null;
18        }
19
20        // All other errors should still be reported!
21        return err;
22      }
23    }),
24    ApolloServerPluginLandingPageLocalDefault({ embed: true }),
25  ],
26});

This example configuration ensures that any error that starts with Known error message is not transmitted to Apollo Studio, but all other errors are sent as normal.

Example: Redacting the error message

If it is necessary to change an error prior to reporting it to Apollo Studio (for example, if there is personally identifiable information in the error message), the rewriteError function can also help.

Consider an example where the error contains a piece of information like an API key:

JavaScript
1throw new ApolloError("The x-api-key:12345 doesn't have sufficient privileges.");

The rewriteError function can ensure that such information is not sent to Apollo Studio and potentially revealed outside its intended scope:

JavaScript
1const { ApolloServer } = require("apollo-server");
2const {
3  ApolloServerPluginUsageReporting,
4  ApolloServerPluginLandingPageLocalDefault,
5} = require('apollo-server-core');
6const server = new ApolloServer({
7  typeDefs,
8  resolvers,
9  csrfPrevention: true,
10  cache: "bounded",
11  plugins: [
12    ApolloServerPluginUsageReporting({
13      rewriteError(err) {
14        // Make sure that a specific pattern is removed from all error messages.
15        err.message = err.message.replace(/x-api-key:[A-Z0-9-]+/, "REDACTED");
16        return err;
17      }
18    }),
19    ApolloServerPluginLandingPageLocalDefault({ embed: true }),
20  ],
21});

In this case, the error above is reported to Apollo Studio as:

Text
1The REDACTED doesn't have sufficient privileges.

Returning HTTP status codes

GraphQL, by design, does not use the same conventions from REST to communicate via HTTP verbs and status codes. Client information should be contained in the schema or as part of the standard response errors field. We recommend using the included Error Codes or Custom Errors for error consistency rather than directly modifying the HTTP response.

You can set custom fields on your HTTP response by using a plugin. Be aware that GraphQL client libraries may not treat all response status codes the same, and so it will be up to your team to decide what patterns to use.

As an example, here is how you could set a custom response header and status code based on a GraphQL error:

JavaScript
1const setHttpPlugin = {
2  async requestDidStart() {
3    return {
4      async willSendResponse({ response }) {
5        response.http.headers.set('Custom-Header', 'hello');
6        if (response?.errors?.[0]?.message === 'teapot') {
7          response.http.status = 418;
8        }
9      }
10    };
11  }
12};
13
14const server = new ApolloServer({
15  typeDefs,
16  resolvers,
17  csrfPrevention: true,
18  cache: 'bounded',
19  plugins: [
20    setHttpPlugin,
21    ApolloServerPluginLandingPageLocalDefault({ embed: true }),
22  ],
23});
Feedback

Edit on GitHub

Forums