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
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 |
---|---|
| The GraphQL operation string contains a syntax error. |
| The GraphQL operation is not valid against the server's schema. |
| The GraphQL operation includes an invalid value for a field argument. |
| The server failed to authenticate with a required data source, such as a REST API. |
| The server was unauthorized to access a required data source, such as a REST API. |
| A client sent the hash of a query string to execute via automatic persisted queries, but the query was not in the APQ cache. |
| A client sent the hash of a query string to execute via automatic persisted queries, but the server has disabled APQ. |
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
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
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
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
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
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 stacktrace
s 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:
:
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
:
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 thedidEncounterErrors
lifecycle event to attach additional properties to the error. These properties can be accessed fromformatError
.
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 throw
ing an AuthenticationError
whenever an incorrect password is provided. We can avoid reporting these errors to Apollo Studio by defining rewriteError
, like so:
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:
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:
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:
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:
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:
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});