引言
NestJS 是 Node.js 生态中最受欢迎的企业级后端框架之一。它借鉴了 Angular 的模块化架构,融合了面向对象编程、函数式编程和响应式编程的思想。TypeScript 的一等支持、依赖注入、装饰器语法让 NestJS 的代码结构清晰、可测试性强。本文从零开始,带你构建一个包含 CRUD、ORM、认证和 API 文档的 NestJS 应用。
一、项目初始化
# 安装 NestJS CLI
npm install -g @nestjs/cli
# 创建项目
nest new my-api
cd my-api
# 启动开发服务器
npm run start:dev
二、Module / Controller / Service 三层架构
NestJS 的核心架构分为三层:
- Module(模块):组织代码的基本单元,管理依赖注入
- Controller(控制器):处理 HTTP 请求,定义路由
- Service(服务):业务逻辑层,被 Controller 调用
2.1 创建用户模块
# 使用 CLI 快速生成
nest generate module users
nest generate controller users
nest generate service users
2.2 定义 Controller
// users.controller.ts
import {
Controller, Get, Post, Put, Delete,
Body, Param, ParseIntPipe, Query,
} from '@nestjs/common';
import { UsersService } from './users.service';
import { CreateUserDto, UpdateUserDto } from './dto';
@Controller('users')
export class UsersController {
constructor(private readonly usersService: UsersService) {}
@Get()
findAll(@Query('page') page = 1, @Query('limit') limit = 10) {
return this.usersService.findAll({ page, limit });
}
@Get(':id')
findOne(@Param('id', ParseIntPipe) id: number) {
return this.usersService.findOne(id);
}
@Post()
create(@Body() createUserDto: CreateUserDto) {
return this.usersService.create(createUserDto);
}
@Put(':id')
update(
@Param('id', ParseIntPipe) id: number,
@Body() updateUserDto: UpdateUserDto,
) {
return this.usersService.update(id, updateUserDto);
}
@Delete(':id')
remove(@Param('id', ParseIntPipe) id: number) {
return this.usersService.remove(id);
}
}
2.3 定义 Service
// users.service.ts
import { Injectable, NotFoundException } from '@nestjs/common';
import { PrismaService } from '../prisma/prisma.service';
import { CreateUserDto, UpdateUserDto } from './dto';
@Injectable()
export class UsersService {
constructor(private prisma: PrismaService) {}
async findAll({ page, limit }: { page: number; limit: number }) {
const skip = (page - 1) * limit;
const [data, total] = await Promise.all([
this.prisma.user.findMany({ skip, take: limit }),
this.prisma.user.count(),
]);
return { data, total, page, limit };
}
async findOne(id: number) {
const user = await this.prisma.user.findUnique({ where: { id } });
if (!user) {
throw new NotFoundException(`User with ID ${id} not found`);
}
return user;
}
async create(dto: CreateUserDto) {
return this.prisma.user.create({ data: dto });
}
async update(id: number, dto: UpdateUserDto) {
await this.findOne(id); // 确保存在
return this.prisma.user.update({ where: { id }, data: dto });
}
async remove(id: number) {
await this.findOne(id);
return this.prisma.user.delete({ where: { id } });
}
}
三、依赖注入
NestJS 的依赖注入(DI)容器自动管理类的实例化和依赖关系。
// users.module.ts
import { Module } from '@nestjs/common';
import { PrismaModule } from '../prisma/prisma.module';
import { UsersController } from './users.controller';
import { UsersService } from './users.service';
@Module({
imports: [PrismaModule], // 导入其他模块
controllers: [UsersController],
providers: [UsersService], // 注册为提供者(可被注入)
exports: [UsersService], // 导出给其他模块使用
})
export class UsersModule {}
四、数据验证(class-validator)
// dto/create-user.dto.ts
import {
IsEmail, IsString, MinLength, MaxLength,
IsOptional, IsEnum,
} from 'class-validator';
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
export enum UserRole {
ADMIN = 'admin',
USER = 'user',
EDITOR = 'editor',
}
export class CreateUserDto {
@ApiProperty({ description: '用户邮箱', example: 'user@example.com' })
@IsEmail()
email: string;
@ApiProperty({ description: '用户名', example: 'john_doe' })
@IsString()
@MinLength(3)
@MaxLength(50)
username: string;
@ApiProperty({ description: '密码', minLength: 8 })
@IsString()
@MinLength(8)
password: string;
@ApiPropertyOptional({ enum: UserRole, default: UserRole.USER })
@IsOptional()
@IsEnum(UserRole)
role?: UserRole;
}
// main.ts 中启用全局验证管道
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);
}
五、Prisma ORM 集成
# 安装 Prisma
npm install @prisma/client
npm install -D prisma
# 初始化 Prisma
npx prisma init
// prisma/schema.prisma
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
model User {
id Int @id @default(autoincrement())
email String @unique
username String @unique
password String
role String @default("user")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
@@map("users")
}
// prisma/prisma.service.ts
import { Injectable, OnModuleInit, OnModuleDestroy } from '@nestjs/common';
import { PrismaClient } from '@prisma/client';
@Injectable()
export class PrismaService
extends PrismaClient
implements OnModuleInit, OnModuleDestroy
{
async onModuleInit() {
await this.$connect();
}
async onModuleDestroy() {
await this.$disconnect();
}
}
六、JWT 认证(Passport)
npm install @nestjs/passport @nestjs/jwt passport passport-jwt
npm install -D @types/passport-jwt
// auth/auth.module.ts
import { Module } from '@nestjs/common';
import { JwtModule } from '@nestjs/jwt';
import { PassportModule } from '@nestjs/passport';
import { UsersModule } from '../users/users.module';
import { AuthService } from './auth.service';
import { AuthController } from './auth.controller';
import { JwtStrategy } from './jwt.strategy';
@Module({
imports: [
UsersModule,
PassportModule,
JwtModule.register({
secret: process.env.JWT_SECRET || 'secret-key',
signOptions: { expiresIn: '7d' },
}),
],
controllers: [AuthController],
providers: [AuthService, JwtStrategy],
})
export class AuthModule {}
// auth/jwt.strategy.ts
import { Injectable, UnauthorizedException } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { ExtractJwt, Strategy } from 'passport-jwt';
import { UsersService } from '../users/users.service';
@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor(private usersService: UsersService) {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
ignoreExpiration: false,
secretOrKey: process.env.JWT_SECRET || 'secret-key',
});
}
async validate(payload: { sub: number; email: string }) {
const user = await this.usersService.findOne(payload.sub);
if (!user) {
throw new UnauthorizedException();
}
return user;
}
}
// 使用 JWT 守卫保护路由
import { UseGuards } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
@Controller('users')
export class UsersController {
@Get('profile')
@UseGuards(AuthGuard('jwt'))
getProfile(@Request() req) {
return req.user;
}
}
七、Swagger API 文档
npm install @nestjs/swagger
// main.ts
import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
const config = new DocumentBuilder()
.setTitle('My API')
.setDescription('API 文档')
.setVersion('1.0')
.addBearerAuth()
.build();
const document = SwaggerModule.createDocument(app, config);
SwaggerModule.setup('api/docs', app, document);
await app.listen(3000);
}
启动服务后访问 http://localhost:3000/api/docs 即可看到交互式 API 文档。
八、测试(Jest)
// users.service.spec.ts
import { Test, TestingModule } from '@nestjs/testing';
import { UsersService } from './users.service';
import { PrismaService } from '../prisma/prisma.service';
describe('UsersService', () => {
let service: UsersService;
let prisma: PrismaService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
UsersService,
{
provide: PrismaService,
useValue: {
user: {
findMany: jest.fn(),
findUnique: jest.fn(),
create: jest.fn(),
update: jest.fn(),
delete: jest.fn(),
count: jest.fn(),
},
},
},
],
}).compile();
service = module.get(UsersService);
prisma = module.get(PrismaService);
});
it('should be defined', () => {
expect(service).toBeDefined();
});
it('should return paginated users', async () => {
const mockUsers = [{ id: 1, email: 'test@test.com', username: 'test' }];
(prisma.user.findMany as jest.Mock).mockResolvedValue(mockUsers);
(prisma.user.count as jest.Mock).mockResolvedValue(1);
const result = await service.findAll({ page: 1, limit: 10 });
expect(result.data).toEqual(mockUsers);
expect(result.total).toBe(1);
});
});
# 运行测试
npm run test # 单元测试
npm run test:e2e # 端到端测试
npm run test:cov # 测试覆盖率
总结
NestJS 的最大优势是架构清晰。Module 组织代码,Controller 处理请求,Service 实现逻辑,Provider 管理依赖,Guard 控制权限,Pipe 验证数据,Interceptor 拦截响应,Filter 处理异常。这种分层设计让团队协作时每个人的职责边界清晰,代码的可维护性和可测试性都远超 Express 风格的松散组织。对于中大型项目,NestJS 是目前 Node.js 生态中最值得投入学习的框架。
返回文章列表
标签:NestJSTypeScript后端