Middleware Concept
Middleware is a function called before the route handler. It can access the request and response objects, as well as the next() middleware function in the application's request-response cycle. Middleware is mainly used for:
- Executing any code
- Modifying request and response objects
- Ending the request-response cycle
- Calling the next middleware function in the stack
Basic Middleware Structure
typescriptimport { Injectable, NestMiddleware } from '@nestjs/common'; import { Request, Response, NextFunction } from 'express'; @Injectable() export class LoggerMiddleware implements NestMiddleware { use(req: Request, res: Response, next: NextFunction) { console.log(`Request... ${req.method} ${req.url}`); next(); } }
Applying Middleware
Apply Middleware in Module
typescriptimport { Module, MiddlewareConsumer, NestModule } from '@nestjs/common'; import { LoggerMiddleware } from './logger.middleware'; @Module({ imports: [], controllers: [], providers: [], }) export class AppModule implements NestModule { configure(consumer: MiddlewareConsumer) { consumer .apply(LoggerMiddleware) .forRoutes('cats'); } }
Limit Middleware to Specific Routes
typescriptconfigure(consumer: MiddlewareConsumer) { consumer .apply(LoggerMiddleware) .exclude('cats', { path: 'cats', method: RequestMethod.GET }) .forRoutes(CatsController); }
Apply Multiple Middleware
typescriptconfigure(consumer: MiddlewareConsumer) { consumer .apply(LoggerMiddleware, AuthMiddleware) .forRoutes(CatsController); }
Functional Middleware
typescriptexport function logger(req: Request, res: Response, next: NextFunction) { console.log(`Request...`); next(); } // Apply functional middleware configure(consumer: MiddlewareConsumer) { consumer .apply(logger) .forRoutes(CatsController); }
Global Middleware
typescriptimport { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; import { logger } from './common/middleware/logger.middleware'; async function bootstrap() { const app = await NestFactory.create(AppModule); app.use(logger); await app.listen(3000); } bootstrap();
Guards Concept
Guards are classes decorated with @Injectable() that implement the CanActivate interface. Guards are responsible for determining whether a request should be handled by a route handler. They are mainly used for:
- Authentication
- Authorization
- Permission checking
Basic Guard Structure
typescriptimport { Injectable, CanActivate, ExecutionContext } from '@nestjs/common'; @Injectable() export class AuthGuard implements CanActivate { canActivate(context: ExecutionContext): boolean { const request = context.switchToHttp().getRequest(); return this.validateRequest(request); } private validateRequest(request: any): boolean { // Validation logic return true; } }
Using Guards
Use Guards on Controllers
typescript@Controller('cats') @UseGuards(AuthGuard) export class CatsController { @Get() findAll() { return 'This action returns all cats'; } }
Use Guards on Specific Routes
typescript@Controller('cats') export class CatsController { @Get() @UseGuards(AuthGuard) findAll() { return 'This action returns all cats'; } }
Global Guards
typescriptimport { Module } from '@nestjs/common'; import { APP_GUARD } from '@nestjs/core'; import { AuthGuard } from './auth.guard'; @Module({ providers: [ { provide: APP_GUARD, useClass: AuthGuard, }, ], }) export class AppModule {}
Role Guard Example
typescriptimport { Injectable, CanActivate, ExecutionContext } from '@nestjs/common'; import { Reflector } from '@nestjs/core'; @Injectable() export class RolesGuard implements CanActivate { constructor(private reflector: Reflector) {} canActivate(context: ExecutionContext): boolean { const requiredRoles = this.reflector.getAllAndOverride<string[]>('roles', [ context.getHandler(), context.getClass(), ]); if (!requiredRoles) { return true; } const { user } = context.switchToHttp().getRequest(); return requiredRoles.some((role) => user.roles?.includes(role)); } } // Create role decorator import { SetMetadata } from '@nestjs/common'; export const Roles = (...roles: string[]) => SetMetadata('roles', roles); // Use role guard @Controller('cats') @UseGuards(RolesGuard) export class CatsController { @Get() @Roles('admin') findAll() { return 'This action returns all cats'; } }
Middleware vs Guards
Middleware Characteristics
- Executes in Express middleware chain
- Can access request and response objects
- Executes before route handler
- Suitable for global logic (logging, CORS, etc.)
- Unaware of route handler
Guard Characteristics
- Executes in NestJS dependency injection system
- Can access ExecutionContext
- Executes after middleware, before interceptors
- Suitable for permission and authorization logic
- Aware of route handler and class
Selection Guide
- Use middleware: Logging, request parsing, CORS, compression, etc.
- Use guards: Authentication, authorization, permission checking, etc.
Custom Decorators
Extract User from Request
typescriptimport { createParamDecorator, ExecutionContext } from '@nestjs/common'; export const User = createParamDecorator( (data: string | undefined, ctx: ExecutionContext) => { const request = ctx.switchToHttp().getRequest(); const user = request.user; return data ? user?.[data] : user; }, ); // Use custom decorator @Get() findOne(@User('id') userId: string) { return this.catsService.findOne(userId); }
JWT Authentication Guard Example
typescriptimport { Injectable, CanActivate, ExecutionContext, UnauthorizedException } from '@nestjs/common'; import { JwtService } from '@nestjs/jwt'; @Injectable() export class JwtAuthGuard implements CanActivate { constructor(private jwtService: JwtService) {} async canActivate(context: ExecutionContext): Promise<boolean> { const request = context.switchToHttp().getRequest(); const token = this.extractTokenFromHeader(request); if (!token) { throw new UnauthorizedException(); } try { const payload = await this.jwtService.verifyAsync(token); request['user'] = payload; } catch { throw new UnauthorizedException(); } return true; } private extractTokenFromHeader(request: Request): string | undefined { const [type, token] = request.headers.authorization?.split(' ') ?? []; return type === 'Bearer' ? token : undefined; } }
Best Practices
- Separation of Concerns: Middleware for global logic, guards for permission control
- Use Decorators: Create custom decorators to simplify guard usage
- Error Handling: Throw appropriate exceptions in guards
- Performance Considerations: Avoid time-consuming operations in guards
- Test Coverage: Write tests for middleware and guards
- Documentation: Add clear documentation for middleware and guards
- Avoid Overuse: Only use middleware and guards when necessary
Summary
The NestJS middleware and guards system provides:
- Flexible request handling mechanism
- Powerful permission control capabilities
- Clear separation of concerns
- Easy to test and maintain code structure
Mastering middleware and guards is key to building secure, maintainable NestJS applications. Middleware handles global logic, guards handle permission control, and together they form the security and functional foundation of the application.