Custom errors and error reporting in GraphQL

Konstantin Tarkus
codeburst
Published in
3 min readNov 26, 2017

--

First of all, big thanks to Facebook developers for open sourcing and maintaining GraphQL.js library. It can’t express how well it works for me and my team and I like really how simple yet flexible it is. As Albert Einstein once said:

Everything should be as simple as it can be, but not simpler.

Below you can find a couple of code recipes demonstrating how to throw custom errors as well as log errors in an API project powered by Node.js (v8 or newer), JavaScript (using Babel compiler) and GraphQL.js.

Let’s assume, that you have videos top-level field in your GraphQL schema, that conditionally throws “Unavailable in your country” error depending on the user’s IP address visiting your website. You could just throw a normal error inside of the videos.resolve() method to handle that use case as follows:

resolve(parent, args, context) {
if (context … ) {
throw new Error('Unavailable in your country.');
}

}

But, if you’re planning to reuse that error in multiple places, you may want to create a custom error type for it containing the unified error message and error code:

export class LegallyUnavailableError extends Error {
code = 451;
message = this.message ||
'This content is not available in your country';
}

Now, let’s alter the code sample above to use that custom error. It would look something like this (videos query field):

import { LegallyUnavailableError } from '../errors';export default {
name: 'Videos',
type: new GraphQLList(VideoType),
resolve(parent, args, context) {
if (context … ) {
throw new LegallyUnavailableError();
}

}
}

If you like, you could also override the default error message providing a localized version as follows:

resolve(parent, args, { t }) {
if (context … ) {
throw new LegallyUnavailableError(t(
'Sorry, but the video content is ' +
'not available in your country.'));
}

}

Where t is a “translator” function (I will cover that in one of the future blog posts).

One caveat here is that the error.code field will not be available within the GraphQL response. In order to make it work, you’d need to provide a custom implementation of the formatError() function in express-graphql settings as follows:

import express from 'express';
import graphql from 'express-graphql';
import schema from './schema';
import errors from './errors';
import Context from './Context;
const app = express();app.use('/graphql', graphql(req => ({
schema,
context: new Context(req),
graphiql: process.env.NODE_ENV === 'production',
formatError(err) {
errors.report(err, req); // <-- log the error
return {
message: err.message,
code: err.originalError && err.originalError.code, // <--
locations: err.locations,
path: err.path
};
}
})));

Similarly, you can expose some additional metadata within the error object returned to a client, like the list of validation (state) errors. See “Validation and User Errors in GraphQL Mutations”.

The errors.report(…) is a custom utility function that asynchronously logs GraphQL errors to Google Stackdriver, Rollbar etc. For example (errors.js):

import Rollbar from 'rollbar';const rollbar = new Rollbar('POST_SERVER_ITEM_ACCESS_TOKEN');function report(err, req) {
rollbar.error(err, req);
}
export class UnauthorizedError extends Error { … }
export class ForbiddenError extends Error { … }
export class LegallyUnavailableError extends Error { … }
export default { report };

For a complete example, visit Node.js API Starter Kit on GitHub.

If you’d like to discuss anything GraphQL-related, feel free to reach out to me on Twitter. Happy coding!

--

--

Empowering startups with cutting-edge expertise: software architecture, optimal practices, database design, web infrastructure, and DevOps mastery.