A small library to handle graphql and apollo errors in a better way
This library is fully tested with 100% coverage
Error handling requires few core features to be useful:
- Ability to customize error in well defined structure cross app
- Ability to hook the error bubbling (In order to log or store the errors somewhere)
- Ability to send the error to the client while sending all the relevant information yet keeping all the sensitive information only on the server
Looking around I found only 2 libraries dealing with errors in graphql and apollo - graphql-errors , apollo-errors.
Both libraries are great start, but they are not powerful enough for my opinion, therefore I decided to write my own error handler. Talking with some friends, I understand I'm not alone with this need, so I created this library as open source.
(Look in the spec files to understand more)
Configure apollo error formatting
import express from 'express';
import bodyParser from 'body-parser';
import { formatErrorGenerator } from 'graphql-apollo-errors';
import schema from './schema';
// You can use what ever you want, this is just an example
var logger = require('minilog')('errors-logger');
const formatErrorOptions = {
logger,
publicDataPath: 'public', // Only data under this path in the data object will be sent to the client (path parts should be separated by . - some.public.path)
showLocations: true, // whether to add the graphql locations to the final error (default false)
showPath: true, // whether to add the graphql path to the final error (default false)
hideSensitiveData: false, // whether to remove the data object from internal server errors (default true)
hooks: {
// This run on the error you really throw from your code (not the graphql error - it means not with path and locations)
onOriginalError: (originalError) => {logger.info(originalError.message)},
// This will run on the processed error, which means after we convert it to boom error if needed
// and after we added the path and location (if requested)
// If the error is not a boom error, this error won't include the original message but general internal server error message
// This will run before we take only the payload and the public path of data
onProcessedError: (processedError) => {logger.info(processedError.message)},
// This will run on the final error, it will only contains the output.payload, and if you configured the publicDataPath
// it will only contain this data under the data object
// If the error is internal error this error will be a wrapped internal error which not contains the sensitive details
// This is the error which will be sent to the client
onFinalError: (finalError) => {logger.info(finalError.message)},
},
nonBoomTransformer: (nonBoomError) => {error instanceof GraphQLError ? SevenBoom.badRequest(error.message) : SevenBoom.badImplementation(error)}
// Optional function to transform non-Boom errors, such as those from Apollo & other 3rd-party libraries, into Boom errors
};
const formatError = formatErrorGenerator(formatErrorOptions);
const app = express();
app.use('/graphql',
bodyParser.json(),
graphqlExpress({
formatError,
schema
})
);
app.listen(8080)
Init SevenBoom object The defalut args for SevenBoom are
const defaultArgsDef = [
{
name : 'errorCode',
order: 1
}, {
name : 'timeThrown',
order: 2,
default: null
}, {
name : 'guid',
order: 3,
default: null
}
];
If you want you can change it using the initSevenBoom function:
import { initSevenBoom } from 'graphql-apollo-errors';
const customArgsDefs = [
{
name : 'errorCode',
order: 1
}
];
initSevenBoom(customArgsDefs);
Use SevenBoom to create your custom error and throw it.
import { SevenBoom } from 'graphql-apollo-errors';
// A resolver which throws error
const getUserByIdResolver = (root, { userId }, context) => {
UserService.getUserById(userId)
.then((user) => {
if (user) return user;
const errorMessage = `User with id: ${userId} not found`;
const errorData = { userId };
const errorName = 'USER_NOT_FOUND';
const err = SevenBoom.notFound(errorMessage, errorData, errorName);
throw(err);
}
}
Enjoy your shiny error on the client
{
"data": {},
"errors": [
{
statusCode: 404,
error: 'Not Found',
message: 'User with id: 123 not found.',
code: 'USER_NOT_FOUND',
timeThrown: "2017-01-16T21:25:58.536Z",
guid: 'b6c44655-0aae-486a-8d28-533db6c6c343',
data: {
userId: '123'
}
}
]
}
There is a lot of changes from v1. (In the implementation, which leads to API changes)
- onStoredError hook is no longer exist (actually the onOriginalError result is the same as the onStoredError before)
- You should not use the throwError any more (it was deleted), you can use the native throw now.
In general this library contain 2 parts:
- SevenBoom - A small library i wrote to create customize errors
- format error function - which knows to fetch the real error, hide sensitive server data, add some hooks points and configuration, and pass it to the client.
MIT - Do what ever you want
I'm open to hear any feedback - new ideas, bugs, needs. Feel free to open issues / PR
Hey dude! Help me out for a couple of 🍻!