外观
NestJS 笔记
约 2169 字大约 7 分钟
2025-08-22
一、NestJS 基础
1.1 什么是 NestJS?
NestJS 是一个用于构建高效、可扩展的 Node.js 服务器端应用程序的框架。它使用渐进式 JavaScript,内置 TypeScript 支持(但也可使用纯 JavaScript),并结合了 OOP(面向对象编程)、FP(函数式编程)和 FRP(函数响应式编程)的元素。
NestJS 的核心优势:
- 模块化架构:清晰的代码组织结构
- 依赖注入系统:简化组件之间的依赖关系
- 强大的 CLI 工具:提高开发效率
- 丰富的生态系统:大量官方和社区模块
- TypeScript 优先:提供更好的类型安全和开发体验
- 多传输层支持:HTTP、WebSockets、gRPC 等
1.2 安装与项目创建
安装 Nest CLI
npm install -g @nestjs/cli创建新项目
nest new my-project
# 选择包管理器(npm/yarn/pnpm)
# 项目创建完成后,进入目录
cd my-project
npm run start项目结构概览
my-project/
├── src/
│ ├── app.controller.ts # 基本控制器
│ ├── app.controller.spec.ts # 控制器测试
│ ├── app.module.ts # 根模块
│ ├── app.service.ts # 基本服务
│ └── main.ts # 应用入口
├── test/ # 端到端测试
├── .env # 环境变量
├── .eslintrc.js # ESLint 配置
├── nest-cli.json # Nest CLI 配置
├── package.json
└── tsconfig.json # TypeScript 配置1.3 应用入口 (main.ts)
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
// 全局前缀
app.setGlobalPrefix('api');
// CORS 配置
app.enableCors({
origin: true,
methods: 'GET,HEAD,PUT,PATCH,POST,DELETE',
credentials: true,
});
await app.listen(3000);
}
bootstrap();二、核心概念
2.1 模块 (Modules)
基本模块结构
import { Module } from '@nestjs/common';
import { CatsController } from './cats.controller';
import { CatsService } from './cats.service';
@Module({
controllers: [CatsController],
providers: [CatsService],
exports: [CatsService] // 其他模块需要使用时导出
})
export class CatsModule {}模块装饰器参数说明:
controllers:该模块中定义的控制器列表providers:该模块提供给 Nest 依赖注入系统的服务imports:导入其他模块,使其导出的提供者可用exports:导出模块内部组件,供其他模块使用
根模块 (AppModule)
import { Module } from '@nestjs/common';
import { CatsModule } from './cats/cats.module';
@Module({
imports: [CatsModule],
})
export class AppModule {}2.2 控制器 (Controllers)
基本控制器
import { Controller, Get, Param } from '@nestjs/common';
@Controller('cats')
export class CatsController {
@Get()
getAll(): string {
return '返回所有猫咪';
}
@Get(':id')
getOne(@Param('id') id: string): string {
return `返回ID为 ${id} 的猫咪`;
}
}请求方法装饰器:
@Get():处理 GET 请求@Post():处理 POST 请求@Put():处理 PUT 请求@Delete():处理 DELETE 请求@Patch():处理 PATCH 请求@Options():处理 OPTIONS 请求@Head():处理 HEAD 请求
请求参数获取:
@Param():获取路由参数@Query():获取查询参数@Body():获取请求体@Headers():获取请求头@Session():获取会话@Ip():获取客户端 IP
2.3 服务 (Providers)
基本服务
import { Injectable } from '@nestjs/common';
@Injectable()
export class CatsService {
private cats = [
{ id: 1, name: 'Whiskers' },
{ id: 2, name: 'Mittens' },
];
getAll(): any[] {
return this.cats;
}
getOne(id: number): any {
return this.cats.find(cat => cat.id === id);
}
}依赖注入
import { Injectable } from '@nestjs/common';
import { CatsService } from './cats.service';
@Injectable()
export class CatsController {
constructor(private catsService: CatsService) {}
@Get()
getAll() {
return this.catsService.getAll();
}
}服务生命周期:
- 默认是单例(Singleton)
- 可以通过
@Injectable({ scope: Scope.REQUEST })设置为请求范围
三、请求处理
3.1 DTO 与验证
创建 DTO (Data Transfer Object)
// create-cat.dto.ts
import { IsString, IsInt, Min } from 'class-validator';
export class CreateCatDto {
@IsString()
name: string;
@IsInt()
@Min(0)
age: number;
@IsString()
breed: string;
}在控制器中使用
import { Body, Post } from '@nestjs/common';
import { CreateCatDto } from './dto/create-cat.dto';
import { CatsService } from './cats.service';
@Post()
create(@Body() createCatDto: CreateCatDto) {
return this.catsService.create(createCatDto);
}自动验证管道 (ValidationPipe)
// main.ts
async function bootstrap() {
const app = await NestFactory.create(AppModule);
// 全局启用验证
app.useGlobalPipes(new ValidationPipe({
whitelist: true, // 自动去除 DTO 中未定义的属性
forbidNonWhitelisted: true, // 拒绝不合法的请求
transform: true, // 自动转换类型
}));
await app.listen(3000);
}3.2 异常处理
内置异常类
import { HttpException, HttpStatus } from '@nestjs/common';
// 抛出标准 HTTP 异常
throw new HttpException('Not found', HttpStatus.NOT_FOUND);
// 抛出预定义的 HTTP 异常
throw new NotFoundException('猫咪未找到');自定义异常过滤器
// http-exception.filter.ts
import { ExceptionFilter, Catch, ArgumentsHost, HttpException } from '@nestjs/common';
import { Response } from 'express';
@Catch(HttpException)
export class HttpExceptionFilter implements ExceptionFilter {
catch(exception: HttpException, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse<Response>();
const status = exception.getStatus();
const message = exception.getResponse();
response
.status(status)
.json({
statusCode: status,
timestamp: new Date().toISOString(),
path: ctx.getRequest().url,
message,
});
}
}全局注册异常过滤器
// main.ts
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalFilters(new HttpExceptionFilter());
await app.listen(3000);
}3.3 中间件 (Middleware)
创建中间件
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(`[${new Date().toISOString()}] ${req.method} ${req.url}`);
next();
}
}在模块中注册中间件
// app.module.ts
import { Module, NestModule, MiddlewareConsumer } from '@nestjs/common';
import { LoggerMiddleware } from './common/middleware/logger.middleware';
import { CatsModule } from './cats/cats.module';
@Module({
imports: [CatsModule],
})
export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
// 应用到所有路由
consumer.apply(LoggerMiddleware).forRoutes('*');
// 或者应用到特定路由
// consumer.apply(LoggerMiddleware).forRoutes(CatsController);
}
}四、数据库集成
4.1 TypeORM 集成
安装依赖
npm install @nestjs/typeorm typeorm mysql2配置 TypeORM
// app.module.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
@Module({
imports: [
TypeOrmModule.forRoot({
type: 'mysql',
host: 'localhost',
port: 3306,
username: 'root',
password: 'password',
database: 'test',
entities: [__dirname + '/**/*.entity{.ts,.js}'],
synchronize: true, // 开发环境使用,生产环境应设为 false
}),
],
})
export class AppModule {}4.2 实体 (Entities)
创建实体
// cat.entity.ts
import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';
@Entity()
export class Cat {
@PrimaryGeneratedColumn()
id: number;
@Column()
name: string;
@Column('int')
age: number;
@Column()
breed: string;
}4.3 仓储 (Repositories)
在模块中导入实体
// cats.module.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { Cat } from './cat.entity';
import { CatsController } from './cats.controller';
import { CatsService } from './cats.service';
@Module({
imports: [TypeOrmModule.forFeature([Cat])],
controllers: [CatsController],
providers: [CatsService],
})
export class CatsModule {}在服务中使用仓储
// cats.service.ts
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Cat } from './cat.entity';
import { CreateCatDto } from './dto/create-cat.dto';
@Injectable()
export class CatsService {
constructor(
@InjectRepository(Cat)
private catsRepository: Repository<Cat>,
) {}
async create(createCatDto: CreateCatDto): Promise<Cat> {
const cat = this.catsRepository.create(createCatDto);
return this.catsRepository.save(cat);
}
async findAll(): Promise<Cat[]> {
return this.catsRepository.find();
}
async findOne(id: number): Promise<Cat> {
return this.catsRepository.findOne({ where: { id } });
}
async remove(id: number): Promise<void> {
await this.catsRepository.delete(id);
}
}五、高级功能
5.1 认证与授权
安装依赖
npm install @nestjs/jwt @nestjs/passport passport passport-jwt创建认证模块
// auth.module.ts
import { Module } from '@nestjs/common';
import { JwtModule } from '@nestjs/jwt';
import { UsersModule } from '../users/users.module';
import { AuthService } from './auth.service';
import { LocalStrategy } from './local.strategy';
import { JwtStrategy } from './jwt.strategy';
@Module({
imports: [
UsersModule,
JwtModule.register({
secret: process.env.JWT_SECRET || 'secretKey',
signOptions: { expiresIn: '60m' },
}),
],
providers: [AuthService, LocalStrategy, JwtStrategy],
exports: [AuthService],
})
export class AuthModule {}JWT 策略
// jwt.strategy.ts
import { ExtractJwt, Strategy } from 'passport-jwt';
import { PassportStrategy } from '@nestjs/passport';
import { Injectable } from '@nestjs/common';
import { jwtConstants } from './constants';
@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor() {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
ignoreExpiration: false,
secretOrKey: jwtConstants.secret,
});
}
async validate(payload: any) {
return { userId: payload.sub, username: payload.username };
}
}使用守卫保护路由
// cats.controller.ts
import { UseGuards } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
@UseGuards(AuthGuard('jwt'))
@Controller('cats')
export class CatsController {
// 受保护的路由
@Get()
getAll() {
return this.catsService.getAll();
}
}5.2 配置管理
安装依赖
npm install @nestjs/config配置模块
// app.module.ts
import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
@Module({
imports: [
ConfigModule.forRoot({
isGlobal: true, // 全局可用
envFilePath: '.env', // 环境变量文件路径
}),
],
})
export class AppModule {}使用配置
// cats.service.ts
import { Inject, Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
@Injectable()
export class CatsService {
constructor(private configService: ConfigService) {}
getDatabaseHost() {
return this.configService.get('DATABASE_HOST');
}
getJwtSecret() {
return this.configService.get('JWT_SECRET', 'defaultSecret'); // 提供默认值
}
}.env 文件示例
DATABASE_HOST=localhost
DATABASE_PORT=3306
JWT_SECRET=mysecretkey5.3 自定义装饰器
创建用户装饰器
// user.decorator.ts
import { createParamDecorator, ExecutionContext } from '@nestjs/common';
export const User = createParamDecorator(
(data: unknown, ctx: ExecutionContext) => {
const request = ctx.switchToHttp().getRequest();
return request.user;
},
);在控制器中使用
// cats.controller.ts
import { User } from '../decorators/user.decorator';
@Get('profile')
getProfile(@User() user: any) {
return user;
}六、测试
6.1 单元测试
服务测试示例
// cats.service.spec.ts
import { Test, TestingModule } from '@nestjs/testing';
import { CatsService } from './cats.service';
describe('CatsService', () => {
let service: CatsService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [CatsService],
}).compile();
service = module.get<CatsService>(CatsService);
});
it('should be defined', () => {
expect(service).toBeDefined();
});
describe('create', () => {
it('should create a cat', async () => {
const cat = await service.create({ name: 'Whiskers', age: 2, breed: 'Tabby' });
expect(cat).toEqual({
id: expect.any(Number),
name: 'Whiskers',
age: 2,
breed: 'Tabby',
});
});
});
});6.2 E2E 测试
使用 Supertest 进行端到端测试
// cats.e2e-spec.ts
import { Test, TestingModule } from '@nestjs/testing';
import { INestApplication } from '@nestjs/common';
import * as request from 'supertest';
import { AppModule } from './../src/app.module';
describe('CatsController (e2e)', () => {
let app: INestApplication;
beforeEach(async () => {
const moduleFixture: TestingModule = await Test.createTestingModule({
imports: [AppModule],
}).compile();
app = moduleFixture.createNestApplication();
await app.init();
});
it('/cats (GET)', () => {
return request(app.getHttpServer())
.get('/cats')
.expect(200)
.expect([]);
});
it('/cats (POST)', () => {
return request(app.getHttpServer())
.post('/cats')
.send({ name: 'Whiskers', age: 2, breed: 'Tabby' })
.expect(201)
.expect({
id: 1,
name: 'Whiskers',
age: 2,
breed: 'Tabby',
});
});
});七、实用技巧与最佳实践
7.1 项目结构组织
推荐的项目结构
src/
├── auth/ # 认证模块
├── common/ # 公共模块
│ ├── decorators/ # 自定义装饰器
│ ├── filters/ # 异常过滤器
│ ├── guards/ # 守卫
│ ├── interceptors/ # 拦截器
│ ├── middleware/ # 中间件
│ └── pipes/ # 管道
├── config/ # 配置
├── users/ # 用户模块
│ ├── dto/ # DTO
│ ├── entities/ # 实体
│ ├── user.controller.ts
│ ├── user.module.ts
│ └── user.service.ts
├── app.module.ts
└── main.ts7.2 管道 (Pipes)
自定义验证管道
// parse-int.pipe.ts
import { PipeTransform, Injectable, ArgumentMetadata, BadRequestException } from '@nestjs/common';
@Injectable()
export class ParseIntPipe implements PipeTransform<string, number> {
transform(value: string, metadata: ArgumentMetadata): number {
const val = parseInt(value, 10);
if (isNaN(val)) {
throw new BadRequestException('Validation failed');
}
return val;
}
}在控制器中使用
// cats.controller.ts
@Get(':id')
findOne(@Param('id', ParseIntPipe) id: number) {
return this.catsService.findOne(id);
}7.3 拦截器 (Interceptors)
日志拦截器
// logging.interceptor.ts
import {
Injectable,
NestInterceptor,
ExecutionContext,
CallHandler,
} from '@nestjs/common';
import { Observable } from 'rxjs';
import { tap } from 'rxjs/operators';
@Injectable()
export class LoggingInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
console.log('Before...');
const now = Date.now();
return next
.handle()
.pipe(
tap(() => console.log(`After... ${Date.now() - now}ms`)),
);
}
}全局注册拦截器
// main.ts
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalInterceptors(new LoggingInterceptor());
await app.listen(3000);
}7.4 守卫 (Guards)
角色守卫
// roles.guard.ts
import {
CanActivate,
ExecutionContext,
Injectable,
UnauthorizedException,
} from '@nestjs/common';
import { Reflector } from '@nestjs/core';
@Injectable()
export class RolesGuard implements CanActivate {
constructor(private reflector: Reflector) {}
canActivate(context: ExecutionContext): boolean {
const roles = this.reflector.get<string[]>('roles', context.getHandler());
if (!roles) {
return true;
}
const request = context.switchToHttp().getRequest();
const user = request.user;
return this.matchRoles(roles, user.roles);
}
private matchRoles(roles: string[], userRoles: string[]): boolean {
return roles.some(role => userRoles.includes(role));
}
}在控制器中使用
// cats.controller.ts
import { Roles } from './roles.decorator';
import { RolesGuard } from './roles.guard';
@UseGuards(RolesGuard)
@Controller('cats')
export class CatsController {
@Roles('admin')
@Post()
create(@Body() createCatDto: CreateCatDto) {
return this.catsService.create(createCatDto);
}
}