Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Per-controller error handling #335

Open
avishayg opened this issue Dec 18, 2020 · 1 comment
Open

Per-controller error handling #335

avishayg opened this issue Dec 18, 2020 · 1 comment

Comments

@avishayg
Copy link

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)?

@FilipeVeber
Copy link

FilipeVeber commented Nov 17, 2021

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants