Hauke P.
Hauke P.

Reputation: 2823

Express.js & TypeScript: How to simplify parameter types for app.use callback for error handling

Having some experience with TypeScript but being new to Express.js, I want to create a generic error handler in my Express.js app written in TypeScript. The following code works in JavaScript:

// catch 404 and forward to error handler
app.use((req, res, next) => {
  next(new createError[404]());
});

// error handler
app.use((err, req, res, next) => {
  // set locals, only providing error in development
  res.locals.message = err.message;
  res.locals.error = req.app.get('env') === 'development' ? err : {};

  // render the error page
  res.status(err.status || 500);
  res.render('error');
});

When trying to compile this with TypeScript however, it does not like the anonymous function in the second app.use call:

error TS7006: Parameter 'err' implicitly has an 'any' type.
app.use((err, req, res, next) => {
         ~~~

error TS7006: Parameter 'req' implicitly has an 'any' type.
app.use((err, req, res, next) => {
              ~~~

error TS7006: Parameter 'res' implicitly has an 'any' type.
app.use((err, req, res, next) => {
                   ~~~

error TS7006: Parameter 'next' implicitly has an 'any' type.
app.use((err, req, res, next) => {
                        ~~~~

Apparently, TypeScript was able to infer the type information of the parameters in the anonymous function for the first app.use call. It was not able to infer it for the second one though.

When changing the line into the following, TypeScript at least does not spit out any errors anymore... but eslint is angry with me because now I'm using implicit anys:

app.use((err: any, req: any, res: any, next: any) => {

Alternatively, Visual Studio Code allows me to apply a quick fix "Infer parameter types from usage" on the second anonymous function. This turns the second app.use line into this beauty (line breaks added by me):

app.use((err: { message: any; status: any; },
         req: { app: { get: (arg0: string) => string; }; },
         res: { locals: { message: any; error: any; };
                status: (arg0: any) => void;
                render: (arg0: string) => void; },
         next: any) => {

While this would do its job, I feel that this beast is pretty much unmaintainable and incomprehensible.

So now I wonder: How would you implement something like this without sacrificing maintainability and comprehensibility?

Upvotes: 1

Views: 1694

Answers (1)

iulian3000
iulian3000

Reputation: 1380

The error you get is comming from the settings you have in tsconfig.json

"noImplicitAny": true

It is good practice to have strict typescript. This is how I handle the errors and this are the types for each argument. Install this package

npm i -D @types/express
import { NextFunction, Request, Response } from 'express';
...
this.app.use((error: unknown, req: Request, res: Response, next: NextFunction): void => {
      if (error instanceof HttpException) {
        res.status(error.code).send({ message: error.message });
        return;
      }

      if (error instanceof Error && error.message) {
        if (error.message === 'Method not implemented') {
          res.status(501).send({ message: httpStatusCode(501) });
          return;
        }

        res.status(500).send({ message: error.message });
        return;
      }

      res.status(500).send({ message: httpStatusCode(500) });
      return;
    });

And this is the HttpException class if you wonder what is

export class HttpException {
  constructor(public code: number, public message: string = httpStatusCode(code)) {}
}

With this setup in conjuntion with an async handler for the middleware you can throw errors or even have promises that fails and the errors are handled in one place.

enter image description here

enter image description here

Upvotes: 2

Related Questions