Middleware vs Guards vs Interceptors in NestJS: Lifecycle, Auth, and E-Commerce Examples
When building NestJS applications, senior developers know that where you put your logic is just as important as what logic you write. NestJS offers multiple layers to handle requests:
Middleware
Guards
Interceptors
Each layer has a unique execution point, making it ideal for specific tasks like authentication, authorization, caching, or logging. Misplacing your logic can lead to security holes, performance issues, or messy code.
NestJS Request Lifecycle
When a request arrives, NestJS executes layers in this order:
Controller / Route Handler – actual business logic
Interceptor (after handler) – post-processing, like caching
Exception Filter – catches errors
Understanding this order is crucial to placing authentication, authorization, and caching logic correctly.
What Each Layer Does
Middleware
Middleware runs before NestJS selects a route. It has access to raw requests and responses.
Use middleware for:
Authenticating users with JWT tokens
Attaching user information to req object
Logging requests for analytics
Blocking unauthorized access immediately
Key Advantage: Stops requests early without hitting controllers or services.
Guards
Guards run after route selection but before controller execution. They decide if a user can perform an action.
Use guards for:
Role-based access control (RBAC)
Permission checks (ABAC)
Protecting admin-only routes
Conditional access based on business rules
Key Advantage: Knows the context of the route and can block execution precisely.
Interceptors
Interceptors wrap around the route execution. They can run before and after the controller.
Use interceptors for:
Caching responses
Logging execution time
Transforming or shaping the response
Handling global concerns without touching business logic
Important: Interceptors are not ideal for authentication because they run after guards and route resolution. This makes them slower and less secure for blocking unauthorized requests.
Why Middleware is Better for Authentication than Interceptors
Executes first → stops bad requests before they reach controllers
Prevents unnecessary execution of guards, pipes, and interceptors for unauthorized users
Provides a centralized place for all auth logic
Keeps performance high under heavy traffic
Example Misuse: If you authenticate in an interceptor, the request may already pass through guards or hit caching logic, causing performance or security problems.
E-Commerce Example: Auth with Middleware + Caching with Interceptors
Imagine a shopping backend:
Middleware validates JWT tokens and attaches user info to requests
Guards check if the user is allowed to update products
Interceptors cache product listings for faster response
Middleware Example
@Injectable()
export class AuthMiddleware implements NestMiddleware {
use(req: Request, res: Response, next: NextFunction) {
const token = req.headers['authorization'];
if (!token || token !== 'Bearer valid-shop-token') {
throw new UnauthorizedException('You are not allowed');
}
req['user'] = { id: '9001', name: 'Hamza', role: 'customer' };
next();
}
}
This middleware blocks unauthorized requests immediately and attaches user info for downstream logic.
Guard Example
@Injectable()
export class RolesGuard implements CanActivate {
canActivate(context: ExecutionContext): boolean {
const req = context.switchToHttp().getRequest();
return req.user?.role === 'admin'; // Only admin can update products
}
}
Interceptor Example
@Injectable()
export class CacheInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler) {
console.log('Caching product listing...');
return next.handle().pipe(
tap(response => {
// Here, you could save the response in Redis or memory cache
}),
);
}
}
Real-World E-Commerce Scenarios
Middleware → Verify user token before accessing cart or checkout
Guard → Only admin can add or update products
Interceptor → Cache product listing, homepage banners, or popular categories
This combination ensures security, performance, and maintainability.
Senior Developer Takeaways
Middleware = Identity/Auth → stop requests early
Guard = Authorization/Permissions → control access per route
Interceptor = Caching/Logging/Transforming → wrap business logic, don’t block requests
Never use interceptors for authentication
Middleware is fast, central, and efficient for global auth
Conclusion
Understanding the NestJS request lifecycle and choosing the right layer for your logic is a key skill for senior developers. In an e-commerce app:
Middleware secures your endpoints
Guards enforce business rules
Interceptors optimize response speed
Together, these layers make your application secure, scalable, and high-performing.
GraphQL has changed how modern APIs are built. Instead of fixed REST endpoints, GraphQL allows clients to ask for exactly the data they need — no more and no less. When combined with NestJS and MongoDB, GraphQL becomes extremely powerful, scalable, and production-ready.
Optimizing the performance of a NestJS application is critical for building scalable, fast, and production-ready APIs. Even though NestJS is a high-performance framework, improper coding practices, unoptimized database queries, and lack of caching can slow down your application.
NestJS interceptors are one of the most powerful tools in the framework, enabling developers to transform responses, cache results, log performance, and optimize requests. For large-scale applications, building high-performance interceptors is essential to improve speed, maintainability, and scalability.