如何在NestJS中实现响应数据自动过滤?
嘿,我刚从Fastify转去学NestJS的时候也碰到过这个困惑!Fastify靠JSON Schema自动过滤响应的体验确实很丝滑,NestJS虽然没有完全一样的原生功能,但有几种方案能实现类似的效果,而且适配Nest的生态:
1. 用ClassSerializerInterceptor(最推荐)
这是Nest官方主推的方式,结合class-transformer库的装饰器就能轻松控制哪些字段返回给客户端,完全不用手动构造响应对象。
举个例子,先定义你的用户实体类,用@Exclude标记要隐藏的字段:
import { Exclude } from 'class-transformer'; export class User { id: number; username: string; email: string; // 标记这个字段不返回给客户端 @Exclude() password: string; constructor(partial: Partial<User>) { Object.assign(this, partial); } }
然后在控制器里启用ClassSerializerInterceptor,可以单路由启用,也可以全局启用:
import { Controller, Post, Body, UseInterceptors } from '@nestjs/common'; import { ClassSerializerInterceptor } from '@nestjs/common/serializer'; import { UsersService } from './users.service'; import { CreateUserDto } from './dto/create-user.dto'; import { User } from './entities/user.entity'; @Controller('users') export class UsersController { constructor(private readonly usersService: UsersService) {} @Post() @UseInterceptors(ClassSerializerInterceptor) // 单路由启用 async create(@Body() createUserDto: CreateUserDto): Promise<User> { // 直接返回完整的User实例,拦截器会自动过滤掉@Exclude标记的字段 return this.usersService.create(createUserDto); } }
如果想全局启用,在根模块里配置:
import { Module } from '@nestjs/common'; import { APP_INTERCEPTOR } from '@nestjs/core'; import { ClassSerializerInterceptor } from '@nestjs/common/serializer'; @Module({ providers: [ { provide: APP_INTERCEPTOR, useClass: ClassSerializerInterceptor, }, ], }) export class AppModule {}
这种方式不仅能过滤字段,还能做字段重命名、类型转换等,非常灵活。
2. 结合Fastify适配器利用原生Schema过滤
如果你在Nest里用的是Fastify适配器(毕竟你之前熟悉Fastify),其实可以直接利用Fastify的响应Schema验证和过滤功能。
你可以在Fastify实例上为路由配置响应Schema,这样Fastify会自动按照Schema过滤返回的数据:
import { NestFactory } from '@nestjs/core'; import { NestFastifyApplication, FastifyAdapter } from '@nestjs/platform-fastify'; import { AppModule } from './app.module'; async function bootstrap() { const app = await NestFactory.create<NestFastifyApplication>( AppModule, new FastifyAdapter(), ); // 获取Fastify实例,配置路由的响应Schema const fastifyInstance = app.getHttpAdapter().getInstance(); fastifyInstance.route({ method: 'POST', url: '/users', schema: { response: { 201: { type: 'object', properties: { id: { type: 'number' }, username: { type: 'string' }, email: { type: 'string' }, }, required: ['id', 'username'], }, }, }, handler: async (request, reply) => { // 这里可以调用Nest的服务逻辑,或者直接复用控制器的方法 const userService = app.get(UsersService); const user = await userService.create(request.body); reply.send(user); }, }); await app.listen(3000); } bootstrap();
不过这种方式需要把路由逻辑直接写在Fastify配置里,和Nest的控制器模式有点割裂,适合需要完全复用Fastify习惯的场景。
3. 自定义响应过滤拦截器
如果上面两种方式都不满足你的需求,还可以自己写一个拦截器,根据自定义的JSON Schema来过滤响应数据。比如:
import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common'; import { Observable } from 'rxjs'; import { map } from 'rxjs/operators'; import { validate } from 'class-validator'; import { plainToInstance } from 'class-transformer'; // 定义一个装饰器,用来标记路由的响应Schema export const ResponseSchema = (schema: any) => { return (target: any, key: string, descriptor: PropertyDescriptor) => { Reflect.defineMetadata('responseSchema', schema, descriptor.value); }; }; @Injectable() export class ResponseFilterInterceptor implements NestInterceptor { intercept(context: ExecutionContext, next: CallHandler): Observable<any> { const handler = context.getHandler(); const schema = Reflect.getMetadata('responseSchema', handler); if (!schema) { return next.handle(); } return next.handle().pipe( map(async (data) => { // 根据Schema验证并转换数据 const instance = plainToInstance(schema, data); const errors = await validate(instance); if (errors.length > 0) { throw new Error('Response validation failed'); } return instance; }), ); } }
然后在控制器里使用:
import { ResponseSchema } from './response-filter.interceptor'; import { UserResponseDto } from './dto/user-response.dto'; // 这个DTO就是你的响应Schema类 @Post() @ResponseSchema(UserResponseDto) async create(@Body() createUserDto: CreateUserDto) { return this.usersService.create(createUserDto); }
这种方式更接近Fastify的Schema驱动模式,你可以完全控制过滤逻辑。
总的来说,NestJS虽然没有Fastify那样开箱即用的JSON Schema响应过滤,但通过上面几种方案,完全能实现相同的效果,其中ClassSerializerInterceptor是最贴合Nest生态、最省心的选择。
内容的提问来源于stack exchange,提问作者Bennison J

