nestjs-patterns

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

NestJS Development Patterns

NestJS开发模式

Production-grade NestJS patterns for modular TypeScript backends.
用于构建模块化TypeScript后端的生产级NestJS模式。

When to Activate

何时启用

  • Building NestJS APIs or services
  • Structuring modules, controllers, and providers
  • Adding DTO validation, guards, interceptors, or exception filters
  • Configuring environment-aware settings and database integrations
  • Testing NestJS units or HTTP endpoints
  • 构建NestJS API或服务时
  • 搭建模块、控制器和提供者的结构时
  • 添加DTO验证、守卫、拦截器或异常过滤器时
  • 配置环境感知设置和数据库集成时
  • 测试NestJS单元或HTTP端点时

Project Structure

项目结构

text
src/
├── app.module.ts
├── main.ts
├── common/
│   ├── filters/
│   ├── guards/
│   ├── interceptors/
│   └── pipes/
├── config/
│   ├── configuration.ts
│   └── validation.ts
├── modules/
│   ├── auth/
│   │   ├── auth.controller.ts
│   │   ├── auth.module.ts
│   │   ├── auth.service.ts
│   │   ├── dto/
│   │   ├── guards/
│   │   └── strategies/
│   └── users/
│       ├── dto/
│       ├── entities/
│       ├── users.controller.ts
│       ├── users.module.ts
│       └── users.service.ts
└── prisma/ or database/
  • Keep domain code inside feature modules.
  • Put cross-cutting filters, decorators, guards, and interceptors in
    common/
    .
  • Keep DTOs close to the module that owns them.
text
src/
├── app.module.ts
├── main.ts
├── common/
│   ├── filters/
│   ├── guards/
│   ├── interceptors/
│   └── pipes/
├── config/
│   ├── configuration.ts
│   └── validation.ts
├── modules/
│   ├── auth/
│   │   ├── auth.controller.ts
│   │   ├── auth.module.ts
│   │   ├── auth.service.ts
│   │   ├── dto/
│   │   ├── guards/
│   │   └── strategies/
│   └── users/
│       ├── dto/
│       ├── entities/
│       ├── users.controller.ts
│       ├── users.module.ts
│       └── users.service.ts
└── prisma/ or database/
  • 把领域代码放在功能模块内。
  • 将通用的过滤器、装饰器、守卫和拦截器放在
    common/
    目录下。
  • 让DTO与所属模块尽量放在一起。

Bootstrap and Global Validation

启动引导与全局验证

ts
async function bootstrap() {
  const app = await NestFactory.create(AppModule, { bufferLogs: true });

  app.useGlobalPipes(
    new ValidationPipe({
      whitelist: true,
      forbidNonWhitelisted: true,
      transform: true,
      transformOptions: { enableImplicitConversion: true },
    }),
  );

  app.useGlobalInterceptors(new ClassSerializerInterceptor(app.get(Reflector)));
  app.useGlobalFilters(new HttpExceptionFilter());

  await app.listen(process.env.PORT ?? 3000);
}
bootstrap();
  • Always enable
    whitelist
    and
    forbidNonWhitelisted
    on public APIs.
  • Prefer one global validation pipe instead of repeating validation config per route.
ts
async function bootstrap() {
  const app = await NestFactory.create(AppModule, { bufferLogs: true });

  app.useGlobalPipes(
    new ValidationPipe({
      whitelist: true,
      forbidNonWhitelisted: true,
      transform: true,
      transformOptions: { enableImplicitConversion: true },
    }),
  );

  app.useGlobalInterceptors(new ClassSerializerInterceptor(app.get(Reflector)));
  app.useGlobalFilters(new HttpExceptionFilter());

  await app.listen(process.env.PORT ?? 3000);
}
bootstrap();
  • 公共API始终开启
    whitelist
    forbidNonWhitelisted
    配置。
  • 优先使用全局校验管道,避免为每个路由重复配置校验规则。

Modules, Controllers, and Providers

模块、控制器与提供者

