inversify-express-utils
inversify-express-utils copied to clipboard
Per-controller error handling
I couldn't find a way to handle all errors in a controller in one handler. For example:
@controller('/user')
export default class UserController implements interfaces.Controller {
@httpGet('/')
private async get(@request() req: Request, @response() res: Response) {
throw new Error('This should be handled in the error handler')
}
@httpPost('/')
private async create(@request() req: Request, @response() res: Response) {
throw new Error('This should be handled in the error handler')
}
// Ideally I would want something like
private errorHandler(err, req, res) {
// handle any error in the controller here
}
}
It's not working also in application level,
app.use(function (err, req, res, next) {
// errors in the controllers doesn't trigger this
});
Is there an elegant way to handle errors without repeating the code? (If I want for example to log any error to some logger)?
I was facing this same issue. Here's how we managed that:
1 - Create a handler file with the HTTP responses:
import { NextFunction, Request, Response } from 'express'
import * as HttpStatus from 'http-status'
...
class Handlers {
public onSuccess(res: Response, data: any): Response {
return res.status(HttpStatus.OK).json(data)
}
public onConflict(res: Response, message: string, err: any): Response {
return res.status(HttpStatus.CONFLICT).json({ message })
}
// Other HTTP responses here
}
2 - Create a middleware to bind in the setup and to call the Handler class
import { NextFunction, Request, Response } from "express";
import Handlers from "../handlers"; // The handler you created
...
export default function errorHandlerMiddleware(err: Error, req: Request, res: Response, next: NextFunction): any {
if (err instanceof RecordNotFoundError) {
return Handlers.onNotFound(res, err.message);
}
if (err instanceof ConflictError) {
return Handlers.onConflict(res, err.message, err);
}
// If no critearia is matched, return a 500 error
return Handlers.onError(res, err.message, err);
}
3 - Config your app to bind the middleware
const app = new InversifyExpressServer(container);
app.setConfig(app => {
// Your configs
app.use(cors());
...
// bind the middleware
app.setErrorConfig((app) => {
app.use(errorHandlerMiddleware);
});
}
4 - Try/catch your controller and use the 'next' function in the catch block. It'll automatically call the middleware as we are using the Chain of Responsability.
import { Response } from "express";
// Import your handler to use the onSuccess method
import Handlers from "../../../core/handlers";
// Import the next function
import { httpGet, next, response } from "inversify-express-utils";
@httpGet("/")
public async getStuff(
@response() res: Response,
@next() next: Function
): Promise<Response> {
try {
const data = await myData();
return Handlers.onSuccess(res, data);
} catch (error) {
next(error);
}
}
And if you want to log the errors, you can call your logger (we are using winston) in the Handler just before the return, as below:
public onConflict(res: Response, message: string, err: any): Response {
logger.error(`ERRO: ${err.name}, Message: ${err.message} - Parameters: [${err.parameters}] `)
return res.status(HttpStatus.CONFLICT).json({ message })
}