基础概念
1. 介绍 NestJS
NestJS 是一个用于构建高效、可扩展的 Node.js 服务端应用程序的框架。它基于 TypeScript 开发,结合了 OOP(面向对象编程)、FP(函数式编程)和 FRP(函数式响应式编程)的最佳实践。
为什么选择 NestJS?
- TypeScript 支持:完整的类型安全,减少运行时错误
- 模块化架构:清晰的代码组织方式
- 依赖注入:松耦合的组件设计
- 丰富的生态:与主流数据库、ORM、认证系统等无缝集成
- 企业级应用:适合构建大型、复杂的应用程序
一个标准的 NestJS 项目结构如下:
bash
project-name/
├── src/
│ ├── main.ts # 应用入口文件
│ ├── app.module.ts # 根模块
│ ├── app.controller.ts # 控制器
│ ├── app.service.ts # 服务
│ └── app.controller.spec.ts # 测试文件
├── test/ # 测试目录
├── nest-cli.json # Nest CLI 配置
├── package.json # 依赖管理
└── tsconfig.json # TypeScript 配置
核心文件说明:
- main.ts:应用的入口点,创建 Nest 应用实例
- app.module.ts:根模块,组织应用的各个部分
- app.controller.ts:处理 HTTP 请求
- app.service.ts:包含业务逻辑
2. 核心概念 - 控制器与服务
2.1 控制器(Controllers)
控制器 负责处理客户端请求并返回响应。它们定义了应用的路由和请求处理逻辑。
创建控制器
typescript
// src/users/users.controller.ts
import { Controller, Get, Post, Body } from '@nestjs/common';
@Controller('users')
export class UsersController {
@Get()
findAll() {
return 'This action returns all users';
}
@Get(':id')
findOne(id: string) {
return `This action returns a #${id} user`;
}
@Post()
create(@Body() createUserDto: any) {
return `This action adds a new user: ${createUserDto.name}`;
}
}
控制器装饰器
- @Controller('users'):定义控制器的基本路由
- @Get():处理 GET 请求
- @Post():处理 POST 请求
- @Put():处理 PUT 请求
- @Delete():处理 DELETE 请求
2.2 服务(Services)
服务 包含应用的业务逻辑,被控制器调用。它们通过依赖注入的方式提供给控制器使用。
创建服务
typescript
// src/users/users.service.ts
import { Injectable } from '@nestjs/common';
@Injectable()
export class UsersService {
private users = [
{ id: '1', name: 'John Doe', email: 'john@example.com' },
{ id: '2', name: 'Jane Doe', email: 'jane@example.com' }
];
findAll() {
return this.users;
}
findOne(id: string) {
return this.users.find(user => user.id === id);
}
create(user: any) {
const newUser = {
id: Date.now().toString(),
...user
};
this.users.push(newUser);
return newUser;
}
update(id: string, updateUser: any) {
const index = this.users.findIndex(user => user.id === id);
if (index >= 0) {
this.users[index] = { ...this.users[index], ...updateUser };
return this.users[index];
}
return null;
}
remove(id: string) {
const index = this.users.findIndex(user => user.id === id);
if (index >= 0) {
return this.users.splice(index, 1);
}
return null;
}
}
2.3 依赖注入
NestJS 使用依赖注入模式来管理组件之间的依赖关系:
typescript
// src/users/users.controller.ts
import { Controller, Get, Post, Body, Param } from '@nestjs/common';
import { UsersService } from './users.service';
@Controller('users')
export class UsersController {
constructor(private readonly usersService: UsersService) {}
@Get()
findAll() {
return this.usersService.findAll();
}
@Get(':id')
findOne(@Param('id') id: string) {
return this.usersService.findOne(id);
}
@Post()
create(@Body() createUserDto: any) {
return this.usersService.create(createUserDto);
}
}
3. 模块系统
3.1 模块的概念
模块 是组织应用程序结构的基本单元。每个 Nest 应用程序至少有一个根模块(AppModule)。
3.2 创建功能模块
typescript
// src/users/users.module.ts
import { Module } from '@nestjs/common';
import { UsersController } from './users.controller';
import { UsersService } from './users.service';
@Module({
controllers: [UsersController],
providers: [UsersService]
})
export class UsersModule {}
3.3 根模块
typescript
// src/app.module.ts
import { Module } from '@nestjs/common';
import { UsersModule } from './users/users.module';
@Module({
imports: [UsersModule],
})
export class AppModule {}
3.4 模块的属性
- controllers:定义属于该模块的控制器
- providers:定义由 Nest 注入器实例化的服务
- imports:导入其他模块
- exports:导出服务供其他模块使用
kotlin
// 共享模块示例
@Module({
providers: [SharedService],
exports: [SharedService]
})
export class SharedModule {}
4. 路由与请求处理
4.1 路由参数
路径参数
如下示例, 框架内置了动态匹配路由的逻辑,假如客户端访问 域名+端口号/images/text-to-image ,且方法是post,会自动走到generateTextToImage并且走相应的service去返回对应的数据
typescript
import { Body, Controller, Get, Param, Post } from '@nestjs/common';
import { ImagesService } from './images.service';
import {
GenerateImageToImageDto,
GenerateTextTOImageDto,
} from './dto/generate-image.dto';
import { GenerationMode } from '../tasks/entities/generation-task.entity';
@Controller('images')
export class ImagesController {
constructor(private readonly imagesService: ImagesService) {}
/**
* POST images/text-to-image
* 创建任务并转发给 n8n
*/
@Post('text-to-image')
async generateTextToImage(@Body() dto: GenerateTextTOImageDto) {
return this.imagesService.createTextToImage(dto);
}
/**
* POST images/image-to-image
* 创建任务并转发给 n8n
*/
@Post('image-to-image')
async generateImageToImage(@Body() dto: GenerateImageToImageDto) {
return this.imagesService.createImageToImage(dto);
}
/**
* GET /images/:id
* 便于前端轮询查询(或调试)
*/
@Get(':id')
async get(@Param('id') id: string) {
return this.imagesService.getTask(id);
}
/**
* 查询多角度任务聚合状态
*/ @Get('multi-angle/:bizId') async getMultiAngleTasks(
@Param('bizId') bizId: string,
): Promise<
Array<{
id: string;
prompt: string;
status: string;
angle?: string;
providerTaskId?: string;
createdAt: Date;
}>
> {
const tasks = await this.imagesService.taskRepo.find({
where: { bizId, mode: GenerationMode.TEXT_TO_IMAGE },
order: { createdAt: 'ASC' },
});
return tasks.map((task) => ({
id: task.id,
prompt: task.prompt,
status: task.status,
angle: task.metadata?.angle, // 从 metadata 中获取角度
providerTaskId: task.providerTaskId,
createdAt: task.createdAt,
}));
}
}
查询参数
使用这个装饰器,拿到对应的查询参数并在服务端重新命名使用
less
@Get('search')
searchUsers(
https//xxx:xxx/user?q=1&page=2&limit=2
@Query('q') query: string,
@Query('page') page: number = 1,
@Query('limit') limit: number = 10
// 重新命名,可以再服务端拿到参与逻辑编写
) {
return { query, page, limit };
}
4.2 请求体
使用 DTO 验证请求数据
如下图所示:
-
CreateUserDto就是一个 DTO,它定义了创建用户时请求体(@Body())应该包含的字段(name、email、password)及类型。 -
作用主要有两个:
- 规范请求格式:明确告诉前端 "创建用户必须传这些字段",避免无效或错误的数据提交。
- 类型约束:在 TypeScript 中提供类型检查,确保后端处理数据时的类型安全(比如不会把
email当成数字处理)。
less
// src/users/dto/create-user.dto.ts
export class CreateUserDto {
name: string;
email: string;
password: string;
}
// 在控制器中使用
@Post()
create(@Body() createUserDto: CreateUserDto) {
return this.usersService.create(createUserDto);
}
4.3 RESTful API 设计
一个标准的 RESTful API 设计应该包含以下端点:
less
@Controller('users')
export class UsersController {
@Get() // GET /users - 获取所有用户
findAll() {}
@Get(':id') // GET /users/:id - 获取单个用户
findOne() {}
@Post() // POST /users - 创建用户
create() {}
@Put(':id') // PUT /users/:id - 更新用户
update() {}
@Delete(':id') // DELETE /users/:id - 删除用户
remove() {}
}
5. 数据验证
5.1 使用 class-validator
NestJS 推荐使用 class-validator 库进行数据验证:
安装依赖
arduino
npm install class-validator class-transformer
创建验证 DTO
less
// src/users/dto/create-user.dto.ts
import { IsString, IsEmail, IsNotEmpty, MinLength } from 'class-validator';
export class CreateUserDto {
@IsString()
@IsNotEmpty()
name: string;
@IsEmail()
@IsNotEmpty()
email: string;
@IsString()
@IsNotEmpty()
@MinLength(6)
password: string;
}
5.2 全局验证管道
在 main.ts 中配置全局验证管道:
javascript
// src/main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { ValidationPipe } from '@nestjs/common';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
// 全局验证管道
app.useGlobalPipes(new ValidationPipe({
whitelist: true, // 自动移除没有装饰器的属性
forbidNonWhitelisted: true, // 如果有非白名单属性则抛出错误
transform: true // 自动转换类型
}));
await app.listen(3000);
}
bootstrap();
5.3 常用验证装饰器
| 装饰器 | 功能 |
|---|---|
| @IsString() | 验证是否为字符串 |
| @IsNumber() | 验证是否为数字 |
| @IsEmail() | 验证是否为邮箱格式 |
| @IsNotEmpty() | 验证是否不为空 |
| @MinLength(n) | 验证最小长度 |
| @MaxLength(n) | 验证最大长度 |
| @IsOptional() | 标记为可选 |
6. 数据库集成
6.1 使用 TypeORM
NestJS 与 TypeORM(一个流行的 ORM 框架)有很好的集成:
安装依赖
bash
npm install @nestjs/typeorm typeorm mysql2
配置数据库连接
php
// src/app.module.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { UsersModule } from './users/users.module';
@Module({
imports: [
TypeOrmModule.forRoot({
type: 'mysql',
host: 'localhost',
port: 3306,
username: 'root',
password: 'password',
database: 'nestjs_db',
entities: [__dirname + '/**/*.entity{.ts,.js}'],
synchronize: true, // 开发环境下自动同步数据库结构
logging: true,
}),
UsersModule,
],
})
export class AppModule {}
6.2 创建实体
less
// src/users/entities/user.entity.ts
import { Entity, Column, PrimaryGeneratedColumn, CreateDateColumn, UpdateDateColumn } from 'typeorm';
@Entity('users')
export class User {
@PrimaryGeneratedColumn('uuid')
id: string;
@Column({ length: 50, nullable: false })
name: string;
@Column({ unique: true, nullable: false })
email: string;
@Column({ nullable: false })
password: string;
@Column({ default: 'user' })
role: string;
@CreateDateColumn()
createdAt: Date;
@UpdateDateColumn()
updatedAt: Date;
}
6.3 使用 Repository
以下例子是将User 组件导入进来,ioc容器会自动检索其是否有对应的实例,如果有则直接使用已有实例,避免重复注册实例增大性能开销
typescript
// src/users/users.service.ts
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { User } from './entities/user.entity';
import { CreateUserDto } from './dto/create-user.dto';
@Injectable()
export class UsersService {
constructor(
@InjectRepository(User)
private usersRepository: Repository<User>,
) {}
async create(createUserDto: CreateUserDto): Promise<User> {
const user = this.usersRepository.create(createUserDto);
return this.usersRepository.save(user);
}
async findAll(): Promise<User[]> {
return this.usersRepository.find();
}
async findOne(id: string): Promise<User> {
return this.usersRepository.findOneBy({ id });
}
async update(id: string, updateUserDto: any): Promise<User> {
await this.usersRepository.update(id, updateUserDto);
return this.findOne(id);
}
async remove(id: string): Promise<void> {
await this.usersRepository.delete(id);
}
}
6.4 模块配置
python
// src/users/users.module.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { User } from './entities/user.entity';
import { UsersService } from './users.service';
import { UsersController } from './users.controller';
@Module({
imports: [TypeOrmModule.forFeature([User])],
controllers: [UsersController],
providers: [UsersService]
})
export class UsersModule {}
7. 错误处理
7.1 内置异常
NestJS 提供了多种内置异常类型:
- 内置异常(7.1)
NotFoundException:资源不存在(对应 404 状态码)BadRequestException:请求参数错误(400)UnauthorizedException:未授权(401)ForbiddenException:权限不足(403)InternalServerErrorException:服务器内部错误(500)
示例中,当通过 id 查询用户时,如果用户不存在,就抛出 NotFoundException,NestJS 会自动返回对应的 404 错误响应。
typescript
import {
BadRequestException,
NotFoundException,
UnauthorizedException,
ForbiddenException,
InternalServerErrorException
} from '@nestjs/common';
@Controller('users')
export class UsersController {
@Get(':id')
findOne(@Param('id') id: string) {
const user = this.usersService.findOne(id);
if (!user) {
throw new NotFoundException(`User with ID ${id} not found`);
}
return user;
}
}
- 自定义异常过滤器(7.2)
默认情况下,NestJS 对异常的响应格式比较简单。通过自定义异常过滤器,可以统一错误响应的结构,增加更多上下文信息(如时间戳、请求路径等)。
-
@Catch(HttpException):表示这个过滤器专门捕获HttpException及其子类(包括上面的内置异常)。 -
implements ExceptionFilter:实现ExceptionFilter接口,必须定义catch方法,用于处理捕获到的异常。 -
逻辑说明:
- 通过
ArgumentsHost获取请求(Request)和响应(Response)对象。 - 从异常中获取状态码(
status)和错误信息(message)。 - 自定义响应格式:包含状态码、时间戳、请求路径、错误信息,让前端能更清晰地理解错误原因。
typescript// src/common/filters/http-exception.filter.ts import { ExceptionFilter, Catch, ArgumentsHost, HttpException } from '@nestjs/common'; import { Request, 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 request = ctx.getRequest<Request>(); const status = exception.getStatus(); response.status(status).json({ statusCode: status, timestamp: new Date().toISOString(), path: request.url, message: exception.message }); } } - 通过
- 全局异常处理(7.3)
通过 app.useGlobalFilters(new HttpExceptionFilter()) 将自定义的 HttpExceptionFilter 注册为全局过滤器,意味着:
- 整个应用中所有抛出的
HttpException及其子类异常,都会被这个过滤器捕获并处理。 - 无需在每个控制器或服务中重复定义异常处理逻辑,实现了错误响应格式的全局统一。
javascript
// src/main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { HttpExceptionFilter } from './common/filters/http-exception.filter';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
// 全局异常过滤器
app.useGlobalFilters(new HttpExceptionFilter());
await app.listen(3000);
}
bootstrap();
深入扩展
IOC 控制反转与 DI 依赖注入
1.1 控制反转(IoC)概念
控制反转 是一种设计原则,它将对象的创建和依赖关系的管理从应用程序代码转移到容器 中。在 NestJS 中,这个容器就是 IoC 容器。
1.2 依赖注入(DI)原理
依赖注入 是实现 IoC 的一种方式,它允许类从外部接收依赖项,而不是自己创建它们。
typescript
// 传统方式 - 紧耦合
class UserService {
private database = new Database();
getUser(id: string) {
return this.database.query(SELECT * FROM users WHERE id = ${id});
}
}
// 依赖注入 - 松耦合
@Injectable()
class UserService {
constructor(private database: DatabaseService) {}
getUser(id: string) {
return this.database.query(SELECT * FROM users WHERE id = ${id});
}
}
1.3 NestJS 中的依赖注入
typescript
import { Injectable } from '@nestjs/common';
@Injectable()
export class UserService {
getUsers() {
return ['Alice', 'Bob', 'Charlie'];
}
}
@Injectable()
export class AuthService {
constructor(private userService: UserService) {}
validateUser(username: string) {
const users = this.userService.getUsers();
return users.includes(username);
}
}
1.4 依赖注入的优势
- 解耦 代码 - 降低组件间的耦合度
- 易于测试 - 可以轻松替换依赖项
- 更好的维护性 - 依赖关系集中管理
- 可扩展性 - 便于功能扩展和重构
- 前置知识 - 装饰器
装饰器
2.1 什么是装饰器
装饰器 是一种特殊类型的声明,它能够被附加到类声明、方法、访问符、属性或参数上。装饰器使用 @expression 形式,其中 expression 必须是一个函数,在运行时被调用,被装饰的声明信息作为参数传入。
2.2 TypeScript 装饰器类型
类装饰器
javascript
function Component(constructor: Function) {
console.log('Component decorator called');
}
@Component
class UserComponent {
constructor() {
console.log('UserComponent created');
}
}
方法装饰器
typescript
function Log(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
descriptor.value = function(...args: any[]) {
console.log(Calling ${propertyKey} with arguments: ${args});
return originalMethod.apply(this, args);
};
return descriptor;
}
class UserService {
@Log
getUser(id: string) {
return User ${id};
}
}
属性装饰器
typescript
function DefaultValue(value: any) {
return function(target: any, propertyKey: string) {
target[propertyKey] = value;
};
}
class User {
@DefaultValue('John Doe')
name: string;
}
2.3 NestJS 中的装饰器
kotlin
NestJS 大量使用装饰器来声明各种组件:
import { Controller, Get, Injectable } from '@nestjs/common';
@Injectable()
export class UserService {}
@Controller('users')
export class UserController {
constructor(private userService: UserService) {}
@Get()
getUsers() {
return this.userService.getUsers();
}
}
3.NestJS 中间件
3.1 中间件的概念
中间件 是在路由处理程序之前调用的函数。中间件函数可以访问请求对象(req)、响应对象(res)和应用程序的请求 - 响应循环中的下一个中间件函数。
3.2 中间件的类型
函数中间件
vbscript
// logger.middleware.ts
import { Request, Response, NextFunction } from 'express';
export function loggerMiddleware(
req: Request,
res: Response,
next: NextFunction
) {
console.log(${req.method} ${req.path} - ${new Date().toISOString()});
next();
}
类中间件
typescript
// 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.path} - ${new Date().toISOString()});
// 可以在这里添加更多逻辑
res.on('finish', () => {
console.log(Response status: ${res.statusCode});
});
next();
}
}
3.3 中间件的使用
全局中间件
javascript
// main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { loggerMiddleware } from './common/middleware/logger.middleware';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
// 使用函数中间件
app.use(loggerMiddleware);
await app.listen(3000);
}
bootstrap();
模块级中间件
typescript
// app.module.ts
import { Module, NestModule, MiddlewareConsumer } from '@nestjs/common';
import { LoggerMiddleware } from './common/middleware/logger.middleware';
import { UserController } from './user/user.controller';
@Module({
imports: [],
controllers: [UserController],
providers: []
})
export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer
.apply(LoggerMiddleware)
.forRoutes(UserController);
}
}
路由级中间件
scss
// app.module.ts
@Module({})
export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer
.apply(LoggerMiddleware)
.forRoutes('users');
// 或者指定 HTTP 方法
consumer
.apply(AuthMiddleware)
.forRoutes({ path: 'users', method: RequestMethod.GET });
// 多个中间件
consumer
.apply(Middleware1, Middleware2)
.forRoutes('posts');
}
}
3.4 常用中间件示例
认证中间件
typescript
// auth.middleware.ts
import { Injectable, NestMiddleware, UnauthorizedException } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';
import { JwtService } from '@nestjs/jwt';
@Injectable()
export class AuthMiddleware implements NestMiddleware {
constructor(private jwtService: JwtService) {}
use(req: Request, res: Response, next: NextFunction) {
const authHeader = req.headers.authorization;
if (!authHeader || !authHeader.startsWith('Bearer ')) {
throw new UnauthorizedException('Authentication required');
}
const token = authHeader.split(' ')[1];
try {
const decoded = this.jwtService.verify(token);
req.user = decoded;
next();
} catch (error) {
throw new UnauthorizedException('Invalid or expired token');
}
}
}
请求计时中间件
typescript
// timing.middleware.ts
import { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';
@Injectable()
export class TimingMiddleware implements NestMiddleware {
use(req: Request, res: Response, next: NextFunction) {
const start = Date.now();
res.on('finish', () => {
const duration = Date.now() - start;
console.log(${req.method} ${req.path} - ${duration}ms);
});
next();
}
}
CORS 中间件
javascript
// main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.enableCors({
origin: ['http://localhost:3000', 'https://myapp.com'],
methods: ['GET', 'POST', 'PUT', 'DELETE'],
allowedHeaders: ['Content-Type', 'Authorization']
});
await app.listen(3000);
}
bootstrap();