ts
@Module({
  controllers: [UsersController],
  providers: [UsersService],
  exports: [UsersService],
})
export class UsersModule {}

@Controller('users')
export class UsersController {
  constructor(private readonly usersService: UsersService) {}

  @Get(':id')
  getById(@Param('id', ParseUUIDPipe) id: string) {
    return this.usersService.getById(id);
  }

  @Post()
  create(@Body() dto: CreateUserDto) {
    return this.usersService.create(dto);
  }
}

@Injectable()
export class UsersService {
  constructor(private readonly usersRepo: UsersRepository) {}

  async create(dto: CreateUserDto) {
    return this.usersRepo.create(dto);
  }
}
  • Controllers should stay thin: parse HTTP input, call a provider, return response DTOs.
  • Put business logic in injectable services, not controllers.
  • Export only the providers other modules genuinely need.
ts
@Module({
  controllers: [UsersController],
  providers: [UsersService],
  exports: [UsersService],
})
export class UsersModule {}

@Controller('users')
export class UsersController {
  constructor(private readonly usersService: UsersService) {}

  @Get(':id')
  getById(@Param('id', ParseUUIDPipe) id: string) {
    return this.usersService.getById(id);
  }

  @Post()
  create(@Body() dto: CreateUserDto) {
    return this.usersService.create(dto);
  }
}

@Injectable()
export class UsersService {
  constructor(private readonly usersRepo: UsersRepository) {}

  async create(dto: CreateUserDto) {
    return this.usersRepo.create(dto);
  }
}
  • 控制器应保持轻量:仅负责解析HTTP输入、调用提供者、返回响应DTO。
  • 将业务逻辑放在可注入的服务中,而非控制器里。
  • 仅导出其他模块确实需要的提供者。

DTOs and Validation

DTO与验证

ts
export class CreateUserDto {
  @IsEmail()
  email!: string;

  @IsString()
  @Length(2, 80)
  name!: string;

  @IsOptional()
  @IsEnum(UserRole)
  role?: UserRole;
}
  • Validate every request DTO with
    class-validator
    .
  • Use dedicated response DTOs or serializers instead of returning ORM entities directly.
  • Avoid leaking internal fields such as password hashes, tokens, or audit columns.
ts
export class CreateUserDto {
  @IsEmail()
  email!: string;

  @IsString()
  @Length(2, 80)
  name!: string;

  @IsOptional()
  @IsEnum(UserRole)
  role?: UserRole;
}
  • class-validator
    验证每个请求DTO。
  • 使用专门的响应DTO或序列化器,不要直接返回ORM实体。
  • 避免泄露密码哈希、令牌或审计列这类内部字段。

Auth, Guards, and Request Context

鉴权、守卫与请求上下文

ts
@UseGuards(JwtAuthGuard, RolesGuard)
@Roles('admin')
@Get('admin/report')
getAdminReport(@Req() req: AuthenticatedRequest) {
  return this.reportService.getForUser(req.user.id);
}
  • Keep auth strategies and guards module-local unless they are truly shared.
  • Encode coarse access rules in guards, then do resource-specific authorization in services.
  • Prefer explicit request types for authenticated request objects.
ts
@UseGuards(JwtAuthGuard, RolesGuard)
@Roles('admin')
@Get('admin/report')
getAdminReport(@Req() req: AuthenticatedRequest) {
  return this.reportService.getForUser(req.user.id);
}
  • 鉴权策略和守卫除非确实需要共享,否则放在模块内部。
  • 在守卫中定义粗粒度的访问规则,在服务中实现资源专属的授权逻辑。
  • 优先为已认证的请求对象使用显式的请求类型。

Exception Filters and Error Shape

异常过滤器与错误格式

ts
@Catch()
export class HttpExceptionFilter implements ExceptionFilter {
  catch(exception: unknown, host: ArgumentsHost) {
    const response = host.switchToHttp().getResponse<Response>();
    const request = host.switchToHttp().getRequest<Request>();

    if (exception instanceof HttpException) {
      return response.status(exception.getStatus()).json({
        path: request.url,
        error: exception.getResponse(),
      });
    }

    return response.status(500).json({
      path: request.url,
      error: 'Internal server error',
    });
  }
}
  • Keep one consistent error envelope across the API.
  • Throw framework exceptions for expected client errors; log and wrap unexpected failures centrally.
