Node~使用NestJS开发后端服务
一、NestJS 简介
NestJS 是一个用于构建高效、可扩展的 Node.js 服务器端应用程序的框架。它使用渐进式 JavaScript,内置并完全支持 TypeScript,并结合了 OOP(面向对象编程)、FP(函数式编程)和 FRP(函数式响应编程)的元素。
核心特性
- 🚀 基于 TypeScript 构建
- 🏗️ 模块化架构设计
- 💉 依赖注入(DI)
- 🛡️ 内置守卫、拦截器、管道等
- 🔌 丰富的生态系统
- 📦 开箱即用的装饰器
二、项目架构
nest-demo/
├── src/
│ ├── common/ # 公共模块
│ │ ├── decorators/ # 自定义装饰器
│ │ ├── filters/ # 异常过滤器
│ │ ├── guards/ # 守卫
│ │ ├── interceptors/ # 拦截器
│ │ ├── pipes/ # 管道
│ │ └── dto/ # 公共 DTO
│ ├── config/ # 配置文件
│ │ └── database.config.ts
│ ├── modules/ # 业务模块
│ │ ├── user/ # 用户模块
│ │ │ ├── entities/ # 实体
│ │ │ ├── dto/ # 数据传输对象
│ │ │ ├── user.controller.ts
│ │ │ ├── user.service.ts
│ │ │ └── user.module.ts
│ │ └── auth/ # 认证模块
│ ├── app.controller.ts
│ ├── app.service.ts
│ ├── app.module.ts
│ └── main.ts
├── test/
├── .env # 环境变量
├── .env.example
├── nest-cli.json
├── package.json
├── tsconfig.json
└── README.md
三、项目初始化
1. 安装 NestJS CLI
npm i -g @nestjs/cli
2. 创建新项目
nest new nest-demo
cd nest-demo
3. 安装必要依赖
# 数据库相关
npm install @nestjs/typeorm typeorm mysql2
# 或者使用 PostgreSQL
# npm install @nestjs/typeorm typeorm pg
# 配置管理
npm install @nestjs/config
# 验证
npm install class-validator class-transformer
# JWT 认证
npm install @nestjs/jwt @nestjs/passport passport passport-jwt
npm install -D @types/passport-jwt
# 加密
npm install bcrypt
npm install -D @types/bcrypt
四、环境配置
1. 创建 .env 文件
# 服务器配置
PORT=3000
NODE_ENV=development
# 数据库配置
DB_TYPE=mysql
DB_HOST=localhost
DB_PORT=3306
DB_USERNAME=root
DB_PASSWORD=your_password
DB_DATABASE=nest_demo
# JWT 配置
JWT_SECRET=your-super-secret-key
JWT_EXPIRES_IN=7d
2. 配置文件 src/config/database.config.ts
import { registerAs } from '@nestjs/config';
import { TypeOrmModuleOptions } from '@nestjs/typeorm';
export default registerAs('database', (): TypeOrmModuleOptions => ({
type: process.env.DB_TYPE as any,
host: process.env.DB_HOST,
port: parseInt(process.env.DB_PORT, 10),
username: process.env.DB_USERNAME,
password: process.env.DB_PASSWORD,
database: process.env.DB_DATABASE,
entities: [__dirname + '/../**/*.entity{.ts,.js}'],
synchronize: process.env.NODE_ENV === 'development', // 生产环境禁用
logging: process.env.NODE_ENV === development',
}));
五、主模块配置 app.module.ts
import { Module } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { TypeOrmModule } from '@nestjs/typeorm';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import databaseConfig from './config/database.config';
import { UserModule } from './modules/user/user.module';
import { AuthModule } from './modules/auth/auth.module';
@Module({
imports: [
// 配置模块
ConfigModule.forRoot({
isGlobal: true,
load: [databaseConfig],
envFilePath: '.env',
}),
// 数据库模块
TypeOrmModule.forRootAsync({
imports: [ConfigModule],
useFactory: (configService: ConfigService) =>
configService.get('database'),
inject: [ConfigService],
}),
// 业务模块
UserModule,
AuthModule,
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
六、用户模块实现
1. 用户实体 modules/user/entities/user.entity.ts
import {
Entity,
Column,
PrimaryGeneratedColumn,
CreateDateColumn,
UpdateDateColumn,
} from 'typeorm';
import { Exclude } from 'class-transformer';
@Entity('users')
export class User {
@PrimaryGeneratedColumn()
id: number;
@Column({ unique: true, length: 50 })
username: string;
@Column({ unique: true, length: 100 })
email: string;
@Column()
@Exclude() // 序列化时排除密码
password: string;
@Column({ nullable: true, length: 50 })
nickname: string;
@Column({ default: true })
isActive: boolean;
@CreateDateColumn()
createdAt: Date;
@UpdateDateColumn()
updatedAt: Date;
}
2. DTO 数据传输对象
modules/user/dto/create-user.dto.ts
import { IsEmail, IsNotEmpty, IsString, MinLength, MaxLength } from 'class-validator';
export class CreateUserDto {
@IsNotEmpty({ message: '用户名不能为空' })
@IsString()
@MinLength(3, { message: '用户名至少3个字符' })
@MaxLength(20, { message: '用户名最多20个字符' })
username: string;
@IsNotEmpty({ message: '邮箱不能为空' })
@IsEmail({}, { message: '邮箱格式不正确' })
email: string;
@IsNotEmpty({ message: '密码不能为空' })
@IsString()
@MinLength(6, { message: '密码至少6个字符' })
password: string;
@IsString()
nickname?: string;
}
modules/user/dto/update-user.dto.ts
import { PartialType } from '@nestjs/mapped-types';
import { CreateUserDto } from './create-user.dto';
import { IsBoolean, IsOptional } from 'class-validator';
export class UpdateUserDto extends PartialType(CreateUserDto) {
@IsOptional()
@IsBoolean()
isActive?: boolean;
}
3. 用户服务 modules/user/user.service.ts
import { Injectable, ConflictException, NotFoundException } 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';
import { UpdateUserDto } from './dto/update-user.dto';
import * as bcrypt from 'bcrypt';
@Injectable()
export class UserService {
constructor(
@InjectRepository(User)
private readonly userRepository: Repository<User>,
) {}
async create(createUserDto: CreateUserDto): Promise<User> {
// 检查用户是否已存在
const existUser = await this.userRepository.findOne({
where: [
{ username: createUserDto.username },
{ email: createUserDto.email },
],
});
if (existUser) {
throw new ConflictException('用户名或邮箱已存在');
}
// 加密密码
const hashedPassword = await bcrypt.hash(createUserDto.password, 10);
// 创建用户
const user = this.userRepository.create({
...createUserDto,
password: hashedPassword,
});
return await this.userRepository.save(user);
}
async findAll(): Promise<User[]> {
return await this.userRepository.find({
select: ['id', 'username', 'email', 'nickname', 'isActive', 'createdAt'],
});
}
async findOne(id: number): Promise<User> {
const user = await this.userRepository.findOne({ where: { id } });
if (!user) {
throw new NotFoundException(`用户 #${id} 不存在`);
}
return user;
}
async findByUsername(username: string): Promise<User | undefined> {
return await this.userRepository.findOne({ where: { username } });
}
async update(id: number, updateUserDto: UpdateUserDto): Promise<User> {
const user = await this.findOne(id);
// 如果更新密码,需要加密
if (updateUserDto.password) {
updateUserDto.password = await bcrypt.hash(updateUserDto.password, 10);
}
Object.assign(user, updateUserDto);
return await this.userRepository.save(user);
}
async remove(id: number): Promise<void> {
const user = await this.findOne(id);
await this.userRepository.remove(user);
}
}
4. 用户控制器 modules/user/user.controller.ts
import {
Controller,
Get,
Post,
Body,
Patch,
Param,
Delete,
UseGuards,
UseInterceptors,
ClassSerializerInterceptor,
ParseIntPipe,
} from '@nestjs/common';
import { UserService } from './user.service';
import { CreateUserDto } from './dto/create-user.dto';
import { UpdateUserDto } from './dto/update-user.dto';
import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard';
@Controller('users')
@UseInterceptors(ClassSerializerInterceptor) // 自动排除 @Exclude() 字段
export class UserController {
constructor(private readonly userService: UserService) {}
@Post()
create(@Body() createUserDto: CreateUserDto) {
return this.userService.create(createUserDto);
}
@Get()
@UseGuards(JwtAuthGuard) // 需要认证
findAll() {
return this.userService.findAll();
}
@Get(':id')
@UseGuards(JwtAuthGuard)
findOne(@Param('id', ParseIntPipe) id: number) {
return this.userService.findOne(id);
}
@Patch(':id')
@UseGuards(JwtAuthGuard)
update(
@Param('id', ParseIntPipe) id: number,
@Body() updateUserDto: UpdateUserDto,
) {
return this.userService.update(id, updateUserDto);
}
@Delete(':id')
@UseGuards(JwtAuthGuard)
remove(@Param('id', ParseIntPipe) id: number) {
return this.userService.remove(id);
}
}
5. 用户模块 modules/user/user.module.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { UserService } from './user.service';
import { UserController } from './user.controller';
import { User } from './entities/user.entity';
@Module({
imports: [TypeOrmModule.forFeature([User])],
controllers: [UserController],
providers: [UserService],
exports: [UserService], // 导出供其他模块使用
})
export class UserModule {}
七、认证模块实现
1. JWT 策略 modules/auth/strategies/jwt.strategy.ts
import { Injectable, UnauthorizedException } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { ExtractJwt, Strategy } from 'passport-jwt';
import { ConfigService } from '@nestjs/config';
@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor(private configService: ConfigService) {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
ignoreExpiration: false,
secretOrKey: configService.get<string>('JWT_SECRET'),
});
}
async validate(payload: any) {
if (!payload.sub || !payload.username) {
throw new UnauthorizedException();
}
return { userId: payload.sub, username: payload.username };
}
}
2. JWT 守卫 modules/auth/guards/jwt-auth.guard.ts
import { Injectable, ExecutionContext } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
import { Observable } from 'rxjs';
@Injectable()
export class JwtAuthGuard extends AuthGuard('jwt') {
canActivate(context: ExecutionContext): boolean | Promise<boolean> | Observable<boolean> {
return super.canActivate(context);
}
}
3. 认证服务 modules/auth/auth.service.ts
import { Injectable, UnauthorizedException } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { UserService } from '../user/user.service';
import * as bcrypt from 'bcrypt';
@Injectable()
export class AuthService {
constructor(
private userService: UserService,
private jwtService: JwtService,
) {}
async validateUser(username: string, password: string): Promise<any> {
const user = await this.userService.findByUsername(username);
if (!user) {
throw new UnauthorizedException('用户名或密码错误');
}
const isPasswordValid = await bcrypt.compare(password, user.password);
if (!isPasswordValid) {
throw new UnauthorizedException('用户名或密码错误');
}
if (!user.isActive) {
throw new UnauthorizedException('账号已被禁用');
}
const { password: _, ...result } = user;
return result;
}
async login(username: string, password: string) {
const user = await this.validateUser(username, password);
const payload = { username: user.username, sub: user.id };
return {
access_token: this.jwtService.sign(payload),
user,
};
}
}
4. 认证控制器 modules/auth/auth.controller.ts
import { Controller, Post, Body } from '@nestjs/common';
import { AuthService } from './auth.service';
import { IsNotEmpty, IsString } from 'class-validator';
class LoginDto {
@IsNotEmpty()
@IsString()
username: string;
@IsNotEmpty()
@IsString()
password: string;
}
@Controller('auth')
export class AuthController {
constructor(private authService: AuthService) {}
@Post('login')
async login(@Body() loginDto: LoginDto) {
return this.authService.login(loginDto.username, loginDto.password);
}
}
5. 认证模块 modules/auth/auth.module.ts
import { Module } from '@nestjs/common';
import { JwtModule } from '@nestjs/jwt';
import { PassportModule } from '@nestjs/passport';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { AuthService } from './auth.service';
import { AuthController } from './auth.controller';
import { JwtStrategy } from './strategies/jwt.strategy';
import { UserModule } from '../user/user.module';
@Module({
imports: [
UserModule,
PassportModule,
JwtModule.registerAsync({
imports: [ConfigModule],
useFactory: async (configService: ConfigService) => ({
secret: configService.get<string>('JWT_SECRET'),
signOptions: {
expiresIn: configService.get<string>('JWT_EXPIRES_IN'),
},
}),
inject: [ConfigService],
}),
],
controllers: [AuthController],
providers: [AuthService, JwtStrategy],
})
export class AuthModule {}
八、全局异常过滤器
common/filters/http-exception.filter.ts
import {
ExceptionFilter,
Catch,
ArgumentsHost,
HttpException,
HttpStatus,
} 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();
const exceptionResponse = exception.getResponse();
const message =
typeof exceptionResponse === 'string'
? exceptionResponse
: (exceptionResponse as any).message || '服务器错误';
response.status(status).json({
statusCode: status,
timestamp: new Date().toISOString(),
path: request.url,
message,
});
}
}
九、全局响应拦截器
common/interceptors/transform.interceptor.ts
import {
Injectable,
NestInterceptor,
ExecutionContext,
CallHandler,
} from '@nestjs/common';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
export interface Response<T> {
code: number;
data: T;
message: 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) => ({
code: 0,
data,
message: 'success',
})),
);
}
}
十、启动配置 main.ts
import { NestFactory } from '@nestjs/core';
import { ValidationPipe } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { AppModule } from './app.module';
import { HttpExceptionFilter } from './common/filters/http-exception.filter';
import { TransformInterceptor } from './common/interceptors/transform.interceptor';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
// 全局前缀
app.setGlobalPrefix('api/v1');
// 全局验证管道
app.useGlobalPipes(
new ValidationPipe({
whitelist: true, // 自动过滤非 DTO 定义的属性
transform: true, // 自动转换类型
forbidNonWhitelisted: true, // 禁止非白名单属性
transformOptions: {
enableImplicitConversion: true,
},
}),
);
// 全局异常过滤器
app.useGlobalFilters(new HttpExceptionFilter());
// 全局响应拦截器
app.useGlobalInterceptors(new TransformInterceptor());
// CORS 配置
app.enableCors({
origin: ['http://localhost:3001', 'https://yourdomain.com'],
credentials: true,
});
const configService = app.get(ConfigService);
const port = configService.get<number>('PORT') || 3000;
await app.listen(port);
console.log(`🚀 Application is running on: http://localhost:${port}`);
console.log(`📚 API Docs: http://localhost:${port}/api/v1`);
}
bootstrap();
十一、API 测试
1. 注册用户
POST http://localhost:3000/api/v1/users
Content-Type: application/json
{
"username": "testuser",
"email": "test@example.com",
"password": "123456",
"nickname": "测试用户"
}
2. 登录
POST http://localhost:3000/api/v1/auth/login
Content-Type: application/json
{
"username": "testuser",
"password": "123456"
}
响应:
{
"code": 0,
"data": {
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"user": {
"id": 1,
"username": "testuser",
"email": "test@example.com",
"nickname": "测试用户"
}
},
"message": "success"
}
3. 获取用户列表(需要认证)
GET http://localhost:3000/api/v1/users
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
十二、运行项目
# 开发模式
npm run start:dev
# 生产模式
npm run build
npm run start:prod
# 调试模式
npm run start:debug
十三、项目优化建议
1. 添加 Swagger 文档
npm install @nestjs/swagger swagger-ui-express
// main.ts
import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
const config = new DocumentBuilder()
.setTitle('NestJS API')
.setDescription('The NestJS API description')
.setVersion('1.0')
.addBearerAuth()
.build();
const document = SwaggerModule.createDocument(app, config);
SwaggerModule.setup('api-docs', app, document);
2. 添加日志系统
npm install nest-winston winston
3. 添加缓存
npm install @nestjs/cache-manager cache-manager
4. 添加限流
npm install @nestjs/throttler
5. 数据库迁移
npm install typeorm-extension
npx typeorm migration:create ./src/migrations/InitialMigration
npx typeorm migration:run
十四、总结
本文介绍了使用 NestJS 构建后端服务的完整流程,包括:
✅ 项目架构设计
✅ 数据库连接(TypeORM + MySQL)
✅ 用户模块 CRUD 实现
✅ JWT 认证系统
✅ 全局异常处理
✅ 数据验证与转换
✅ 响应格式统一
NestJS 提供了强大的模块化架构和依赖注入系统,使得代码更加清晰、可维护。结合 TypeScript 的类型系统,可以大大提高开发效率和代码质量。