1

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?

8
  • 2
    github.com/DefinitelyTyped/DefinitelyTyped/blob/… Commented Jan 16, 2021 at 11:11
  • @jonrsharpe: Thank you for the link to this type definition. Do I get it correctly that this type definition is not included in @types/express and I would have to add it manually to my project? If yes, why is that so? Commented Jan 16, 2021 at 16:34
  • That type is in @types/express-serve-static-core, which @types/express depends on. Commented Jan 16, 2021 at 16:35
  • Ah, I see. I (obviously) have @types/express installed. So why can't tsc automatically infer this type definition from my code? Commented Jan 16, 2021 at 16:41
  • 2
    I'm not sure why exactly the inference breaks down there, but the fixes are shown in stackoverflow.com/a/65517283/3001761. Commented Jan 16, 2021 at 17:08

1 Answer 1

2

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

Sign up to request clarification or add additional context in comments.

1 Comment

Thanks a lot for this alternative. I was looking for a solution with more automatic inference. See the comments above on how I finally solved it.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.