ts
@Catch()
export class HttpExceptionFilter implements ExceptionFilter {
  catch(exception: unknown, host: ArgumentsHost) {
    const response = host.switchToHttp().getResponse<Response>();
    const request = host.switchToHttp().getRequest<Request>();

    if (exception instanceof HttpException) {
      return response.status(exception.getStatus()).json({
        path: request.url,
        error: exception.getResponse(),
      });
    }

    return response.status(500).json({
      path: request.url,
      error: 'Internal server error',
    });
  }
}
  • 整个API保持统一的错误返回结构。
  • 预期的客户端错误抛出框架异常,非预期的失败统一在中心层记录日志并包装。

Config and Environment Validation

配置与环境变量验证

ts
ConfigModule.forRoot({
  isGlobal: true,
  load: [configuration],
  validate: validateEnv,
});
  • Validate env at boot, not lazily at first request.
  • Keep config access behind typed helpers or config services.
  • Split dev/staging/prod concerns in config factories instead of branching throughout feature code.
ts
ConfigModule.forRoot({
  isGlobal: true,
  load: [configuration],
  validate: validateEnv,
});
  • 在服务启动时校验环境变量,不要等到首次请求时才懒加载校验。
  • 通过带类型的辅助方法或配置服务来访问配置。
  • 在配置工厂中拆分开发/预发/生产环境的逻辑,不要在功能代码中到处做分支判断。

Persistence and Transactions

持久化与事务

  • Keep repository / ORM code behind providers that speak domain language.
  • For Prisma or TypeORM, isolate transactional workflows in services that own the unit of work.
  • Do not let controllers coordinate multi-step writes directly.
  • 把仓库/ORM代码封装在使用领域语言的提供者之后。
  • 对于Prisma或TypeORM,将事务流程隔离在负责工作单元的服务中。
  • 不要让控制器直接协调多步写入操作。

Testing

测试

ts
describe('UsersController', () => {
  let app: INestApplication;

  beforeAll(async () => {
    const moduleRef = await Test.createTestingModule({
      imports: [UsersModule],
    }).compile();

    app = moduleRef.createNestApplication();
    app.useGlobalPipes(new ValidationPipe({ whitelist: true, transform: true }));
    await app.init();
  });
});
  • Unit test providers in isolation with mocked dependencies.
  • Add request-level tests for guards, validation pipes, and exception filters.
  • Reuse the same global pipes/filters in tests that you use in production.
ts
describe('UsersController', () => {
  let app: INestApplication;

  beforeAll(async () => {
    const moduleRef = await Test.createTestingModule({
      imports: [UsersModule],
    }).compile();

    app = moduleRef.createNestApplication();
    app.useGlobalPipes(new ValidationPipe({ whitelist: true, transform: true }));
    await app.init();
  });
});
  • 单元测试时通过Mock依赖单独测试提供者。
  • 为守卫、校验管道和异常过滤器添加请求级别的测试。
  • 在测试中复用生产环境使用的全局管道/过滤器。

Production Defaults

生产环境默认配置

  • Enable structured logging and request correlation ids.
  • Terminate on invalid env/config instead of booting partially.
  • Prefer async provider initialization for DB/cache clients with explicit health checks.
  • Keep background jobs and event consumers in their own modules, not inside HTTP controllers.
  • Make rate limiting, auth, and audit logging explicit for public endpoints.
  • 开启结构化日志和请求关联ID。
  • 遇到无效的环境变量/配置时直接终止服务,不要部分启动。
  • 数据库/缓存客户端优先使用异步提供者初始化,并附带明确的健康检查。
  • 后台任务和事件消费者放在独立模块中,不要放在HTTP控制器里。
  • 公共端点要明确配置限流、鉴权和审计日志。