NestJS中间件 (Middleware)

学习目标

  • 理解中间件在NestJS中的作用和地位
  • 掌握中间件的创建和基本使用方法
  • 学会配置全局中间件和路由中间件
  • 理解内置中间件的使用场景
  • 能够开发自定义中间件实现特定功能

核心知识点

1. 中间件概念

中间件是在请求处理管道中执行的函数,它们可以访问请求对象(req)、响应对象(res)和下一个中间件函数(next)。中间件的主要作用:

  • 执行任何代码
  • 修改请求和响应对象
  • 结束请求-响应周期
  • 调用堆栈中的下一个中间件函数

在NestJS中,中间件本质上是Express中间件,因为NestJS默认使用Express作为底层HTTP服务器。

2. 中间件类型

NestJS支持多种类型的中间件:

  • 函数中间件:简单的函数形式中间件
  • 类中间件:使用@Injectable()装饰器的类形式中间件
  • 内置中间件:NestJS提供的内置中间件(如express.json()
  • 第三方中间件:如helmetcors

3. 创建函数中间件

函数中间件是最简单的中间件形式:

// logger.middleware.ts
export function loggerMiddleware(req, res, next) {
  console.log(`Request received at ${new Date().toISOString()}`);
  console.log(`Method: ${req.method}, URL: ${req.url}`);
  next(); // 调用下一个中间件
}

4. 创建类中间件

类中间件需要使用@Injectable()装饰器,并实现NestMiddleware接口:

// logger.middleware.ts
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 received at ${new Date().toISOString()}`);
    console.log(`Method: ${req.method}, URL: ${req.url}`);
    next(); // 调用下一个中间件
  }
}

5. 应用中间件

中间件需要在模块的configure()方法中注册。要使用configure()方法,模块需要实现NestModule接口:

// app.module.ts
import { Module, NestModule, MiddlewareConsumer } from '@nestjs/common';
import { LoggerMiddleware } from './logger.middleware';
import { CatsController } from './cats/cats.controller';

@Module({
  imports: [],
  controllers: [CatsController],
  providers: [],
})
export class AppModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer
      .apply(LoggerMiddleware) // 应用LoggerMiddleware
      .forRoutes('cats'); // 对cats路由应用中间件
  }
}

6. 路由中间件

可以为特定的路由路径和HTTP方法应用中间件:

// app.module.ts
import { Module, NestModule, MiddlewareConsumer, RequestMethod } from '@nestjs/common';
import { LoggerMiddleware } from './logger.middleware';
import { CatsController } from './cats/cats.controller';

@Module({
  imports: [],
  controllers: [CatsController],
  providers: [],
})
export class AppModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer
      .apply(LoggerMiddleware)
      .forRoutes({
        path: 'cats',
        method: RequestMethod.GET,
      }); // 只对GET /cats路由应用中间件
  }
}

7. 全局中间件

使用use()方法可以应用全局中间件:

// main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { loggerMiddleware } from './logger.middleware';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  
  // 应用全局中间件
  app.use(loggerMiddleware);
  
  await app.listen(3000);
}
bootstrap();

8. 内置中间件

NestJS内置了Express的中间件,如:

  • express.json():解析JSON格式的请求体
  • express.urlencoded():解析URL编码的请求体
  • express.static():提供静态文件服务

使用方式:

// main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import * as express from 'express';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  
  // 使用内置中间件
  app.use(express.json()); // 解析JSON请求体
  app.use(express.urlencoded({ extended: true })); // 解析URL编码的请求体
  app.use(express.static('public')); // 提供public目录下的静态文件
  
  await app.listen(3000);
}
bootstrap();

9. 中间件链

可以同时应用多个中间件,它们会按照注册顺序执行:

// app.module.ts
import { Module, NestModule, MiddlewareConsumer } from '@nestjs/common';
import { LoggerMiddleware } from './logger.middleware';
import { AuthMiddleware } from './auth.middleware';
import { CatsController } from './cats/cats.controller';

@Module({
  imports: [],
  controllers: [CatsController],
  providers: [],
})
export class AppModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer
      .apply(LoggerMiddleware, AuthMiddleware) // 应用多个中间件
      .forRoutes('cats'); // 对cats路由应用中间件链
  }
}

10. 中间件排除

可以使用exclude()方法排除特定路由不应用中间件:

// app.module.ts
import { Module, NestModule, MiddlewareConsumer } from '@nestjs/common';
import { LoggerMiddleware } from './logger.middleware';
import { CatsController } from './cats/cats.controller';

@Module({
  imports: [],
  controllers: [CatsController],
  providers: [],
})
export class AppModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer
      .apply(LoggerMiddleware)
      .exclude('cats/public') // 排除cats/public路由
      .forRoutes('cats'); // 对其他cats路由应用中间件
  }
}

11. 中间件的依赖注入

类中间件可以使用依赖注入,注入其他服务:

// auth.middleware.ts
import { Injectable, NestMiddleware, UnauthorizedException } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';
import { AuthService } from './auth.service';

@Injectable()
export class AuthMiddleware implements NestMiddleware {
  constructor(private authService: AuthService) {}

  use(req: Request, res: Response, next: NextFunction) {
    const token = req.headers.authorization?.split(' ')[1];
    if (!token) {
      throw new UnauthorizedException('No token provided');
    }

    const isValid = this.authService.validateToken(token);
    if (!isValid) {
      throw new UnauthorizedException('Invalid token');
    }

    next();
  }
}

实践案例分析

案例:创建日志中间件

需求分析

我们需要创建一个日志中间件,用于记录所有HTTP请求的详细信息,包括:

  • 请求时间
  • HTTP方法
  • 请求路径
  • 客户端IP
  • 响应状态码
  • 响应时间

实现步骤

  1. 创建日志中间件类
  2. 在模块中注册中间件
  3. 测试中间件的效果

代码实现

1. 创建日志中间件
// logger.middleware.ts
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) {
    const startTime = Date.now();
    const { method, originalUrl, ip } = req;

    console.log(`[${new Date().toISOString()}] ${method} ${originalUrl} from ${ip}`);

    // 监听响应结束事件,记录响应信息
    res.on('finish', () => {
      const endTime = Date.now();
      const responseTime = endTime - startTime;
      const { statusCode } = res;

      console.log(`[${new Date().toISOString()}] ${method} ${originalUrl} ${statusCode} ${responseTime}ms`);
    });

    next();
  }
}
2. 在模块中注册中间件
// app.module.ts
import { Module, NestModule, MiddlewareConsumer } from '@nestjs/common';
import { LoggerMiddleware } from './logger.middleware';
import { CatsController } from './cats/cats.controller';
import { DogsController } from './dogs/dogs.controller';

@Module({
  imports: [],
  controllers: [CatsController, DogsController],
  providers: [],
})
export class AppModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer
      .apply(LoggerMiddleware)
      .forRoutes('*'); // 对所有路由应用日志中间件
  }
}
3. 测试中间件

创建简单的控制器进行测试:

// cats.controller.ts
import { Controller, Get, Post, Body } from '@nestjs/common';

@Controller('cats')
export class CatsController {
  @Get()
  findAll() {
    return ['cat1', 'cat2', 'cat3'];
  }

  @Post()
  create(@Body() cat) {
    return { ...cat, id: 1 };
  }
}
// dogs.controller.ts
import { Controller, Get } from '@nestjs/common';

@Controller('dogs')
export class DogsController {
  @Get()
  findAll() {
    return ['dog1', 'dog2', 'dog3'];
  }
}

测试结果

当我们发送HTTP请求时,日志中间件会记录请求和响应信息:

[2023-10-01T10:00:00.000Z] GET /cats from ::1
[2023-10-01T10:00:00.005Z] GET /cats 200 5ms

[2023-10-01T10:00:05.000Z] POST /cats from ::1
[2023-10-01T10:00:05.008Z] POST /cats 201 8ms

[2023-10-01T10:00:10.000Z] GET /dogs from ::1
[2023-10-01T10:00:10.003Z] GET /dogs 200 3ms

代码解析

  1. 中间件创建

    • 创建了一个实现NestMiddleware接口的类中间件
    • use方法中记录请求开始时间和请求信息
    • 监听响应的finish事件,记录响应状态码和响应时间
  2. 中间件注册

    • AppModule中实现NestModule接口
    • configure方法中使用MiddlewareConsumer注册中间件
    • 使用forRoutes('*')对所有路由应用中间件
  3. 测试验证

    • 创建了两个简单的控制器用于测试
    • 发送HTTP请求后,中间件会记录详细的请求和响应信息

互动思考问题

  1. 思考:中间件、守卫(Guards)和拦截器(Interceptors)的区别是什么?它们各自的使用场景是什么?

  2. 讨论:在什么情况下应该使用全局中间件,什么情况下应该使用路由中间件?

  3. 实践:尝试创建一个认证中间件,用于验证请求中的JWT令牌,并将验证通过的用户信息添加到请求对象中。

  4. 挑战:如何创建一个CORS中间件,用于处理跨域资源共享?

  5. 扩展:了解NestJS的异常过滤器(Exception Filters),思考它与中间件的区别和配合使用的场景。

小结

本集我们学习了NestJS中间件的核心概念和使用方法,包括:

  • 中间件的基本概念和作用
  • 函数中间件和类中间件的创建方法
  • 全局中间件和路由中间件的配置方式
  • 内置中间件的使用场景
  • 中间件链的创建和使用
  • 中间件排除的配置方法
  • 中间件的依赖注入

通过实践案例,我们创建了一个完整的日志中间件,展示了如何使用中间件记录HTTP请求和响应的详细信息。中间件是NestJS处理HTTP请求的重要组件,用于实现日志记录、认证、CORS等横切关注点。

在下一集中,我们将学习NestJS的异常过滤器(Exception Filters),了解如何统一处理应用中的异常,提供一致的错误响应格式。

« 上一篇 NestJS模块 (Modules) 下一篇 » NestJS异常过滤器 (Exception Filters)