Request Lifecycle Optimization
Request Lifecycle Optimization involves understanding the exact order in which NestJS executes its layers (Middleware -> Guards -> Interceptors -> Pipes -> Controllers) and placing logic in the most efficient location to fail fast and save resources.
Overview
A common performance mistake in NestJS is doing heavy work (like a database query to check permissions) after parsing a massive JSON payload, instead of before.
If an unauthorized user uploads a 50MB file to your /video/upload endpoint, you want to reject that request in 1 millisecond. If you check their authorization in the Controller or an Interceptor, NestJS will fully download the 50MB file into memory first, parse it, and then reject it. This is a massive waste of CPU, memory, and bandwidth.
By understanding the Request Lifecycle, you can “fail fast”.
Key Concepts
- The Lifecycle Order:
- Incoming Request
- Middleware
- Guards (Authentication / Authorization)
- Interceptors (Pre-Controller)
- Pipes (Validation / Transformation)
- Controller (Route Handler)
- Service (Business Logic)
- Interceptors (Post-Controller)
- Exception Filters (If an error occurred anywhere)
- Server Response
Code Examples
1. The Wrong Place for Authorization
Do not put authorization logic inside an Interceptor or Controller.
// BAD: Interceptors run AFTER the body is parsed.
@Injectable()
export class AuthInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
const req = context.switchToHttp().getRequest();
// The 50MB body has already been processed! We wasted resources.
if (req.headers.authorization !== 'secret') {
throw new UnauthorizedException();
}
return next.handle();
}
}
2. The Right Place: Guards
Guards execute before Interceptors and Pipes. They are designed for fast, boolean checks to determine if the request should proceed.
// GOOD: Guards run early in the lifecycle.
@Injectable()
export class AuthGuard implements CanActivate {
canActivate(context: ExecutionContext): boolean {
const req = context.switchToHttp().getRequest();
// The request is rejected instantly, before heavy payload parsing!
return req.headers.authorization === 'secret';
}
}
3. Middleware for Ultra-Fast Rejection
Middleware is the very first thing to run. If you have a specific IP address that is maliciously DDoSing you, you want to drop their connection before NestJS even creates the Execution Context or instantiates Guards.
// VERY FAST: Runs at the Express/Fastify layer
import { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';
@Injectable()
export class IpBlockMiddleware implements NestMiddleware {
private blockedIps = new Set(['192.168.1.100']);
use(req: Request, res: Response, next: NextFunction) {
if (this.blockedIps.has(req.ip)) {
// Drop the connection immediately
return res.status(403).send('Forbidden');
}
next();
}
}
Best Practices
- Guards for Auth, Pipes for Data: Never validate
req.bodystructure inside a Guard. Never check user permissions inside a Pipe. Adhere strictly to the intended purpose of each lifecycle layer. - Switch to Fastify: If you have optimized your lifecycle but still need raw HTTP throughput, switch the underlying NestJS engine from Express to Fastify. Fastify processes the HTTP lifecycle significantly faster, often yielding a 20-30% increase in requests per second without changing your business logic.