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 };
});
}
}