I'm trying to create a global error handler in node js with typescript. I have created some custom classes which extends error class something like this:-
custom class
export abstract class CustomError extends Error {
abstract statusCode: number;
abstract errorMessage: string;
abstract data: unknown;
constructor() {
super();
Object.setPrototypeOf(this, CustomError.prototype);
}
abstract serializeError(): { message: string; fields?: string }[] | string;
}
Bad request error class
import { StatusCodes } from 'http-status-codes';
import { CustomError } from './custom.error';
class BadRequestException extends CustomError {
statusCode = StatusCodes.BAD_REQUEST;
errorMessage: string;
data: any;
constructor(errorMessage?: string) {
super();
// set default value
this.errorMessage = errorMessage || 'Bad Request';
Object.setPrototypeOf(this, BadRequestException.prototype);
}
serializeError() {
return this.errorMessage;
}
}
export default BadRequestException;
server.js
dotenv.config();
const app: Express = express();
app.use('/', routes);
app.use(globalErrorHandler);
export default app;
routes.ts
import express from 'express';
import UserController from '../controller/user/user-controller';
import NotFoundException from '../exception/not-found-exception';
import authorizationMiddleware from '../middleware/authorization-middleware';
const router = express.Router();
// users routes
router.post('/user/create', UserController.createUser);
router.get('/user/profile/:key', UserController.getProfile);
router.get('/user/directus', UserController.getDirectUs);
router.use('*', () => {
throw new NotFoundException();
});
export default router;
controller.ts
import { Request, Response } from 'express';
import { StatusCodes } from 'http-status-codes';
import { directus } from '../../config/directus-confgi';
import BadRequestException from '../../exception/bad-request-exception';
import { USER } from '../../interface/user-interface';
import UserService from './user.service';
class UserController {
// getting user profile
static async getDirectUs(request: Request, response: Response) {
try {
const user = await directus.items('articles').readOne(15);
response.status(StatusCodes.OK).json({ user });
} catch (error: any) {
throw new BadRequestException(error.message);
}
}
}
export default UserController;
Global Error Handler
import { StatusCodes } from 'http-status-codes';
import { Request, Response, NextFunction } from 'express';
import { CustomError } from '../exception/custom.error';
const globalErrorHandler = (err: any, req: Request, res: Response,next:NextFunction) => {
if (err instanceof CustomError) {
const error: { message: string; errors?: any } = {
message: err.errorMessage ? req.body.i18nObj.__(err.errorMessage) : '',
};
if (err.data) error.errors = err.serializeError();
return res.status(err.statusCode).json(error);
}
res.status(StatusCodes.INTERNAL_SERVER_ERROR).json({
message: err.message
? err.message
: req.body.i18nObj.__('MESSAGES.UNABLE_COMPLETE_REQUEST'),
error: { message: err.message, err },
});
};
export default globalErrorHandler;
I'm getting errors in my controller catch block and bad-request error class but after that my global error handler middleware function is not able to get this error, in a result, I'm not able to send this error as a response.
I don't know what I'm doing wrong or how can call my middleware function without throwing an actual error from the controller I want to use my custom error class to formate error and status code.
In Express v4, you need to manually call next() in async controllers in order for error handling middleware to work.
Catching Errors
For errors returned from asynchronous functions invoked by route handlers and middleware, you must pass them to the next() function, where Express will catch and process them.
class UserController {
// getting user profile
static async getDirectUs(
request: Request,
response: Response,
next: NextFunction
) {
try {
const user = await directus.items("articles").readOne(15);
response.status(StatusCodes.OK).json({ user });
} catch (error: any) {
// pass the custom error to `next()`
next(new BadRequestException(error.message));
}
}
}
From Express v5 you won't have to do this
Starting with Express 5, route handlers and middleware that return a Promise will call next(value) automatically when they reject or throw an error
Related
I'm using express with typescript. I want to extend my express request interface for that I have done something like this:-
Middleware.ts
import { NextFunction, Request, Response } from 'express';
// eslint-disable-next-line #typescript-eslint/no-var-requires
const configureI18n = require('../helpers/i18n');
const [_, i18nObj] = configureI18n(false);
export interface CRequest extends Request {
i18nObj: any;
}
const langHeadersMiddleware = (
request: CRequest,
response: Response,
next: NextFunction
): void => {
try {
const language = request.headers['accept-language'];
i18nObj.setLocale(language ? language : 'en');
request.i18nObj = i18nObj;
next();
} catch (error) {
i18nObj.setLocale('en');
}
};
export default langHeadersMiddleware;
route.ts
getUserProfile.get(
'/:id',
async (request: CRequest, response: express.Response) => {
try {
const id = request.params.id;
response.json({
err: 0,
message: request.i18nObj.__('MESSAGES.USER_FETCH'),
data
});
} catch (error: any) {
response.json({ err: 1, message: error.message, error });
}
}
);
In this route I'm getting an error:-
No overload matches this call.
The last overload gave the following error.
Argument of type '(request: CRequest, response: express.Response) => Promise' is not assignable to parameter of type 'Application<Record<string, any>>'.
Type '(request: CRequest, response: Response<any, Record<string, any>>) => Promise' is missing the following properties from type 'Application<Record<string, any>>': init, defaultConfiguration, engine, set, and 61 more.
I went through so many blogs, article but everyone is using the same as I did.
you would have to fork and rework the whole express package, which would definitely not recommend :)
But You can:
add your i18n to the request in the middleware as you're doing and just use it with //#ts-ignore above it
add your i18n to the request body in the middleware and just use it
this is the all-exception.filter.ts:
#Catch()
export class AllExceptionsFilter implements ExceptionFilter {
constructor(private readonly httpAdapterHost: HttpAdapterHost) {}
catch(exception: HttpException, host: ArgumentsHost) {
const { httpAdapter } = this.httpAdapterHost;
const ctx = host.switchToHttp();
const response = ctx.getResponse<Response>();
const request = ctx.getRequest<Request>();
const message = exception.message;
const status =
exception instanceof HttpException
? exception.getStatus()
: HttpStatus.INTERNAL_SERVER_ERROR;
console.log();
const responseBody = {
success: false,
message,
};
httpAdapter.reply(ctx.getResponse(), responseBody, status);
}
}
and this is a service method that returning just one item:
findOne(id: number) {
return this.prisma.restaurant.findUniqueOrThrow({
where: {
id,
},
});
}
The problem is that findUniqueOrThrow will throw 404 if the item is not found. but in the global filter when I log the status, I always receive a 500 status code.
Here a full example for filter handling when Prisma.findFirstOrThrow throw not found exception:
notFound.filter.ts
import { ArgumentsHost, Catch, ExceptionFilter } from '#nestjs/common';
import { NotFoundError } from '#prisma/client/runtime';
import { Response } from 'express';
#Catch(NotFoundError)
export class NotFoundExceptionFilter implements ExceptionFilter {
public catch(exception: NotFoundError, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse<Response>();
return response.status(404).json({ statusCode: 404, error: 'Not Found' });
}
}
main.ts
async function bootstrap() {
const app = await NestFactory.create(AppModule);
...
app.useGlobalFilters(new NotFoundExceptionFilter());
await app.listen(3000);
}
Et voila !
prisma on it's own does not throw an HttpException from Nest, which is where the getStatus() method exists. The error thrown will also fail the exception instanceof HttpException check. You should wrap the call in a try/catch and transform the error to the appropriate exception type so that Nest's filter can handle sending back to proper exception status code
I can't get user from request in decorator nest, pleas help me.
Middleware good working it find user by token and save user in request
my middleware:
import { Injectable, NestMiddleware, HttpStatus } from '#nestjs/common';
import { HttpException } from '#nestjs/common/exceptions/http.exception';
import { Request, Response } from 'express';
import { AuthenticationService } from '../modules/authentication-v1/authentication.service';
#Injectable()
export class AuthenticationMiddleware implements NestMiddleware {
constructor(
private readonly authenticationService : AuthenticationService
) {
}
async use(req: Request, res: Response, next: Function) {
let token = req.headers;
if(!token) {
throw new HttpException('token is required', 401);
}
if (!token.match(/Bearer\s(\S+)/)) {
throw new HttpException('Unsupported token', 401);
}
const [ tokenType, tokenValue ] = token.split(' ');
try {
const result = await this.authenticationService.getAccessToken(tokenValue);
req.user = result;
next();
} catch (e) {
throw new HttpException(e.message, 401);
}
}
}
but here request don't have property user and i don't know why
user decorator:
export const User = createParamDecorator((data: any, req) => {
return req.user; // but here user undefined
});
app module:
export class AppModule {
configure(consumer: MiddlewareConsumer) {
consumer
.apply(AuthenticationMiddleware)
.forRoutes({ path: 'auto-reports-v1', method: RequestMethod.GET });
}
}
route method:
#UseInterceptors(LoggingInterceptor)
#Controller('auto-reports-v1')
#ApiTags('auto-reports-v1')
export class AutoReportsController {
constructor(private readonly autoReportsService: AutoReportsService) {}
#Get()
async findAll(
#Query() filter: any,
#User() user: any): Promise<Paginated> {
return this.autoReportsService.findPaginatedByFilter(filter, user);
}
}
In NestJS with Fastify, middleware attaches values to req.raw. This is because middleware runs before the request gets wrapped by the FastifyRequest object, so all value attachments are made to the IncomingRequest object (same as Express Request object). Fastify will then wrap the IncomingRequest in its own FastifyRequest object and expose the IncomingRequest through req.raw meaning the user you are looking for is at req.raw.user not req.user. If you want to have the same functionality across Express and Fastify, I'd suggest using a Guard instead.
Accepted answer didn't help me, but the "custom decorators" from nest js documentation uses createParamDecorator with ExecutionContext as second parameter, and this worked for me:
import { createParamDecorator, ExecutionContext } from '#nestjs/common';
export const User = createParamDecorator(
(data: unknown, ctx: ExecutionContext) => {
const request = ctx.switchToHttp().getRequest();
return request.user;
},
);
I have an api NestJS where I implemented an error filter to catch all kinds of exceptions. In main.ts I set the api to use the filter globally.
Apparently, it only catches the errors in the controllers, because when I throw an exception in the context of service, the exception is thrown in the console and the api falls.
main.ts:
import { NestFactory } from '#nestjs/core';
import { SwaggerModule, DocumentBuilder } from '#nestjs/swagger';
import {
FastifyAdapter,
NestFastifyApplication,
} from '#nestjs/platform-fastify';
import { AppModule } from './app.module';
import { HttpExceptionFilter } from './exception-filters/http-exception.filter';
import { AllExceptionsFilter } from './exception-filters/exception.filter';
import { ValidationPipe } from '#nestjs/common';
async function bootstrap() {
const app = await NestFactory.create<NestFastifyApplication>(
AppModule,
new FastifyAdapter());
const options = new DocumentBuilder()
.setTitle('API Agendamento.Vip')
// .setDescription('')
.setVersion('1.0')
.build();
const document = SwaggerModule.createDocument(app, options);
SwaggerModule.setup('api', app, document);
// app.useGlobalFilters(new HttpExceptionFilter());
app.useGlobalPipes(new ValidationPipe());
app.useGlobalFilters(new AllExceptionsFilter());
await app.listen(process.env.PORT || 3001);
}
bootstrap();
in a service method these exceptions are thrown
if (err) { throw new InternalServerErrorException('error', err); }
if (!user) { throw new NotFoundException('device info missing'); }
if (!user.active) { throw new HttpException('active error', 400); }
my exception filter:
import { ExceptionFilter, Catch, ArgumentsHost, HttpException, HttpStatus, BadRequestException } from '#nestjs/common';
import { FastifyRequest, FastifyReply } from 'fastify';
#Catch()
export class AllExceptionsFilter implements ExceptionFilter {
catch(exception: unknown, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response: FastifyReply<any> = ctx.getResponse();
const request: FastifyRequest = ctx.getRequest();
const status =
exception instanceof HttpException
? exception.getStatus()
: HttpStatus.INTERNAL_SERVER_ERROR;
const objResponse = Object.assign(exception, {
timestamp: new Date().toISOString(),
path: request.req.url
});
response.status(status).send({
objResponse
});
}
}
Exceptions are thrown on the Node console, causing the api to stop.
They are only captured by the filter if they are in the context of the Controller.
What can I do to get exceptions caught in the service as well?
Having seen your filter we can try two things first adding the HttpException to the #Catch decorator (just for testing I beleive won't make any difference)
#catch(HttpException)
export class AllExceptionFilter implements ExceptionFilter {
/*your code*/
}
Then instead of using app.useGlobalFilters add in the app module the following:
import { Module } from '#nestjs/common';
import { APP_FILTER } from '#nestjs/core';
import { AllExceptionsFilter } from './exception-filters/exception.filter';
#Module({
providers: [
{
provide: APP_FILTER,
useClass: AllExceptionFilter,
},
],
})
export class AppModule {}
This will put the filter in the global scope and will give you the capability to use the global injector, this is the aproach I always use for Globals(Filters, Guards, Pipes) and have worked for me like a charm
The problem was that I was throwing exceptions from within the model method:
async signInApp(dataLogin: DataLoginDto) {
return new Promise((resolve, _reject) => {
this.userModel.findOne({
email: dataLogin.email
}, (err, user) => {
if (err) { throw new InternalServerErrorException('error', err); }
if (!user) { throw new NotFoundException('device info missing'); }
// ...Code omitted
I changed the code by placing exceptions outside the scope of mongoose methods:
async signInApp(dataLogin: DataLoginDto) {
const user = await this.userModel.findOne({
email: dataLogin.email
}).select('password active').exec();
if (!user) { throw new NotFoundException('user not found'); }
if (!user.active) { throw new UnauthorizedException('unable to access your account'); }
// ...Code omitted
Is it possible to make a redirect from a Nest controller without the usage of the #Response object?
For now I know that we can only do this via direct #Response object injection into the route handler.
You can write a RedirectInterceptor:
#Injectable()
export class RedirectInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, stream$: Observable<any>): Observable<any> {
const response = context.switchToHttp().getResponse();
response.redirect('redirect-target');
return stream$;
}
}
Then use it in your controller like this:
#Get('user')
#UseInterceptors(RedirectInterceptor)
getUser() {
// will be redirected.
}
It is important not to return anything from your controller, otherwise you will get the following error: Can't set headers after they are sent.
If needed the RedirectInterceptor can be dynamic as well:
#Injectable()
export class RedirectInterceptor implements NestInterceptor {
constructor(private readonly target: string) {}
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
intercept(context: ExecutionContext, stream$: Observable<any>): Observable<any> {
const response = context.switchToHttp().getResponse();
response.redirect(this.target);
^^^^^^^^^^^
return stream$;
}
}
and then in the controller:
#UseInterceptors(new RedirectInterceptor('redirect-target'))
(a bit of a different implementation to another answer here...)
I created a RedirectError which can be thrown more dynamically than a decorator
import { ExceptionFilter, Catch, ArgumentsHost } from '#nestjs/common';
import { Response } from 'express';
export class RedirectError extends Error {
constructor(public readonly status: number, public readonly url: string) {
super();
}
}
#Catch(RedirectError)
export class RedirectFilter implements ExceptionFilter {
public catch(exception: RedirectError, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse<Response>();
return response.redirect(exception.status, exception.url);
}
}
and then in main.ts set it:
app.useGlobalFilters(new RedirectFilter());
and finally to use it:
throw new RedirectError(302, '/some-target');
I've done it more complex, but I think it is good enough.
Create a class such as util/RedirectException like this:
The code like this:
import { HttpException, HttpStatus } from '#nestjs/common';
export class RedirectException extends HttpException {
constructor(message?: string | object) {
super(message, HttpStatus.CONTINUE);
}
}
Create a RedirectFilter by: nest g f RedirectFilter
Write the code like this:
import { ArgumentsHost, Catch, ExceptionFilter, HttpException, HttpStatus } from '#nestjs/common';
import { RedirectException } from './util/redirect-exception';
#Catch()
export class RedirectFilter implements ExceptionFilter {
catch(exception: any, host: ArgumentsHost) {
const res = host.switchToHttp().getResponse(),
req = host.switchToHttp().getRequest();
try {
if (exception instanceof RedirectException) {
Object.keys(exception.message).forEach(k => {
req.session[k] = exception.message[k];
});
req.session.save(() => {
res.redirect(exception.message.url);
});
return;
}
if (exception instanceof HttpException) {
return res.status(exception.status).json(exception.message)
}
res.status(500).json({status: 500, message: 'Internal Server error'})
} catch (e) {
return res.status(500)
.json({
status: HttpStatus.INTERNAL_SERVER_ERROR,
message: e.message
});
}
}
}
This class help you handle all the response when an exception is throw. And yes, this include the Redirect exception. Now we can throw the exception with exactly params, and it work!
Use the filter in main.ts: app.useGlobalFilters(new RedirectFilter());
And in controller, if you want to redirect to an url, just do this any time you want
Code:
throw new RedirectException({
url: 'the url you want to redirect',
field1: 'The first field you want to pass to session'
field2: 'The second field you want to pass to session'
});
Don't forget setup express-session if you want to pass data by session when redirect: https://www.npmjs.com/package/express-session.
If you don't want to use this, just replace the code inside if (exception instanceof RedirectException) {} to: res.redirect(exception.message.url);. It don't check and setup the session anymore.