1. 为什么选择NestJS作为企业级Node.js框架
第一次接触NestJS时,我就被它清晰的架构设计所吸引。作为一个长期使用Express和Koa的老手,我深知传统Node.js框架在构建大型应用时的痛点——随着业务复杂度上升,代码很容易变成意大利面条式的混乱结构。NestJS通过模块化设计和依赖注入,完美解决了这个问题。
2017年首次发布的NestJS,借鉴了Angular的架构思想,但专门为服务端应用优化。它底层兼容Express和Fastify,这意味着你可以继续使用熟悉的HTTP处理方式,同时享受更高级的架构支持。根据2023年的开发者调查报告,NestJS已经成为企业级Node.js开发的首选框架,在需要长期维护的中大型项目中尤其受欢迎。
提示:如果你是从Express迁移过来的开发者,NestJS的学习曲线会非常平缓。它没有完全抛弃你熟悉的中间件概念,而是用更结构化的方式重新组织了这些概念。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 项目初始化与环境配置
2.1 安装Node.js与Nest CLI
在开始之前,确保你的系统已经安装了Node.js LTS版本(当前推荐18.x)。我建议使用nvm管理Node版本,这在需要切换不同项目环境时特别有用:
bash复制curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.3/install.sh | bash
nvm install --lts
nvm use --lts
接下来全局安装Nest CLI工具,这是官方推荐的脚手架:
bash复制npm install -g @nestjs/cli
2.2 创建新项目
使用CLI初始化项目比手动搭建要高效得多,它能自动生成标准的项目结构和基础配置文件:
bash复制nest new my-nest-project
创建完成后,你会看到一个精心设计的目录结构:
code复制src/
├── app.controller.ts
├── app.module.ts
├── app.service.ts
└── main.ts
这个结构已经体现了NestJS的核心概念:模块(Module)、控制器(Controller)和服务(Service)。我们稍后会详细解析每个部分。
3. 核心概念深度解析
3.1 模块(Module):应用的骨架
模块是NestJS组织代码的基本单元。每个NestJS应用至少有一个根模块(通常是AppModule),你可以把它想象成一栋大楼的承重结构。打开自动生成的app.module.ts,你会看到类似这样的代码:
typescript复制@Module({
imports: [],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
@Module装饰器接收的配置对象有三个关键属性:
- imports:声明本模块依赖的其他模块
- controllers:注册本模块包含的控制器
- providers:注册本模块提供的服务
在实际项目中,我们会根据业务领域划分模块。比如一个电商系统可能有:
- UserModule:用户管理
- ProductModule:商品管理
- OrderModule:订单处理
这种模块化设计使得代码更易于维护和测试。我建议每个模块都有自己的目录,包含相关的控制器、服务、实体等文件。
3.2 控制器(Controller):处理HTTP请求
控制器负责处理传入的请求并返回响应。它们相当于传统MVC模式中的"Controller"。让我们看看自动生成的app.controller.ts:
typescript复制@Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
@Get()
getHello(): string {
return this.appService.getHello();
}
}
几个关键点:
- @Controller()装饰器标记这是一个控制器类
- 构造函数中注入了AppService(依赖注入的体现)
- @Get()装饰器定义了一个GET路由处理程序
你可以轻松添加更多路由:
typescript复制@Controller('users')
export class UsersController {
@Get()
findAll(): string {
return 'All users';
}
@Get(':id')
findOne(@Param('id') id: string): string {
return `User ${id}`;
}
@Post()
create(@Body() createUserDto: any): string {
return 'User created';
}
}
注意:在实际项目中,应该为请求体创建明确的DTO(数据传输对象)类,而不是使用any类型。这能提供更好的类型安全和文档支持。
3.3 服务(Service):业务逻辑的家
服务是存放业务逻辑的地方,遵循单一职责原则。自动生成的app.service.ts非常简单:
typescript复制@Injectable()
export class AppService {
getHello(): string {
return 'Hello World!';
}
}
@Injectable()装饰器表明这个类可以被NestJS的依赖注入系统管理。在实际项目中,服务可能包含数据库操作、外部API调用、复杂计算等逻辑。
一个更真实的用户服务可能长这样:
typescript复制@Injectable()
export class UsersService {
private readonly users: User[] = [];
create(user: CreateUserDto): User {
const newUser = { id: Date.now().toString(), ...user };
this.users.push(newUser);
return newUser;
}
findAll(): User[] {
return this.users;
}
findOne(id: string): User {
return this.users.find(user => user.id === id);
}
}
4. 实战:构建一个完整的REST API
4.1 创建用户模块
让我们通过一个完整的用户管理API来实践这些概念。首先用CLI生成模块:
bash复制nest generate module users
nest generate controller users
nest generate service users
这会创建users目录和相应的文件。现在你的目录结构应该是:
code复制src/
├── users/
│ ├── users.module.ts
│ ├── users.controller.ts
│ └── users.service.ts
└── ...
4.2 定义用户实体和DTO
创建src/users/entities/user.entity.ts定义用户实体:
typescript复制export class User {
id: string;
username: string;
email: string;
password: string;
}
创建src/users/dto/create-user.dto.ts定义创建用户的DTO:
typescript复制export class CreateUserDto {
username: string;
email: string;
password: string;
}
4.3 完善用户服务
更新users.service.ts:
typescript复制import { Injectable } from '@nestjs/common';
import { User } from './entities/user.entity';
import { CreateUserDto } from './dto/create-user.dto';
@Injectable()
export class UsersService {
private readonly users: User[] = [];
create(createUserDto: CreateUserDto): User {
const user: User = {
id: Date.now().toString(),
...createUserDto,
};
this.users.push(user);
return user;
}
findAll(): User[] {
return this.users;
}
findOne(id: string): User {
return this.users.find(user => user.id === id);
}
}
4.4 实现控制器方法
更新users.controller.ts:
typescript复制import { Controller, Get, Post, Body, Param } from '@nestjs/common';
import { UsersService } from './users.service';
import { CreateUserDto } from './dto/create-user.dto';
import { User } from './entities/user.entity';
@Controller('users')
export class UsersController {
constructor(private readonly usersService: UsersService) {}
@Post()
create(@Body() createUserDto: CreateUserDto): User {
return this.usersService.create(createUserDto);
}
@Get()
findAll(): User[] {
return this.usersService.findAll();
}
@Get(':id')
findOne(@Param('id') id: string): User {
return this.usersService.findOne(id);
}
}
4.5 测试API
启动开发服务器:
bash复制npm run start:dev
现在你可以用Postman或curl测试这些端点:
- POST /users - 创建用户
- GET /users - 获取所有用户
- GET /users/:id - 获取特定用户
5. 进阶技巧与最佳实践
5.1 使用类验证器
安装class-validator和class-transformer来验证输入数据:
bash复制npm install class-validator class-transformer
更新create-user.dto.ts:
typescript复制import { IsEmail, IsString, MinLength } from 'class-validator';
export class CreateUserDto {
@IsString()
@MinLength(3)
username: string;
@IsEmail()
email: string;
@IsString()
@MinLength(8)
password: string;
}
在main.ts中启用全局验证管道:
typescript复制import { ValidationPipe } from '@nestjs/common';
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalPipes(new ValidationPipe());
await app.listen(3000);
}
bootstrap();
现在如果发送无效数据,API会自动返回400错误和详细的验证信息。
5.2 异常处理
NestJS提供了丰富的HTTP异常类。例如,当用户不存在时:
typescript复制import { NotFoundException } from '@nestjs/common';
// 在UsersService中
findOne(id: string): User {
const user = this.users.find(user => user.id === id);
if (!user) {
throw new NotFoundException(`User with ID ${id} not found`);
}
return user;
}
5.3 使用拦截器格式化响应
创建统一的响应格式:
typescript复制import { CallHandler, ExecutionContext, Injectable, NestInterceptor } from '@nestjs/common';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
export interface Response<T> {
data: T;
}
@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 })));
}
}
在main.ts中注册全局拦截器:
typescript复制app.useGlobalInterceptors(new TransformInterceptor());
现在所有响应都会自动包装在data字段中。
6. 数据库集成
6.1 安装TypeORM
NestJS与TypeORM集成非常好:
bash复制npm install @nestjs/typeorm typeorm mysql2
6.2 配置数据库连接
更新app.module.ts:
typescript复制import { TypeOrmModule } from '@nestjs/typeorm';
@Module({
imports: [
TypeOrmModule.forRoot({
type: 'mysql',
host: 'localhost',
port: 3306,
username: 'root',
password: 'password',
database: 'nest_test',
entities: [User],
synchronize: true, // 开发环境使用,生产环境应该关闭
}),
UsersModule,
],
})
export class AppModule {}
6.3 创建用户实体
更新user.entity.ts:
typescript复制import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number;
@Column()
username: string;
@Column()
email: string;
@Column()
password: string;
}
6.4 更新用户服务
users.service.ts现在可以使用TypeORM:
typescript复制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>,
) {}
create(createUserDto: CreateUserDto): Promise<User> {
const user = this.usersRepository.create(createUserDto);
return this.usersRepository.save(user);
}
findAll(): Promise<User[]> {
return this.usersRepository.find();
}
findOne(id: number): Promise<User> {
return this.usersRepository.findOne({ where: { id } });
}
}
记得更新控制器中的类型提示,将id从string改为number。
7. 认证与授权
7.1 安装必要依赖
bash复制npm install @nestjs/passport passport passport-local
npm install @nestjs/jwt passport-jwt
npm install bcrypt
npm install -D @types/passport-local @types/passport-jwt @types/bcrypt
7.2 实现密码哈希
更新user.entity.ts:
typescript复制import { BeforeInsert, Column } from 'typeorm';
import * as bcrypt from 'bcrypt';
@Entity()
export class User {
// ...其他列
@BeforeInsert()
async hashPassword() {
this.password = await bcrypt.hash(this.password, 10);
}
}
7.3 创建认证模块
bash复制nest generate module auth
nest generate service auth
实现auth.service.ts:
typescript复制import { Injectable, UnauthorizedException } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { UsersService } from '../users/users.service';
import * as bcrypt from 'bcrypt';
@Injectable()
export class AuthService {
constructor(
private usersService: UsersService,
private jwtService: JwtService,
) {}
async validateUser(username: string, pass: string): Promise<any> {
const user = await this.usersService.findOneByUsername(username);
if (user && (await bcrypt.compare(pass, user.password))) {
const { password, ...result } = user;
return result;
}
return null;
}
async login(user: any) {
const payload = { username: user.username, sub: user.id };
return {
access_token: this.jwtService.sign(payload),
};
}
}
7.4 实现本地策略
创建auth/local.strategy.ts:
typescript复制import { Strategy } from 'passport-local';
import { PassportStrategy } from '@nestjs/passport';
import { Injectable, UnauthorizedException } from '@nestjs/common';
import { AuthService } from './auth.service';
@Injectable()
export class LocalStrategy extends PassportStrategy(Strategy) {
constructor(private authService: AuthService) {
super();
}
async validate(username: string, password: string): Promise<any> {
const user = await this.authService.validateUser(username, password);
if (!user) {
throw new UnauthorizedException();
}
return user;
}
}
7.5 创建登录端点
创建auth.controller.ts:
typescript复制import { Controller, Post, Request, UseGuards } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
import { AuthService } from './auth.service';
@Controller('auth')
export class AuthController {
constructor(private authService: AuthService) {}
@UseGuards(AuthGuard('local'))
@Post('login')
async login(@Request() req) {
return this.authService.login(req.user);
}
}
7.6 保护路由
创建JWT策略auth/jwt.strategy.ts:
typescript复制import { ExtractJwt, Strategy } from 'passport-jwt';
import { PassportStrategy } from '@nestjs/passport';
import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor(configService: ConfigService) {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
ignoreExpiration: false,
secretOrKey: configService.get('JWT_SECRET'),
});
}
async validate(payload: any) {
return { userId: payload.sub, username: payload.username };
}
}
现在你可以在需要保护的控制器方法上使用@UseGuards(AuthGuard('jwt'))装饰器。
8. 测试与部署
8.1 单元测试
NestJS内置了Jest支持。测试一个服务:
typescript复制import { Test, TestingModule } from '@nestjs/testing';
import { UsersService } from './users.service';
describe('UsersService', () => {
let service: UsersService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [UsersService],
}).compile();
service = module.get<UsersService>(UsersService);
});
it('should be defined', () => {
expect(service).toBeDefined();
});
it('should create a user', () => {
const user = service.create({
username: 'test',
email: 'test@example.com',
password: 'password',
});
expect(user).toHaveProperty('id');
expect(user.username).toBe('test');
});
});
8.2 E2E测试
测试整个应用:
typescript复制import { Test, TestingModule } from '@nestjs/testing';
import { INestApplication } from '@nestjs/common';
import * as request from 'supertest';
import { AppModule } from './../src/app.module';
describe('AppController (e2e)', () => {
let app: INestApplication;
beforeEach(async () => {
const moduleFixture: TestingModule = await Test.createTestingModule({
imports: [AppModule],
}).compile();
app = moduleFixture.createNestApplication();
await app.init();
});
it('/ (GET)', () => {
return request(app.getHttpServer())
.get('/')
.expect(200)
.expect('Hello World!');
});
});
8.3 生产部署
对于生产环境,你应该:
- 关闭TypeORM的synchronize,使用迁移管理数据库结构
- 设置环境变量(使用@nestjs/config模块)
- 启用HTTPS
- 使用PM2或Docker管理进程
一个简单的Dockerfile示例:
dockerfile复制FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install --production
COPY . .
RUN npm run build
EXPOSE 3000
CMD ["node", "dist/main"]
9. 项目结构与代码组织建议
经过多个NestJS项目实践,我总结出以下目录结构最佳实践:
code复制src/
├── common/ # 共享模块、工具、装饰器等
├── config/ # 配置文件
├── database/ # 数据库实体和迁移
├── modules/ # 业务模块
│ ├── auth/ # 认证模块
│ ├── users/ # 用户模块
│ └── ... # 其他模块
├── main.ts # 应用入口
└── app.module.ts # 根模块
每个业务模块内部结构:
code复制users/
├── dto/ # 数据传输对象
├── entities/ # 数据库实体
├── interfaces/ # 类型接口
├── users.controller.ts
├── users.module.ts
├── users.service.ts
└── users.repository.ts # 可选,复杂项目可以单独抽象仓库层
这种结构保持了良好的关注点分离,随着项目规模扩大依然能保持清晰。
