Error Handler middleware and Custom Error Class - nuthanc/microservice GitHub Wiki

Error Handler middleware

  • Only requirement for being error-handling middleware is having 4 arguments with the correct order
  • Very important goal: Consistent error message
  • Wire error-handler to index.ts or app.ts of various services
app.use(errorHandler);
  • Then in signup.ts(auth service), throw an Error instead of sending the error as response(For consistent structured error response)
if (existingUser) {
      throw new BadRequestError('Email in use');
}
  • When Error is thrown, it's automatically picked up by that error-handler middleware
// error-handler.ts
import { Request, Response, NextFunction } from 'express';
import { CustomError } from '../errors/custom-error';

export const errorHandler = (
  err: Error,
  req: Request,
  res: Response,
  next: NextFunction
) => {
  if (err instanceof CustomError) {
    return res.status(err.statusCode).send({ errors: err.serializeErrors() });
  }
  console.error(err);
  res.status(400).send({ errors: [{ message: 'Something went wrong' }] });
};

CustomError

  • Create CustomError Abstract class(subclass of Error) with serializeErrors method and statusCode property
  • Abstract class: is a restricted class that cannot be used to create objects (to access it, it must be inherited from another class)
  • abstract property to make it as compulsory property in subclass
  • If we are extending a built-in class, we need to use Object.setPrototypeOf
// custom-error.ts in common/src/errors
export abstract class CustomError extends Error {
  abstract statusCode: number;

  constructor(message: string) {
    super(message);

    Object.setPrototypeOf(this, CustomError.prototype);
  }

  abstract serializeErrors(): { message: string; field?: string }[];
}

// not-authorized-error.ts in common/src/errors
import { CustomError } from './custom-error';

export class NotAuthorizedError extends CustomError {
  statusCode = 401;

  constructor() {
    super('Not Authorized');

    Object.setPrototypeOf(this, NotAuthorizedError.prototype);
  }

  serializeErrors() {
    return [{ message: 'Not Authorized' }];
  }
}
// auth/src/routes/sigin.ts

import express, { Request, Response } from 'express';
import { body } from 'express-validator';
import { validateRequest, BadRequestError} from '@rztickets/common';
import { User } from '../models/user';
import { Password } from '../services/password';
import jwt from 'jsonwebtoken';


const router = express.Router();

router.post(
  '/api/users/signin',
  [
    body('email').isEmail().withMessage('Email must be valid'),
    body('password')
      .trim()
      .notEmpty()
      .withMessage('You must supply a password'),
  ],
  validateRequest,
  async (req: Request, res: Response) => {
    const { email, password } = req.body;

    const existingUser = await User.findOne({ email });
    if (!existingUser) {
      throw new BadRequestError('Invalid credentials');
    }
.......
// validate-request.ts in common/src/middlewares
import { Request, Response, NextFunction } from 'express';
import { validationResult } from 'express-validator';
import { RequestValidationError } from '../errors/request-validation-error';

export const validateRequest = (
  req: Request,
  res: Response,
  next: NextFunction
) => {
  const errors = validationResult(req);

  if (!errors.isEmpty()) {
    // .array() Returns: an array of validation errors.
    throw new RequestValidationError(errors.array());
  }

  next();
};
// request-validation-error.ts in common/src/errors
import { ValidationError } from 'express-validator';
import { CustomError } from './custom-error';

export class RequestValidationError extends CustomError {
  statusCode = 400;

  constructor(public errors: ValidationError[]) {
    super('Invalid request parameters'); // Only for logging purpose

    // Only because we are extending a built-in class
    Object.setPrototypeOf(this, RequestValidationError.prototype);
  }

  serializeErrors() {
    return this.errors.map((err) => {
      return { message: err.msg, field: err.param };
    });
  }
}