NestJS 介绍与核心概念
什么是 NestJS?
NestJS 是一个用于构建高效、可扩展的 Node.js 服务器端应用程序的企业级框架。它使用 TypeScript 构建,结合了面向对象编程(OOP)、函数式编程(FP)和函数响应式编程(FRP)的元素。
NestJS 的本质
可以这样理解 NestJS:
- Express.js:像是给你一堆建材和工具,让你自己盖房子
- NestJS:则像是给你一个完整的建筑架构图、施工团队和标准化的建造流程
javascript
// 传统Express.js开发
const express = require("express");
const app = express();
// 中间件、路由、控制器都混在一起
app.use(express.json());
app.get("/users", (req, res) => {
// 业务逻辑直接写在路由处理中
User.find({}, (err, users) => {
if (err) return res.status(500).json({ error: err });
res.json(users);
});
});
app.post("/users", (req, res) => {
const user = new User(req.body);
user.save((err) => {
if (err) return res.status(400).json({ error: err });
res.status(201).json(user);
});
});
app.listen(3000);typescript
// NestJS企业级开发
import { Controller, Get, Post, Body, ValidationPipe } from "@nestjs/common";
import { UserService } from "./user.service";
import { CreateUserDto } from "./dto/create-user.dto";
@Controller("users")
export class UserController {
constructor(private readonly userService: UserService) {}
@Get()
async findAll() {
// 业务逻辑委托给服务层
return await this.userService.findAll();
}
@Post()
async create(@Body() createUserDto: CreateUserDto) {
// 自动验证、类型检查
return await this.userService.create(createUserDto);
}
}
// 模块化架构
@Module({
controllers: [UserController],
providers: [UserService],
exports: [UserService],
})
export class UserModule {}NestJS 的设计哲学
1. 架构优先
NestJS 采用架构优先的设计理念,强制开发者遵循最佳实践:
typescript
// NestJS的分层架构
// 控制器层 (Controller Layer) - 处理HTTP请求和响应
@Controller("products")
export class ProductController {
constructor(private readonly productService: ProductService) {}
@Get()
findAll(): Promise<Product[]> {
return this.productService.findAll();
}
@Get(":id")
findOne(@Param("id") id: string): Promise<Product> {
return this.productService.findOne(id);
}
@Post()
create(@Body() createProductDto: CreateProductDto): Promise<Product> {
return this.productService.create(createProductDto);
}
@Put(":id")
update(
@Param("id") id: string,
@Body() updateProductDto: UpdateProductDto
): Promise<Product> {
return this.productService.update(id, updateProductDto);
}
@Delete(":id")
remove(@Param("id") id: string): Promise<void> {
return this.productService.remove(id);
}
}
// 服务层 (Service Layer) - 业务逻辑处理
@Injectable()
export class ProductService {
constructor(
@InjectRepository(Product)
private readonly productRepository: Repository<Product>
) {}
async findAll(): Promise<Product[]> {
return await this.productRepository.find();
}
async findOne(id: string): Promise<Product> {
const product = await this.productRepository.findOne(id);
if (!product) {
throw new NotFoundException(`产品 #${id} 不存在`);
}
return product;
}
async create(createProductDto: CreateProductDto): Promise<Product> {
const product = this.productRepository.create(createProductDto);
return await this.productRepository.save(product);
}
async update(
id: string,
updateProductDto: UpdateProductDto
): Promise<Product> {
const existingProduct = await this.findOne(id);
Object.assign(existingProduct, updateProductDto);
return await this.productRepository.save(existingProduct);
}
async remove(id: string): Promise<void> {
await this.findOne(id); // 验证存在性
await this.productRepository.delete(id);
}
}
// 数据访问层 (Data Access Layer) - 数据库操作
import {
Entity,
PrimaryGeneratedColumn,
Column,
CreateDateColumn,
UpdateDateColumn,
} from "typeorm";
@Entity("products")
export class Product {
@PrimaryGeneratedColumn("uuid")
id: string;
@Column()
name: string;
@Column("decimal", { precision: 10, scale: 2 })
price: number;
@Column({ default: true })
available: boolean;
@CreateDateColumn()
createdAt: Date;
@UpdateDateColumn()
updatedAt: Date;
}2. 依赖注入 (Dependency Injection)
依赖注入是 NestJS 的核心模式,它让代码更加模块化和可测试:
typescript
// 没有依赖注入的问题
class OrderController {
private orderService: OrderService;
private emailService: EmailService;
private paymentService: PaymentService;
constructor() {
// 紧耦合:控制器自己创建依赖
this.orderService = new OrderService();
this.emailService = new EmailService();
this.paymentService = new PaymentService();
}
async createOrder(orderData: CreateOrderDto) {
// 难以测试:无法轻松替换依赖
const order = await this.orderService.create(orderData);
await this.paymentService.processPayment(order.payment);
await this.emailService.sendConfirmation(order.customerEmail);
return order;
}
}
// NestJS依赖注入解决方案
@Injectable()
export class OrderService {
constructor(
private readonly emailService: EmailService,
private readonly paymentService: PaymentService,
private readonly orderRepository: OrderRepository
) {}
async createOrder(createOrderDto: CreateOrderDto): Promise<Order> {
// 业务逻辑清晰,依赖自动注入
const order = await this.orderRepository.save(createOrderDto);
try {
await this.paymentService.processPayment(order.payment);
await this.emailService.sendConfirmation(order.customerEmail);
} catch (error) {
// 事务回滚处理
await this.orderRepository.remove(order);
throw error;
}
return order;
}
}
@Controller("orders")
export class OrderController {
constructor(private readonly orderService: OrderService) {}
@Post()
async create(@Body() createOrderDto: CreateOrderDto) {
// 控制器专注于HTTP层面的处理
return await this.orderService.createOrder(createOrderDto);
}
}
// 模块配置:NestJS自动管理依赖关系
@Module({
controllers: [OrderController],
providers: [OrderService, EmailService, PaymentService, OrderRepository],
})
export class OrderModule {}3. 装饰器模式
装饰器让代码声明式且易于理解:
typescript
// HTTP方法装饰器
@Controller("users")
export class UserController {
@Get()
findAll() {
return "获取所有用户";
}
@Get(":id")
findOne(@Param("id") id: string) {
return `获取用户 ${id}`;
}
@Post()
create(@Body() createUserDto: CreateUserDto) {
return `创建用户`;
}
@Put(":id")
update(@Param("id") id: string, @Body() updateUserDto: UpdateUserDto) {
return `更新用户 ${id}`;
}
@Delete(":id")
remove(@Param("id") id: string) {
return `删除用户 ${id}`;
}
}
// 验证装饰器
export class CreateUserDto {
@IsEmail()
@IsNotEmpty()
email: string;
@IsString()
@IsMinLength(3)
@MaxLength(50)
name: string;
@IsString()
@IsMinLength(8)
@IsStrongPassword()
password: string;
@IsOptional()
@IsDateString()
birthDate?: string;
@IsOptional()
@IsEnum(Role)
role?: Role;
}
// 自定义装饰器
export const CurrentUser = createParamDecorator(
(data: unknown, ctx: ExecutionContext): User => {
const request = ctx.switchToHttp().getRequest();
return request.user;
}
);
export const Roles = (...roles: Role[]) => SetMetadata("roles", roles);
export const RequireRoles = (...roles: Role[]): MethodDecorator => {
return (target, propertyKey, descriptor) => {
SetMetadata("roles", roles)(target, propertyKey, descriptor);
UseGuards(RolesGuard)(target, propertyKey, descriptor);
};
};
// 使用自定义装饰器
@Controller("admin")
export class AdminController {
@Get("dashboard")
@RequireRoles(Role.ADMIN)
getDashboard(@CurrentUser() user: User) {
return `管理员 ${user.name} 的仪表板`;
}
@Post("users")
@RequireRoles(Role.ADMIN, Role.MODERATOR)
createUser(@Body() createUserDto: CreateUserDto) {
return "创建新用户";
}
}4. 模块化系统
模块化让大型应用保持结构清晰:
typescript
// 核心模块 - 基础功能
@Module({
imports: [],
controllers: [],
providers: [
// 全局服务
{
provide: APP_FILTER,
useClass: AllExceptionsFilter,
},
{
provide: APP_INTERCEPTOR,
useClass: LoggingInterceptor,
},
],
exports: [],
})
export class CoreModule {}
// 用户模块
@Module({
imports: [TypeOrmModule.forFeature([User]), AuthModule],
controllers: [UserController],
providers: [UserService, UserResolver],
exports: [UserService],
})
export class UserModule {}
// 产品模块
@Module({
imports: [TypeOrmModule.forFeature([Product]), UploadModule],
controllers: [ProductController],
providers: [ProductService, ProductResolver],
exports: [ProductService],
})
export class ProductModule {}
// 订单模块
@Module({
imports: [
TypeOrmModule.forFeature([Order, OrderItem]),
UserModule,
ProductModule,
PaymentModule,
],
controllers: [OrderController],
providers: [OrderService, OrderResolver],
exports: [OrderService],
})
export class OrderModule {}
// 应用根模块
@Module({
imports: [
// 配置模块
ConfigModule.forRoot({
isGlobal: true,
load: [databaseConfig, appConfig, authConfig],
}),
// 数据库模块
TypeOrmModule.forRootAsync({
imports: [ConfigModule],
useFactory: (config: ConfigService) => ({
type: "postgres",
host: config.get("DATABASE_HOST"),
port: config.get("DATABASE_PORT"),
username: config.get("DATABASE_USERNAME"),
password: config.get("DATABASE_PASSWORD"),
database: config.get("DATABASE_NAME"),
entities: [__dirname + "/**/*.entity{.ts,.js}"],
synchronize: process.env.NODE_ENV !== "production",
}),
inject: [ConfigService],
}),
// 功能模块
CoreModule,
AuthModule,
UserModule,
ProductModule,
OrderModule,
NotificationModule,
// GraphQL模块
GraphQLModule.forRootAsync({
imports: [ConfigModule],
useFactory: async (configService: ConfigService) => ({
autoSchemaFile: true,
sortSchema: true,
playground: configService.get("NODE_ENV") !== "production",
context: ({ req }) => ({ req }),
}),
inject: [ConfigService],
}),
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}NestJS 的核心特性
1. 类型安全的依赖注入
NestJS 提供完全类型安全的 DI 容器:
typescript
// 服务定义
interface ICacheService {
get<T>(key: string): Promise<T | null>;
set<T>(key: string, value: T, ttl?: number): Promise<void>;
del(key: string): Promise<void>;
}
@Injectable()
export class RedisCacheService implements ICacheService {
constructor(
@InjectRedis() private readonly redis: Redis,
private readonly configService: ConfigService
) {}
async get<T>(key: string): Promise<T | null> {
const value = await this.redis.get(key);
return value ? JSON.parse(value) : null;
}
async set<T>(key: string, value: T, ttl?: number): Promise<void> {
const serialized = JSON.stringify(value);
const expiry = ttl || this.configService.get("CACHE_TTL", 3600);
await this.redis.setex(key, expiry, serialized);
}
async del(key: string): Promise<void> {
await this.redis.del(key);
}
}
// 使用抽象接口进行依赖注入
@Injectable()
export class UserService {
constructor(
@InjectRepository(User) private readonly userRepository: Repository<User>,
@Inject("CacheService") private readonly cacheService: ICacheService
) {}
async findById(id: string): Promise<User | null> {
// 1. 先从缓存获取
const cached = await this.cacheService.get<User>(`user:${id}`);
if (cached) return cached;
// 2. 从数据库获取
const user = await this.userRepository.findOne(id);
if (!user) return null;
// 3. 缓存结果
await this.cacheService.set(`user:${id}`, user, 1800);
return user;
}
async invalidateCache(id: string): Promise<void> {
await this.cacheService.del(`user:${id}`);
}
}
// 模块中配置provider
@Module({
providers: [
{
provide: "CacheService",
useClass: RedisCacheService,
},
],
})
export class CacheModule {}2. 请求生命周期管理
NestJS 提供完整的请求生命周期钩子:
typescript
// 全局中间件
@Injectable()
export class RequestLoggerMiddleware implements NestMiddleware {
private readonly logger = new Logger(RequestLoggerMiddleware.name);
use(req: Request, res: Response, next: NextFunction) {
const startTime = Date.now();
const requestId = randomUUID();
// 添加请求ID到请求对象
req["requestId"] = requestId;
// 记录请求开始
this.logger.log(
`开始处理请求: ${req.method} ${req.url}`,
`RequestID: ${requestId}`
);
// 监听响应完成
res.on("finish", () => {
const duration = Date.now() - startTime;
this.logger.log(
`请求完成: ${req.method} ${req.url} - ${res.statusCode}`,
`RequestID: ${requestId} | Duration: ${duration}ms`
);
});
next();
}
}
// 守卫 (Guards)
@Injectable()
export class AuthGuard implements CanActivate {
constructor(
private readonly jwtService: JwtService,
private readonly configService: ConfigService
) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
const request = context.switchToHttp().getRequest();
const token = this.extractTokenFromHeader(request);
if (!token) {
throw new UnauthorizedException("缺少访问令牌");
}
try {
const payload = await this.jwtService.verifyAsync(token, {
secret: this.configService.get("JWT_SECRET"),
});
request["user"] = payload;
} catch {
throw new UnauthorizedException("无效的访问令牌");
}
return true;
}
private extractTokenFromHeader(request: Request): string | undefined {
const [type, token] = request.headers.authorization?.split(" ") ?? [];
return type === "Bearer" ? token : undefined;
}
}
// 拦截器 (Interceptors)
@Injectable()
export class TransformInterceptor<T>
implements NestInterceptor<T, ResponseDto<T>>
{
intercept(
context: ExecutionContext,
next: CallHandler
): Observable<ResponseDto<T>> {
return next.handle().pipe(
map((data) => ({
success: true,
data,
timestamp: new Date().toISOString(),
})),
catchError((error) => {
throw new BadRequestException(error.message || "请求处理失败", error);
})
);
}
}
// 异常过滤器 (Exception Filters)
@Catch()
export class AllExceptionsFilter implements ExceptionFilter {
private readonly logger = new Logger(AllExceptionsFilter.name);
catch(exception: unknown, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse<Response>();
const request = ctx.getRequest<Request>();
let status = HttpStatus.INTERNAL_SERVER_ERROR;
let message = "服务器内部错误";
if (exception instanceof HttpException) {
status = exception.getStatus();
const exceptionResponse = exception.getResponse();
message =
typeof exceptionResponse === "string"
? exceptionResponse
: (exceptionResponse as any).message;
}
this.logger.error(
`${request.method} ${request.url}`,
exception instanceof Error ? exception.stack : exception
);
response.status(status).json({
success: false,
statusCode: status,
message,
timestamp: new Date().toISOString(),
path: request.url,
});
}
}
// 模块中配置执行顺序
@Module({
providers: [
{
provide: APP_FILTER,
useClass: AllExceptionsFilter,
},
{
provide: APP_INTERCEPTOR,
useClass: LoggingInterceptor,
},
{
provide: APP_INTERCEPTOR,
useClass: TransformInterceptor,
},
],
})
export class AppModule {}3. 数据库集成
NestJS 与主要 ORM/ODM 深度集成:
typescript
// TypeORM集成示例
@Module({
imports: [
TypeOrmModule.forRootAsync({
imports: [ConfigModule],
useFactory: (config: ConfigService) => ({
type: "postgres",
host: config.get("DATABASE_HOST"),
port: config.get("DATABASE_PORT"),
username: config.get("DATABASE_USERNAME"),
password: config.get("DATABASE_PASSWORD"),
database: config.get("DATABASE_NAME"),
entities: [User, Product, Order],
synchronize: false,
migrations: ["dist/migrations/*{.ts,.js}"],
logging: config.get("NODE_ENV") === "development",
retryAttempts: 3,
retryDelay: 3000,
}),
inject: [ConfigService],
}),
TypeOrmModule.forFeature([User]),
],
controllers: [UserController],
providers: [UserService],
exports: [UserService],
})
export class UserModule {}
// Mongoose集成示例
@Module({
imports: [
MongooseModule.forRootAsync({
imports: [ConfigModule],
useFactory: (config: ConfigService) => ({
uri: config.get("MONGODB_URI"),
connectionFactory: (connection) => {
connection.plugin(require("mongoose-autopopulate"));
connection.plugin(require("mongoose-lean-defaults"));
return connection;
},
}),
inject: [ConfigService],
}),
MongooseModule.forFeatureAsync([
{
name: Product.name,
useFactory: () => ProductSchema,
},
]),
],
controllers: [ProductController],
providers: [ProductService],
exports: [ProductService],
})
export class ProductModule {}
// Prisma集成示例
@Injectable()
export class UserRepository {
constructor(
private readonly prisma: PrismaService,
private readonly logger: Logger
) {}
async create(createUserDto: CreateUserDto): Promise<User> {
try {
const hashedPassword = await bcrypt.hash(createUserDto.password, 10);
const user = await this.prisma.user.create({
data: {
...createUserDto,
password: hashedPassword,
},
select: {
id: true,
email: true,
name: true,
role: true,
createdAt: true,
},
});
this.logger.log(`用户创建成功: ${user.email}`);
return user;
} catch (error) {
if (error.code === "P2002") {
throw new ConflictException("该邮箱已被使用");
}
throw error;
}
}
async findAll(params: FindAllUserDto): Promise<PaginatedResult<User>> {
const { page = 1, limit = 10, search, role } = params;
const skip = (page - 1) * limit;
const where: Prisma.UserWhereInput = {};
if (search) {
where.OR = [
{ name: { contains: search, mode: "insensitive" } },
{ email: { contains: search, mode: "insensitive" } },
];
}
if (role) {
where.role = role;
}
const [users, total] = await Promise.all([
this.prisma.user.findMany({
where,
select: {
id: true,
email: true,
name: true,
role: true,
createdAt: true,
updatedAt: true,
},
skip,
take: limit,
orderBy: { createdAt: "desc" },
}),
this.prisma.user.count({ where }),
]);
return {
data: users,
meta: {
total,
page,
limit,
totalPages: Math.ceil(total / limit),
},
};
}
}4. WebSocket 支持
NestJS 内置了强大的 WebSocket 支持:
typescript
// WebSocket Gateway
@WebSocketGateway({
cors: {
origin: process.env.FRONTEND_URL,
credentials: true,
},
namespace: "chat",
})
export class ChatGateway
implements OnGatewayInit, OnGatewayConnection, OnGatewayDisconnect
{
@WebSocketServer()
server: Server;
private readonly logger: Logger = new Logger(ChatGateway.name);
private connectedClients: Map<string, WebSocket> = new Map();
constructor(
private readonly chatService: ChatService,
private readonly authService: AuthService
) {}
afterInit(server: Server) {
this.logger.log("WebSocket服务器已初始化");
}
async handleConnection(client: WebSocket, request: Request) {
try {
// WebSocket认证
const token = this.extractTokenFromQuery(request.url);
const user = await this.authService.validateToken(token);
// 注册连接
this.connectedClients.set(user.id, client);
client["user"] = user;
this.logger.log(`用户 ${user.email} 已连接`);
// 发送历史消息
const recentMessages = await this.chatService.getRecentMessages();
client.send(
JSON.stringify({
event: "history",
data: recentMessages,
})
);
} catch (error) {
this.logger.error("WebSocket认证失败:", error);
client.close(1008, "认证失败");
}
}
handleDisconnect(client: WebSocket) {
const user = client["user"];
if (user) {
this.connectedClients.delete(user.id);
this.logger.log(`用户 ${user.email} 已断开连接`);
// 广播用户离线
this.broadcast(user.id, {
event: "user-left",
data: { userId: user.id, userName: user.name },
excludeSender: true,
});
}
}
@SubscribeMessage("sendMessage")
async handleMessage(
client: WebSocket,
payload: SendMessageDto
): Promise<void> {
const user = client["user"];
try {
// 验证消息内容
await this.validateMessage(payload);
// 保存消息到数据库
const message = await this.chatService.saveMessage({
content: payload.content,
senderId: user.id,
type: payload.type || "text",
});
// 广播消息给所有用户
this.broadcast(user.id, {
event: "newMessage",
data: {
id: message.id,
content: message.content,
sender: {
id: user.id,
name: user.name,
avatar: user.avatar,
},
timestamp: message.createdAt,
type: message.type,
},
});
} catch (error) {
this.logger.error("消息处理失败:", error);
client.send(
JSON.stringify({
event: "error",
data: { message: "消息发送失败" },
})
);
}
}
@SubscribeMessage("typing")
handleTyping(client: WebSocket, payload: { isTyping: boolean }): void {
const user = client["user"];
this.broadcast(user.id, {
event: "userTyping",
data: {
userId: user.id,
userName: user.name,
isTyping: payload.isTyping,
},
excludeSender: true,
});
}
private broadcast(
senderId: string,
message: { event: string; data: any },
options: { excludeSender?: boolean } = {}
): void {
this.connectedClients.forEach((client, userId) => {
if (options.excludeSender && userId === senderId) {
return;
}
if (client.readyState === WebSocket.OPEN) {
client.send(JSON.stringify(message));
}
});
}
private extractTokenFromQuery(url: string): string | undefined {
const query = new URLSearchParams(url.split("?")[1] || "");
return query.get("token");
}
private async validateMessage(payload: SendMessageDto): Promise<void> {
if (!payload.content || payload.content.trim().length === 0) {
throw new Error("消息内容不能为空");
}
if (payload.content.length > 1000) {
throw new Error("消息内容过长");
}
}
}
// 消息DTO
export class SendMessageDto {
@IsNotEmpty()
@IsLength({ max: 1000 })
content: string;
@IsOptional()
@IsEnum(["text", "image", "file"])
type?: "text" | "image" | "file";
@IsOptional()
@IsString()
@IsUrl()
attachmentUrl?: string;
}
// 聊天服务
@Injectable()
export class ChatService {
constructor(
@InjectRepository(Message)
private readonly messageRepository: Repository<Message>
) {}
async saveMessage(createMessageDto: CreateMessageDto): Promise<Message> {
const message = this.messageRepository.create(createMessageDto);
return await this.messageRepository.save(message);
}
async getRecentMessages(limit = 50): Promise<Message[]> {
return await this.messageRepository.find({
relations: ["sender"],
order: { createdAt: "DESC" },
take: limit,
select: {
id: true,
content: true,
type: true,
createdAt: true,
sender: {
id: true,
name: true,
avatar: true,
},
},
});
}
}NestJS 的优势与应用场景
NestJS 的核心优势
企业级架构:
- 强制使用分层架构
- 模块化设计
- 依赖注入提高可测试性
类型安全:
- 原生 TypeScript 支持
- 编译时类型检查
- 智能代码提示
丰富的生态系统:
- 内置 WebSocket 支持
- GraphQL 开箱即用
- 微服务架构支持
开发体验:
- 热重载
- 丰富的 CLI 工具
- 完整的测试支持
适用场景
NestJS 特别适合以下类型的项目:
- 企业级 Web 应用:需要严格架构和可维护性
- 微服务架构:模块化设计天然支持微服务
- GraphQL API:内置支持,类型安全的 GraphQL 开发
- 实时应用:WebSocket 支持和事件驱动架构
- 大型团队协作:标准化的代码结构和开发流程
总结
NestJS 通过结合现代软件架构模式和 TypeScript 的类型安全,为 Node.js 应用开发提供了企业级的解决方案。它不仅提高了代码的可维护性和可测试性,还通过丰富的功能和优秀的开发体验,让后端开发变得更加高效和愉快。
NestJS 的核心价值
- 架构一致性:强制遵循最佳实践,降低技术债务
- 类型安全:TypeScript 原生支持,减少运行时错误
- 模块化设计:清晰的模块边界,便于团队协作
- 丰富的功能:WebSocket、GraphQL、微服务等开箱即用
- 优秀的开发体验:CLI 工具、热重载、测试支持
掌握 NestJS 不仅能让你构建高质量的企业级应用,还能让你深入理解现代软件架构设计的精髓。在构建大型、复杂的后端应用时,NestJS 是一个非常值得选择的框架。