本教程面向零基础或刚接触 NestJS 的开发者,按由浅入深的顺序讲解所有主要知识点。建议按章节顺序阅读。
一、NestJS 简介
什么是 NestJS?
NestJS 是一个基于 Node.js 的 服务端框架,使用 TypeScript 编写,架构风格借鉴了 Angular。特点包括:
- 分层清晰:控制器、服务、模块等概念明确,适合中大型项目
- 依赖注入(DI):内置 IoC 容器,便于测试与解耦
- TypeScript 优先:类型安全、装饰器驱动
- 可扩展:支持 REST、GraphQL、WebSocket、微服务等
- 生态丰富:与 TypeORM、Prisma、Passport、class-validator 等无缝集成
适合做什么?
- RESTful API / BFF(Backend for Frontend)
- GraphQL 服务
- 微服务(配合 Kafka、RabbitMQ 等)
- 实时应用(WebSocket、SSE)
- 需要严格架构与可维护性的 Node 后端
前置知识
- JavaScript/TypeScript 基础
- Node.js 与 npm 基本使用
- 了解 HTTP、REST 概念更佳
二、环境准备与创建项目
环境要求
- Node.js:18.x 及以上(建议 20 LTS)
- 包管理器:npm / yarn / pnpm
使用 CLI 创建项目
安装 Nest CLI(可选,也可用 npx 不全局安装):
npm i -g @nestjs/cli
nest new my-app
或直接用 npx:
npx @nestjs/cli new my-app
按提示选择包管理器(npm / yarn / pnpm),完成后进入项目并启动:
cd my-app
npm run start
浏览器访问 http://localhost:3000 应看到 Hello World!。
常用脚本
| 命令 | 说明 |
|---|---|
npm run start | 开发模式(默认端口 3000) |
npm run start:dev | 监听文件变化自动重启 |
npm run start:debug | 调试模式 |
npm run build | 编译为 JavaScript 到 dist/ |
npm run start:prod | 以生产模式运行 dist/ |
日常开发用 npm run start:dev,保存即热重载。
三、项目目录结构
my-app/
├── src/
│ ├── app.module.ts # 根模块
│ ├── app.controller.ts # 根控制器
│ ├── app.service.ts # 根服务
│ └── main.ts # 入口文件,引导 Nest 应用
├── test/ # 测试
├── nest-cli.json # Nest CLI 配置
├── tsconfig.json
└── package.json
- main.ts:创建
NestFactory.create(AppModule)、监听端口、可配置全局管道/前缀等 - app.module.ts:根模块,通过
imports聚合其他模块 - app.controller.ts / app.service.ts:根控制器与根服务,演示最基础的请求处理与业务逻辑
后续会按功能拆成多个模块(如 users、posts),每个模块可有自己的 *.module.ts、*.controller.ts、*.service.ts。
四、核心概念:模块、控制器、服务
4.1 模块(Module)
模块是组织代码的单元,把控制器、服务、其他模块组织在一起。
// app.module.ts
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
@Module({
imports: [], // 其他模块
controllers: [AppController],
providers: [AppService],
exports: [AppService], // 导出后,其他模块可注入 AppService
})
export class AppModule {}
- imports:引入其他模块(使用其 exports)
- controllers:该模块下的控制器(处理 HTTP 等)
- providers:该模块下的可注入服务
- exports:把 providers 暴露给其他模块
4.2 控制器(Controller)
控制器负责处理入站请求并返回响应,对应路由与 HTTP 方法。
// app.controller.ts
import { Controller, Get, Post, Body, Param } from '@nestjs/common';
import { AppService } from './app.service';
@Controller() // 路由前缀为空,即根
export class AppController {
constructor(private readonly appService: AppService) {}
@Get()
getHello(): string {
return this.appService.getHello();
}
@Get('info')
getInfo() {
return { name: 'My App', version: '1.0' };
}
}
- @Controller('prefix'):该控制器下所有路由共用的前缀,如
@Controller('users')则路由为/users/... - @Get()、@Post()、@Put()、@Patch()、@Delete():对应 HTTP 方法
- constructor 中注入 AppService:由 Nest 的依赖注入提供实例
4.3 服务(Provider / Service)
服务承载业务逻辑,被控制器注入并调用,避免在控制器里写复杂逻辑。
// app.service.ts
import { Injectable } from '@nestjs/common';
@Injectable()
export class AppService {
getHello(): string {
return 'Hello World!';
}
}
- @Injectable():表示该类可由 Nest 的 DI 容器管理,并注入到其他类中
关系小结:请求 → 控制器(路由)→ 调用服务(业务)→ 返回响应。
五、依赖注入(DI)
Nest 内置 IoC 容器:你只声明「需要什么类」,容器负责创建实例并注入。
@Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
// ↑ 私有只读 ↑ 类型声明即可,Nest 自动注入实例
}
- Provider:在某个模块的
providers里声明(如AppService) - 注入:在控制器或其它服务的
constructor中写上类型,Nest 会注入该模块或已导入模块的exports中的对应实例 - 作用域:默认单例;可按需使用
Scope.REQUEST等实现请求级实例
六、路由与请求处理
6.1 路径与参数
@Controller('users')
export class UsersController {
@Get() // GET /users
findAll() { return []; }
@Get(':id') // GET /users/123
findOne(@Param('id') id: string) {
return { id };
}
@Post()
create(@Body() body: CreateUserDto) {
return body;
}
@Put(':id')
update(@Param('id') id: string, @Body() body: UpdateUserDto) {
return { id, ...body };
}
@Delete(':id')
remove(@Param('id') id: string) {
return { deleted: id };
}
}
常用参数装饰器:
| 装饰器 | 含义 |
|---|---|
@Param('id') | 路径参数 |
@Query('key') | 查询参数 |
@Body() | 请求体(JSON) |
@Headers('name') | 请求头 |
@Req() / @Res() | 原生 request / response(慎用,会失去部分 Nest 能力) |
6.2 全局路由前缀
在 main.ts 中可为所有路由加前缀:
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.setGlobalPrefix('api'); // 所有路由变为 /api/...
await app.listen(3000);
}
七、DTO 与验证(Validation)
7.1 为什么用 DTO
- 定义请求体的形状,便于类型提示和文档
- 配合 class-validator 做自动校验,非法请求在进入控制器前被拦截
7.2 安装与启用
npm i class-validator class-transformer
在 main.ts 中启用全局验证管道:
import { ValidationPipe } from '@nestjs/common';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalPipes(new ValidationPipe({
whitelist: true, // 自动去掉 DTO 中未声明的属性
forbidNonWhitelisted: true, // 若带未声明属性则直接 400
transform: true, // 按类型自动转换(如 string → number)
}));
await app.listen(3000);
}
7.3 定义 DTO
// create-user.dto.ts
import { IsString, IsEmail, MinLength, IsOptional } from 'class-validator';
export class CreateUserDto {
@IsString()
@MinLength(2)
name: string;
@IsEmail()
email: string;
@IsString()
@MinLength(6)
password: string;
@IsOptional()
@IsString()
avatar?: string;
}
控制器中直接使用即可,校验失败时 Nest 自动返回 400 与错误信息:
@Post()
create(@Body() dto: CreateUserDto) {
return this.usersService.create(dto);
}
八、管道(Pipe)
管道用于转换或校验输入数据,在到达控制器方法之前执行。
8.1 内置管道示例
- ValidationPipe:上面已用,配合 class-validator
- ParseIntPipe:将参数转为整数,否则 400
@Get(':id')
findOne(@Param('id', ParseIntPipe) id: number) {
return this.usersService.findOne(id);
}
8.2 自定义管道
实现 PipeTransform 接口即可:
// parse-bool.pipe.ts
import { PipeTransform, Injectable, BadRequestException } from '@nestjs/common';
@Injectable()
export class ParseBoolPipe implements PipeTransform<string, boolean> {
transform(value: string): boolean {
if (value === 'true') return true;
if (value === 'false') return false;
throw new BadRequestException('Expected "true" or "false"');
}
}
在参数或控制器上使用:@Query('active', ParseBoolPipe) active: boolean。
九、守卫(Guard)
守卫决定是否允许请求继续,常用于权限、角色校验。
9.1 使用方式
- 控制器级:
@UseGuards(RolesGuard) - 方法级:同上
- 全局:
app.useGlobalGuards(new RolesGuard())
9.2 简单示例
// roles.guard.ts
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
@Injectable()
export class RolesGuard implements CanActivate {
constructor(private reflector: Reflector) {}
canActivate(context: ExecutionContext): boolean {
const requiredRoles = this.reflector.get<string[]>('roles', context.getHandler());
if (!requiredRoles) return true;
const { user } = context.switchToHttp().getRequest();
return requiredRoles.some((role) => user?.roles?.includes(role));
}
}
通过 Reflector 读取元数据(如自定义装饰器 @Roles('admin')),再根据 user 判断。若返回 false,Nest 默认返回 403。
十、拦截器(Interceptor)
拦截器可在请求前/后统一处理逻辑,如日志、超时、响应格式封装等。
10.1 响应映射示例
// transform.interceptor.ts
import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
export interface Response<T> {
data: T;
timestamp: string;
}
@Injectable()
export class TransformInterceptor<T> implements NestInterceptor<T, Response<T>> {
intercept(context: ExecutionContext, next: CallHandler): Observable<Response<T>> {
return next.handle().pipe(
map((data) => ({
data,
timestamp: new Date().toISOString(),
})),
);
}
}
在 main.ts 中全局注册:app.useGlobalInterceptors(new TransformInterceptor());,则所有控制器返回的数据会被包成 { data, timestamp }。
10.2 典型用途
- 统一包装
{ code, data, message } - 日志、耗时统计
- 超时控制(
timeoutRxJS 操作符)
十一、异常与异常过滤器
11.1 内置 HTTP 异常
在服务或控制器中直接抛出,Nest 会转为对应状态码的 JSON:
import { NotFoundException, BadRequestException } from '@nestjs/common';
throw new NotFoundException('用户不存在');
throw new BadRequestException('参数错误');
常用:BadRequestException(400)、UnauthorizedException(401)、ForbiddenException(403)、NotFoundException(404) 等。
11.2 异常过滤器(Exception Filter)
若希望统一格式或对特定异常做特殊处理,可写异常过滤器:
@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 result = exception.getResponse();
response.status(status).json({
statusCode: status,
message: typeof result === 'object' ? (result as any).message : result,
timestamp: new Date().toISOString(),
});
}
}
使用:@UseFilters(HttpExceptionFilter) 或 app.useGlobalFilters(new HttpExceptionFilter())。
十二、中间件(Middleware)
中间件在路由处理器之前执行,可做日志、解析、限流等。
12.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) {
console.log(`${req.method} ${req.url}`);
next();
}
}
在模块中挂到指定路由:
export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer.apply(LoggerMiddleware).forRoutes('*');
}
}
12.2 函数式中间件
若无需依赖注入,可写为普通函数,在 consumer.apply(LoggerMiddleware) 处传入即可。
十三、配置(ConfigModule)
13.1 使用 @nestjs/config
npm i @nestjs/config
根模块中引入:
import { ConfigModule } from '@nestjs/config';
@Module({
imports: [
ConfigModule.forRoot({
isGlobal: true,
envFilePath: ['.env.local', '.env'],
}),
],
})
export class AppModule {}
13.2 读取配置
在服务或控制器中注入 ConfigService:
constructor(private config: ConfigService) {}
someMethod() {
const port = this.config.get<number>('PORT', 3000);
const dbUrl = this.config.get<string>('DATABASE_URL');
}
推荐配合 .env 文件,不要提交敏感信息。
十四、数据库(TypeORM 简介)
Nest 与 TypeORM、Prisma 等都能很好集成,这里以 TypeORM 为例说明「模块 + 实体」的用法。
14.1 安装
npm i @nestjs/typeorm typeorm mysql2
14.2 注册与实体
// app.module.ts
import { TypeOrmModule } from '@nestjs/typeorm';
import { User } from './users/user.entity';
@Module({
imports: [
TypeOrmModule.forRoot({
type: 'mysql',
host: 'localhost',
port: 3306,
username: 'root',
password: 'xxx',
database: 'mydb',
entities: [User],
synchronize: true, // 开发时可 true,生产建议 false + 迁移
}),
TypeOrmModule.forFeature([User]), // 在 UsersModule 中
],
})
export class AppModule {}
14.3 实体与在服务中注入 Repository
// user.entity.ts
import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm';
@Entity('users')
export class User {
@PrimaryGeneratedColumn()
id: number;
@Column()
name: string;
@Column({ unique: true })
email: string;
}
// users.service.ts
@Injectable()
export class UsersService {
constructor(
@InjectRepository(User)
private userRepo: Repository<User>,
) {}
async findAll(): Promise<User[]> {
return this.userRepo.find();
}
}
更多(关系、迁移、事务)请查阅 TypeORM 文档。
十五、认证(JWT 示例)
认证涉及「登录签发 token」和「请求时校验 token」,常用 Passport + JWT。
15.1 安装
npm i @nestjs/passport @nestjs/jwt passport passport-jwt passport-local
npm i -D @types/passport-jwt @types/passport-local
15.2 思路简述
- 登录:用
LocalStrategy校验用户名密码,通过后用JwtService.sign()签发 token,返回给前端。 - 受保护路由:用
JwtStrategy从 Header 的 Bearer token 中解析出用户信息,挂到request.user;再用 Guard 在需要登录的接口上使用JwtAuthGuard。
15.3 配置与使用
在模块中注册:
JwtModule.register({
secret: process.env.JWT_SECRET || 'your-secret',
signOptions: { expiresIn: '7d' },
}),
PassportModule,
在需要登录的控制器或方法上添加:
@UseGuards(JwtAuthGuard)
@Get('profile')
getProfile(@Request() req) {
return req.user;
}
具体实现可参考官方文档 NestJS 认证。
十六、测试
16.1 单元测试(服务)
使用 Jest,对服务类进行隔离测试,依赖用 mock:
describe('AppService', () => {
let service: AppService;
beforeEach(async () => {
const module = await Test.createTestingModule({
providers: [AppService],
}).compile();
service = module.get<AppService>(AppService);
});
it('getHello', () => {
expect(service.getHello()).toBe('Hello World!');
});
});
16.2 E2E 测试
对完整 HTTP 请求做测试:
describe('AppController (e2e)', () => {
it('/ (GET)', () => {
return request(app.getHttpServer())
.get('/')
.expect(200)
.expect('Hello World!');
});
});
运行:npm run test(单元)、npm run test:e2e(E2E)。
十七、构建与部署
17.1 构建
npm run build
产物在 dist/,为编译后的 JavaScript。
17.2 生产运行
npm run start:prod
会执行 node dist/main(或你配置的入口)。生产环境建议用 PM2、Docker 或云平台的 Node 运行时,并设置好环境变量(如 NODE_ENV=production、数据库与 JWT 的配置)。
17.3 部署注意点
- 使用 环境变量 管理配置,不要写死敏感信息
- 数据库用 迁移 管理表结构,不要依赖
synchronize: true - 可配合 Nginx 做反向代理与静态资源
- 健康检查可暴露一个
/health接口供负载均衡使用
十八、学习路径小结
- 入门:创建项目 → 理解 Module / Controller / Service → 改根路由看效果
- 请求:路由、@Param / @Query / @Body、DTO + ValidationPipe
- 结构:按功能拆模块(如 UsersModule)、依赖注入与 exports
- 增强:管道、守卫、拦截器、异常过滤器、中间件
- 数据:ConfigModule、TypeORM/Prisma、实体与 Repository
- 安全:JWT 认证、角色守卫
- 质量:单元测试、E2E 测试
- 上线:build、环境变量、进程管理、健康检查
遇到问题可查阅 NestJS 官方文档。