乐闻世界logo
搜索文章和话题

What is the difference between NestJS middleware and guards?

2月17日 22:34

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

typescript
import { 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

typescript
import { 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

typescript
configure(consumer: MiddlewareConsumer) { consumer .apply(LoggerMiddleware) .exclude('cats', { path: 'cats', method: RequestMethod.GET }) .forRoutes(CatsController); }

Apply Multiple Middleware

typescript
configure(consumer: MiddlewareConsumer) { consumer .apply(LoggerMiddleware, AuthMiddleware) .forRoutes(CatsController); }

Functional Middleware

typescript
export 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

typescript
import { 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

typescript
import { 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

typescript
import { 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

typescript
import { 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

typescript
import { 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

typescript
import { 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

  1. Separation of Concerns: Middleware for global logic, guards for permission control
  2. Use Decorators: Create custom decorators to simplify guard usage
  3. Error Handling: Throw appropriate exceptions in guards
  4. Performance Considerations: Avoid time-consuming operations in guards
  5. Test Coverage: Write tests for middleware and guards
  6. Documentation: Add clear documentation for middleware and guards
  7. 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.

标签:NestJS