Skip to content

A Express middleware for organizing and returning custom errors in JSON APIs

License

Notifications You must be signed in to change notification settings

antonio-muniz/errorizer

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

24 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

errorizer

Travis Coveralls npm David license

An Express error middleware for organizing and returning custom errors in JSON APIs.

Without appending functions to Express objects, this package lets you:

  • return useful, standardized error responses to your clients;
  • declare API errors once, use them anywhere;
  • respond errors with ease, using only meaningful error codes;

Table of contents


Requirements

Usage

const express = require('express');
const errorizer = require('errorizer');

let app = express();

app.post('/users', (req, res, next) => {
  if (!isEmail(req.body.email)) {
    return next('INVALID_EMAIL');
  }
  if (emailExists(req.body.email)) {
    return next('EMAIL_ALREADY_EXISTS');
  }
  // ...
});

app.use(errorizer({
  INVALID_EMAIL: {
    status: 400,
    message: 'The email is not in a valid format'
  },
  EMAIL_ALREADY_EXISTS: {
    status: 400,
    message: 'The specified email already belongs to another user'
  }
}));

app.listen(3000);
// Response example

HTTP 400
Content-Type: "application/json; charset=utf-8"
{
  "status": "Bad Request",
  "code": "INVALID_EMAIL",
  "message": "The email is not in a valid format"
}

Defining errors

The API errors must be declared in an object and passed as a parameter to the main function.

let errors = { /* ... */ };
let errorMiddleware = errorizer(errors);
app.use(errorMiddleware);

The errors object must contain error codes as keys. The error codes will identify the error in your app and should also be used by clients.

As shown in the examples, each error may have these properties:

  • status (number, required): the HTTP status code for the error.
  • message (string, required): a brief description of the error.
  • detail (string/object/array, optional): a property for adding more details (e.g.: a longer description, a link to the documentation, etc.)

PROTIP: Declare your errors in a separate file (e.g.: errors.js) to keep your application startup file clean.

Example

// errors.js

module.exports = {

  INVALID_EMAIL: {
    status: 400,
    message: 'The email is not in a valid format'
  },

  EMAIL_ALREADY_EXISTS: {
    status: 400,
    message: 'The specified email already belongs to another user'
  },

  USER_NOT_FOUND: {
    status: 404,
    message: 'Could not find a user with the specified ID'
  },

  INVALID_LOGIN_OR_PASSWORD: {
    status: 401,
    message: 'The login and/or password are incorrect'
  }

};

Responding errors

When an error condition is met somewhere in the code, all that needs to be done is to make the error code reach Express' next function.

For convenience, you can specify the error code in multiple ways:

  • as a string;
  • within an instance of Error;
  • in the code property of an object

Example

errorizer({
  USER_NOT_FOUND: {
    status: 404,
    message: 'The specified user could not be found',
    detail: {
      description: 'This error occurs when the requested user does not exist',
      docs: 'http://docs.myapp.com/v1/errors/USER_NOT_FOUND'
    }
  }
});

// different ways to respond an error
function(req,res,next) => {
  // plain string
  next('USER_NOT_FOUND');

  // Error instance
  next(new Error('USER_NOT_FOUND'));

  // "code" property in object
  next({ code: 'USER_NOT_FOUND' });
}

// response sent for any of the above
HTTP 404
Content-Type: "application/json; charset=utf-8"
{
  "status": "Not Found",
  "code": "USER_NOT_FOUND",
  "message": "The specified user could not be found",
  "detail": {
    "description": "This error occurs when the requested user does not exist",
    "docs": "http://docs.myapp.com/v1/errors/USER_NOT_FOUND"
  }
}

Customizing errors with templates

Sometimes, you need an error response to contain values which may vary on every occurrence of it.

For example, a form validation response with information about all fields with invalid values for UI highlighting.

HTTP 400
Content-Type: "application/json; charset=utf-8"
{
  "status": "Bad Request",
  "code": "FORM_VALIDATION_ERROR",
  "message": "Some form fields contain invalid values",
  "detail": [
    {
      "field": "email",
      "message": "The email is required"
    },
    {
      "field": "password",
      "message": "The password is required"
    }
  ]
}

Or an error response containing data from the request.

HTTP 400
Content-Type: "application/json; charset=utf-8"
{
  "status": "Bad Request",
  "code": "INVALID_DATE",
  "message": "07/24/2017 is not a valid date",
  "detail": {
    "format": "YYYY-MM-dd",
    "docs": "http://docs.myapp.com/v1/errors/INVALID_DATE"
  }
}

You achieve this by declaring error templates and using the custom property!

Instead of declaring FORM_VALIDATION_ERROR and INVALID_DATE as static error objects, you can declare them as functions that return error objects (templates).

// errors.js

module.exports = {

  FORM_VALIDATION_ERROR: function(err) {
    return {
      status: 400,
      message: 'Some form fields contain invalid values',
      detail: err.custom
    }
  },

  INVALID_DATE: function(err) {
    return {
      status: 400,
      message: err.custom.date + ' is not a valid date',
      detail: {
        format: err.custom.format,
        docs: "http://docs.myapp.com/v1/errors/INVALID_DATE"
      }
    }
  }

};

Then, your route handlers can customize the errors by sending data in the custom property.

function(req, res, next) => {
  let formErrors = validateForm(req);
  if (formErrors.length > 0) {
    return next({
      code: 'FORM_VALIDATION_ERROR',
      custom: formErrors
    });
  }
};

function(req, res, next) => {
  const dateFormat = 'YYYY-mm-dd';
  if (!dateFormatIsCorrect(req.body.date, dateFormat)) {
    return next({
      code: 'INVALID_DATE',
      custom: {
        date: req.body.date,
        format: dateFormat
      }
    });
  }
};

Unexpected errors

If there's a bug in the code and an error is thrown out of nowhere, the error middleware will take care of it and generate the error below:

HTTP 500
Content-Type: "application/json; charset=utf-8"
{
  "status": "Internal Server Error",
  "code": "UNEXPECTED_ERROR",
  "message": "An unexpected error has occurred while fulfilling the request"
}

Contributing

Feel free to contribute. All feedback is welcome!

Before opening a pull request, make sure to have your changes covered by tests.

License

MIT