docs.nestjs.com icon indicating copy to clipboard operation
docs.nestjs.com copied to clipboard

Cover error handling of Middleware in documentation

Open l0rn opened this issue 6 years ago • 9 comments

I'd like to file a:

[ X ] Documentation issue or request

Nowhere in Nest v5 docs is an example to be found how to handle errors originating from middleware. Neither in the Exception Filter section nor in the Middleware section.

I also tried to put my Filter on the Middleware itself - doesn't get executed either:

import { CsurfMiddleware as NestCsurfMiddleware } from '@nest-middlewares/csurf';
import { UseFilters, Injectable } from '@nestjs/common';
import { CSRFExceptionFilter } from './csrf-exception.filter';

@UseFilters(
    new CSRFExceptionFilter(),
)
@Injectable()
export class CsurfMiddleware extends NestCsurfMiddleware {}

This guy seems to have the same question but it's left unanswered: https://github.com/nestjs/nest/issues/294

I would argue this is a rather common use case as Middleware is based on express middleware in many cases throwing all kinds of typescript unfriendly errors you might want to catch / transform.

So a concrete documentation requests would be:

  • How / where to handle errors coming from middleware

I'd be happy to contribute it myself, but I couldn't figure out how to do it until now. So if someone points me to the right direction i will write it up for the docs.

l0rn avatar Dec 15 '18 12:12 l0rn

Would just like to echo that as a new Nest user this has been difficult. Since most of the examples are for Express, it's hard to figure out how to make error handling work. In our case, throwing an HttpException doesn't seem to work when done from our own middleware, only if done in the middleware use function itself. For example, take this middleware:

public use(req: Request, res: Response, next: () => void) {
    const errorHandler = (err) => {
      if(err) {
        let errorDetail: string;
        if(err.name === 'UnauthorizedError') errorDetail = `Authorization failed: please try logging in again`;

        throw new HttpException({
          status: HttpStatus.FORBIDDEN,
          error: errorDetail,
        }, 403);
      }
      
      next();
    };

    this.jwt({
      secret: this.expressJwtSecret({
        cache: true,
        rateLimit: true,
        jwksRequestsPerMinute: 5,
        jwksUri: `${ACT_AUTH_ISSUER}/.well-known/jwks.json`
      }),
      audience: ACT_AUTH_AUDIENCE,
      algorithms: ['RS256']
    })(req, res, errorHandler);
  }

If I leave that throw where it is, the application stops. If I move it above the this.jwt call, it works as expected. I'm sure I'm doing something dumb, I just don't know what it is!

mcblum avatar May 24 '19 13:05 mcblum

Ok scratch that... I kind of forgot I could just use Express directly. The following worked:

 const errorHandler = (err) => {
      if(err) {
        let errorDetail: string;
        if(err.name === 'UnauthorizedError') errorDetail = `Authorization failed: please try logging in again`;

        return res.status(403).json({
          statusCode: 403,
          timestamp: new Date().toISOString(),
          path: req.url,
          error: errorDetail
        });
      }

      next();
    };

mcblum avatar May 24 '19 13:05 mcblum

Is there a clear direction to take when faced with this issue? I've seen several bugs logged about the lack of documentation surrounding middleware and exception handling, but none of the threads provide a clear "this is how you do it" answer. The page on exception filters contains no reference to middlware, and the page on middlware contains no reference to exceptions or errors. It's just a dead end for those looking for answers.

What I'd like to know, is how do I throw and error in a middleware, and have it handled by NestJS in the same way that happens when you throw it from a controller?

jestinjht avatar Jun 08 '20 18:06 jestinjht

@jestinjht yeah i've been facing the same issue , the only workaround i found was to generate a custom error message and redirect to my front end where i can display the message or do this : throw new HttpException( { statusCode: 403, message: 'USER_DOES_NOT_EXIST', }, 403, ); but this can't be cought by the controller and just makes the application stop working

BlackCat1606 avatar Sep 09 '20 16:09 BlackCat1606

@BlackCat1606 That's more or less what I ended up doing. Since NestJS apparently can't handle errors that are thrown in middleware (I'd love for this to be false, but I have yet to see docs or examples that show otherwise), I ended up just having the middleware return the request with an error code directly rather than passing control onward or throwing an exception. It basically means parallel error handling, where exceptions get handled one way in a controller, but another way in middleware. Not very clean, and not at all reusable.

jestinjht avatar Sep 09 '20 16:09 jestinjht

@jestinjht yeah it's very scuffed , but i won't give up lol , i will keep looking for an answer , and i will notify u if i find something .

BlackCat1606 avatar Sep 09 '20 17:09 BlackCat1606

I'm wondering if I'm missing something here. If I do something like this

import { Module, NestModule, MiddlewareConsumer } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { APP_FILTER } from '@nestjs/core';
import { CatchAllFilter } from './catch-all.filter';

@Module({
  imports: [],
  controllers: [AppController],
  providers: [
    AppService,
    {
      provide: APP_FILTER,
      useClass: CatchAllFilter,
    },
  ],
})
export class AppModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer
      .apply((req, res, next) => {
        throw new Error('Bad Request from Middleware');
      })
      .forRoutes('*');
  }
}

And I have a filter like

import { ArgumentsHost, Catch, ExceptionFilter } from '@nestjs/common';

@Catch()
export class CatchAllFilter<T extends Error> implements ExceptionFilter {
  catch(exception: T, host: ArgumentsHost) {
    console.log(exception);
    const res = host.switchToHttp().getResponse();
    res.status(500).send({ message: exception.message });
  }
}

I'm able to catch the error thrown from the middleware, log it, and do any sort of transformation I need to on it.

jmcdo29 avatar Sep 09 '20 17:09 jmcdo29

I don't think you even need to write the ExceptionFilter.

Example middleware that does jwt claims checking:

import { HttpException, HttpStatus } from '@nestjs/common';
import { NextFunction } from 'express';
import jwtDecode from 'jwt-decode';

export function auth(group: string) {
  return (req: Request, res: Response, next: NextFunction) => {
    const header = req.headers['authorization'];

    const token = header.substring(7, header.length);
    const claims = jwtDecode(token);

    console.log(claims);
    if (claims['groups'].includes(group)) {
      next();
    } else {
      throw new HttpException(
        {
          status: HttpStatus.FORBIDDEN,
          error: 'Insufficient permissions to access this endpoint',
        },
        HttpStatus.FORBIDDEN,
      );
    }
  };
}

Response on error condition:

{
  "status": 403,
  "error": "Insufficient permissions to access this endpoint"
}

geekyme avatar Jan 26 '21 13:01 geekyme

The lack of info regarding Error handling + Middleware drove me crazy for days as well..but it seems the answer is pretty simple. The above examples apply to function middleware. For Class-based middleware, exceptions can be handled gracefully (ie. not shut the app down) by passing them directly into the next() callback. You can either wrap your block with try/catch and use a single next(err); to catch the error or, return next(err); everywhere. NestMiddleware will handle the error and send a response to the client.

use(req: Request, res: Response, next: NextFunction) {
  try {
    if (error condition) {
        throw new HttpException('Not authorized', HttpStatus.UNAUTHORIZED);
    }
    next();
  } catch (e) {
    next(e); // if (e instanceof HttpException)
  }
}

If you wish to log the error or customise/override the response message on it's way out, refer to jmcdo29 's sample code above to @\Catch and provide the APP_FILTER.

futchdev avatar Feb 06 '21 16:02 futchdev