NestJS: Enterprise-grade Node Framework
NestJS architecture with modules, controllers and providers, dependency injection, DTO validation with class-validator, exception filters, and when to choose it over Express.
Why NestJS
Express is flexible but leaves architecture to you. NestJS (TypeScript-first, heavily inspired by Angular) gives a fixed project layout: modules, controllers, providers, pipes, guards and interceptors — plus a DI container, testing utilities and official integrations (TypeORM, Prisma, GraphQL, microservices, WebSockets).
Modules, Controllers, Providers
// users.controller.ts
@Controller('users')
export class UsersController {
constructor(private readonly users: UsersService) {} // 构造注入
@Get(':id')
findOne(@Param('id') id: string) {
return this.users.findOne(id);
}
@Post()
create(@Body() dto: CreateUserDto) {
return this.users.create(dto);
}
}
// users.module.ts
@Module({
controllers: [UsersController],
providers: [UsersService],
})
export class UsersModule {}
// app.module.ts 汇总
@Module({ imports: [UsersModule, DbModule] })
export class AppModule {}
DTO Validation and Pipes
import { IsEmail, IsInt, Min } from 'class-validator';
export class CreateUserDto {
@IsEmail() email: string;
@Min(2) name: string;
@IsInt() age?: number;
}
// 全局开启 ValidationPipe(main.ts)
app.useGlobalPipes(
new ValidationPipe({
whitelist: true, // 剔除 DTO 上不存在的字段
transform: true, // 自动转类型
}),
);
// 全局异常过滤器:统一错误响应
@Catch()
export class AllExceptionsFilter implements ExceptionFilter {
catch(exception: unknown, host: ArgumentsHost) {
const res = host.switchToHttp().getResponse();
res.status(500).json({ code: 500, message: 'server error' });
}
}
Cross-Cutting Concerns: Guards and Interceptors
@Injectable()
export class JwtAuthGuard implements CanActivate {
constructor(private jwt: JwtService) {}
async canActivate(ctx: ExecutionContext): Promise<boolean> {
const req = ctx.switchToHttp().getRequest();
const token = req.headers.authorization?.replace('Bearer ', '');
try { req.user = await this.jwt.verifyAsync(token); return true; }
catch { return false; }
}
}
// 拦截器:统一响应格式 / 缓存 / 记录耗时
@Injectable()
export class LoggingInterceptor implements NestInterceptor {
intercept(ctx: ExecutionContext, next: CallHandler) {
console.log('before', ctx.getHandler().name);
return next.handle().pipe(tap(() => console.log('after')));
}
}
Choose Express for small APIs and full control; choose NestJS for teams and long-lived projects where convention beats configuration.
为什么用 NestJS
Express 灵活但架构全靠自己。NestJS(TypeScript 优先,深受 Angular 影响)提供固定的项目结构:模块、控制器、提供者、管道、守卫与拦截器,外加依赖注入容器、测试工具与官方集成(TypeORM、Prisma、GraphQL、微服务、WebSocket)。
模块、控制器与提供者
// users.controller.ts
@Controller('users')
export class UsersController {
constructor(private readonly users: UsersService) {} // 构造注入
@Get(':id')
findOne(@Param('id') id: string) {
return this.users.findOne(id);
}
@Post()
create(@Body() dto: CreateUserDto) {
return this.users.create(dto);
}
}
// users.module.ts
@Module({
controllers: [UsersController],
providers: [UsersService],
})
export class UsersModule {}
// app.module.ts 汇总
@Module({ imports: [UsersModule, DbModule] })
export class AppModule {}
DTO 校验与管道
import { IsEmail, IsInt, Min } from 'class-validator';
export class CreateUserDto {
@IsEmail() email: string;
@Min(2) name: string;
@IsInt() age?: number;
}
// 全局开启 ValidationPipe(main.ts)
app.useGlobalPipes(
new ValidationPipe({
whitelist: true, // 剔除 DTO 上不存在的字段
transform: true, // 自动转类型
}),
);
// 全局异常过滤器:统一错误响应
@Catch()
export class AllExceptionsFilter implements ExceptionFilter {
catch(exception: unknown, host: ArgumentsHost) {
const res = host.switchToHttp().getResponse();
res.status(500).json({ code: 500, message: 'server error' });
}
}
横切关注点:守卫与拦截器
@Injectable()
export class JwtAuthGuard implements CanActivate {
constructor(private jwt: JwtService) {}
async canActivate(ctx: ExecutionContext): Promise<boolean> {
const req = ctx.switchToHttp().getRequest();
const token = req.headers.authorization?.replace('Bearer ', '');
try { req.user = await this.jwt.verifyAsync(token); return true; }
catch { return false; }
}
}
// 拦截器:统一响应格式 / 缓存 / 记录耗时
@Injectable()
export class LoggingInterceptor implements NestInterceptor {
intercept(ctx: ExecutionContext, next: CallHandler) {
console.log('before', ctx.getHandler().name);
return next.handle().pipe(tap(() => console.log('after')));
}
}
小型 API 且想完全掌控选 Express;团队协作与长期项目「约定优于配置」选 NestJS。