NestJS项目从零到一
NestJS项目从零到一
目录
- 项目初始化与脚手架
- 项目结构规划
- 数据库连接配置
- DTO数据验证与封装
- 统一返回结果封装
- JWT认证与授权
- 接口文档生成
- CRUD接口实现示例
- 文件上传功能
- 大文件分片上传
- 路由守卫与拦截器
- 日志系统
- 环境配置管理
- 部署与生产环境
1. 项目初始化与脚手架
1.1 安装NestJS CLI
1
| npm install -g @nestjs/cli
|
1.2 创建新项目
使用NestJS CLI创建项目:
1 2
| nest new nest-serve cd nest-serve
|
项目创建选项:
创建项目时会询问包管理器选择:
1 2 3 4
| ? Which package manager would you ❤️ to use? ❯ npm yarn pnpm
|
项目结构预览:
创建完成后会自动生成标准项目结构:
1 2 3 4 5 6 7 8 9 10 11 12
| nest-serve/ ├── src/ │ ├── main.ts # 应用入口 │ ├── app.module.ts # 根模块 │ ├── app.controller.ts # 根控制器 │ └── app.service.ts # 根服务 ├── test/ # 测试文件 ├── .eslintrc.js # ESLint配置 ├── .prettierrc # Prettier配置 ├── tsconfig.json # TypeScript配置 ├── package.json # 项目依赖 └── README.md # 项目说明
|
验证项目创建成功:
1.3 项目结构说明
1 2 3 4 5 6 7 8 9
| nest-serve/ ├── src/ │ ├── main.ts # 应用入口文件 │ ├── app.module.ts # 根模块 │ ├── app.controller.ts # 根控制器 │ └── app.service.ts # 根服务 ├── test/ # 测试文件目录 ├── package.json └── tsconfig.json
|
1.4 移除测试模块(可选)
如果不需要测试功能,可以删除test目录和相关的测试依赖:
在package.json中移除测试相关依赖:
1 2 3 4 5 6 7 8 9 10
| { "devDependencies": { } }
|
2. 项目结构规划
2.1 推荐的模块化结构
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| src/ ├── common/ # 公共模块 │ ├── decorator/ # 装饰器 │ ├── dto/ # 数据传输对象 │ ├── entities/ # 实体类 │ ├── filter/ # 异常过滤器 │ ├── guards/ # 守卫 │ ├── interceptor/ # 拦截器 │ ├── pipeTransform/ # 管道 │ ├── service/ # 公共服务 │ └── utils/ # 工具函数 ├── config/ # 配置文件 ├── module/ # 业务模块 │ ├── auth/ # 认证模块 │ ├── users/ # 用户模块 │ ├── upload/ # 文件上传模块 │ └── ... # 其他业务模块 └── types/ # TypeScript类型定义
|
2.2 创建模块结构
1 2 3 4 5
| mkdir -p src/common/{decorator,dto,entities,filter,guards,interceptor,pipeTransform,service,utils} mkdir -p src/config mkdir -p src/module/{auth,users,upload} mkdir -p src/types
|
3. 数据库连接配置
3.1 安装数据库依赖
1 2
| npm install @nestjs/typeorm typeorm mysql2 sqlite3 npm install @nestjs/config
|
3.2 配置多数据库连接
创建数据库配置文件 src/config/db.ts:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| import { join } from "path";
const env = process.env;
export interface MysqlConfig { DB_TYPE: string; DB_HOST: string; DB_PORT: number; DB_USER: string; DB_PASSWORD: string; DB_DATABASE: string; }
export interface SqliteConfig { DB_TYPE: string; DB_DATABASE: string; }
const mysqlConfig: MysqlConfig = { DB_TYPE: "mysql", DB_HOST: env.DB_HOST || "127.0.0.1", DB_PORT: Number(env.DB_PORT) || 3306, DB_USER: env.DB_USER || "root", DB_PASSWORD: env.DB_PASSWORD || "123456", DB_DATABASE: env.DB_DATABASE || "nest-serve", };
const sqliteConfig: SqliteConfig = { DB_TYPE: "sqlite", DB_DATABASE: join(process.cwd(), env.DB_DATABASE || "sqlitedata/nest.db"), };
const DB_TYPE = env.DB_TYPE; const dbConfig = DB_TYPE === "sqlite" ? sqliteConfig : mysqlConfig;
export default { dbConfig };
|
3.3 在主模块中配置数据库
src/app.module.ts:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
| import { Module } from "@nestjs/common"; import { ConfigModule, ConfigService } from "@nestjs/config"; import { TypeOrmModule } from "@nestjs/typeorm"; import { join } from "path"; import DBConfig from "./config/db";
@Module({ imports: [ ConfigModule.forRoot({ isGlobal: true, envFilePath: `.env.${process.env.NODE_ENV || "dev"}`, }), TypeOrmModule.forRootAsync({ imports: [ConfigModule], useFactory: (configService: ConfigService) => { const dbType = DBConfig.dbConfig.DB_TYPE as "mysql" | "sqlite"; if (dbType === "sqlite") { return { type: dbType, database: DBConfig.dbConfig.DB_DATABASE, synchronize: process.env.NODE_ENV !== "production", logging: process.env.NODE_ENV !== "production", entities: [join(__dirname, "module/**/*.entity.{ts,js}")], }; } else { return { type: dbType, host: DBConfig.dbConfig.DB_HOST, port: DBConfig.dbConfig.DB_PORT, username: DBConfig.dbConfig.DB_USER, password: DBConfig.dbConfig.DB_PASSWORD, database: DBConfig.dbConfig.DB_DATABASE, synchronize: process.env.NODE_ENV !== "production", logging: process.env.NODE_ENV !== "production", entities: [join(__dirname, "module/**/*.entity.{ts,js}")], }; } }, inject: [ConfigService], }), ], controllers: [], providers: [], }) export class AppModule {}
|
3.4 环境配置文件
创建 .env.dev:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| # 数据库配置 DB_TYPE=mysql DB_HOST=localhost DB_PORT=3306 DB_USER=root DB_PASSWORD=123456 DB_DATABASE=nest-serve
# JWT配置 JWT_SECRET=your-secret-key JWT_EXPIRES_IN=7d
# 应用配置 PORT=3000 NODE_ENV=dev
|
4. DTO数据验证与封装
4.1 安装验证依赖
1
| npm install class-validator class-transformer
|
4.2 创建基础DTO
src/common/dto/base.dto.ts:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| import { IsOptional, IsNumber, Min } from 'class-validator'; import { Type } from 'class-transformer';
export class PaginationDto { @IsOptional() @Type(() => Number) @IsNumber() @Min(1) page?: number = 1;
@IsOptional() @Type(() => Number) @IsNumber() @Min(1) pageSize?: number = 10; }
export class IdDto { @IsNumber() @Min(1) id: number; }
|
4.3 用户模块DTO示例
src/module/users/dto/create-user.dto.ts:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| import { IsString, IsEmail, IsNotEmpty, MinLength, MaxLength } from 'class-validator';
export class CreateUserDto { @IsString() @IsNotEmpty({ message: '用户名不能为空' }) @MinLength(2, { message: '用户名至少2个字符' }) @MaxLength(20, { message: '用户名最多20个字符' }) username: string;
@IsEmail({}, { message: '邮箱格式不正确' }) @IsNotEmpty({ message: '邮箱不能为空' }) email: string;
@IsString() @IsNotEmpty({ message: '密码不能为空' }) @MinLength(6, { message: '密码至少6个字符' }) password: string; }
|
src/module/users/dto/update-user.dto.ts:
1 2 3 4
| import { PartialType } from '@nestjs/mapped-types'; import { CreateUserDto } from './create-user.dto';
export class UpdateUserDto extends PartialType(CreateUserDto) {}
|
5. 统一返回结果封装
5.1 创建响应接口
src/common/types/public.ts:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| export interface ResponseData<T = any> { code: number; message: string; data?: T; success: boolean; }
export interface PaginationData<T = any> { list: T[]; total: number; page: number; pageSize: number; totalPages: number; }
export class ResponseResult { static success<T>(data?: T, message = '操作成功'): ResponseData<T> { return { code: 200, message, data, success: true, }; }
static pagination<T>(list: T[], total: number, page: number, pageSize: number): ResponseData<PaginationData<T>> { return this.success({ list, total, page, pageSize, totalPages: Math.ceil(total / pageSize), }); }
static error(message = '操作失败', code = 500): ResponseData { return { code, message, success: false, }; } }
|
5.2 创建统一响应拦截器
src/common/interceptor/response.interceptor.ts:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common'; import { Observable } from 'rxjs'; import { map } from 'rxjs/operators'; import { ResponseData } from '../types/public';
@Injectable() export class ResponseInterceptor<T> implements NestInterceptor<T, ResponseData<T>> { intercept(context: ExecutionContext, next: CallHandler): Observable<ResponseData<T>> { return next.handle().pipe( map((data) => { if (data && typeof data === 'object' && 'code' in data && 'success' in data) { return data; } return { code: 200, message: '操作成功', data: data || null, success: true, }; }), ); } }
|
6. JWT认证与授权
6.1 安装JWT依赖
1 2
| npm install @nestjs/jwt bcrypt npm install @types/bcrypt -D
|
6.2 创建JWT配置
src/config/jwt.ts:
1 2 3 4 5 6
| import { registerAs } from '@nestjs/config';
export default registerAs('jwt', () => ({ secret: process.env.JWT_SECRET || 'your-secret-key', expiresIn: process.env.JWT_EXPIRES_IN || '7d', }));
|
6.3 创建认证守卫
src/common/guards/auth.guard.ts:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| import { Injectable, CanActivate, ExecutionContext, UnauthorizedException } from '@nestjs/common'; import { JwtService } from '@nestjs/jwt'; import { Request } from 'express';
@Injectable() export class AuthGuard implements CanActivate { constructor(private jwtService: JwtService) {}
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); 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; } }
|
6.4 创建角色守卫
src/common/guards/roles.guard.ts:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common'; import { Reflector } from '@nestjs/core';
@Injectable() export class RolesGuard implements CanActivate { constructor(private reflector: Reflector) {}
canActivate(context: ExecutionContext): boolean { const requiredRoles = this.reflector.get<string[]>('roles', context.getHandler()); if (!requiredRoles) { return true; } const { user } = context.switchToHttp().getRequest(); return requiredRoles.some((role) => user.roles?.includes(role)); } }
|
6.5 创建角色装饰器
src/common/decorator/roles.decorator.ts:
1 2 3 4
| import { SetMetadata } from '@nestjs/common';
export const ROLES_KEY = 'roles'; export const Roles = (...roles: string[]) => SetMetadata(ROLES_KEY, roles);
|
7. 接口文档生成
7.1 安装Swagger依赖
1
| npm install @nestjs/swagger swagger-ui-express
|
7.2 配置Swagger
src/main.ts:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| import { NestFactory } from '@nestjs/core'; import { ValidationPipe } from '@nestjs/common'; import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger'; import { AppModule } from './app.module';
async function bootstrap() { const app = await NestFactory.create(AppModule);
app.useGlobalPipes(new ValidationPipe());
const config = new DocumentBuilder() .setTitle('NestJS API文档') .setDescription('NestJS项目API接口文档') .setVersion('1.0') .addBearerAuth() .build(); const document = SwaggerModule.createDocument(app, config); SwaggerModule.setup('api', app, document);
await app.listen(process.env.PORT || 3000); } bootstrap();
|
7.3 在控制器中使用Swagger装饰器
src/module/users/users.controller.ts:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| import { Controller, Get, Post, Body, Patch, Param, Delete, UseGuards } from '@nestjs/common'; import { ApiTags, ApiOperation, ApiBearerAuth } from '@nestjs/swagger'; import { UsersService } from './users.service'; import { CreateUserDto } from './dto/create-user.dto'; import { UpdateUserDto } from './dto/update-user.dto'; import { AuthGuard } from '../../common/guards/auth.guard';
@ApiTags('用户管理') @Controller('users') @UseGuards(AuthGuard) @ApiBearerAuth() export class UsersController { constructor(private readonly usersService: UsersService) {}
@Post() @ApiOperation({ summary: '创建用户' }) create(@Body() createUserDto: CreateUserDto) { return this.usersService.create(createUserDto); }
@Get() @ApiOperation({ summary: '获取用户列表' }) findAll() { return this.usersService.findAll(); }
@Get(':id') @ApiOperation({ summary: '获取用户详情' }) findOne(@Param('id') id: string) { return this.usersService.findOne(+id); }
@Patch(':id') @ApiOperation({ summary: '更新用户信息' }) update(@Param('id') id: string, @Body() updateUserDto: UpdateUserDto) { return this.usersService.update(+id, updateUserDto); }
@Delete(':id') @ApiOperation({ summary: '删除用户' }) remove(@Param('id') id: string) { return this.usersService.remove(+id); } }
|
8. CRUD接口实现示例
8.1 创建用户实体
src/module/users/entities/user.entity.ts:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| import { Entity, Column, PrimaryGeneratedColumn, CreateDateColumn, UpdateDateColumn } from 'typeorm';
@Entity('users') export class User { @PrimaryGeneratedColumn() id: number;
@Column({ unique: true }) username: string;
@Column({ unique: true }) email: string;
@Column() password: string;
@Column({ default: 'user' }) role: string;
@Column({ default: true }) isActive: boolean;
@CreateDateColumn() createdAt: Date;
@UpdateDateColumn() updatedAt: Date; }
|
8.2 创建用户服务
src/module/users/users.service.ts:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
| import { Injectable, NotFoundException } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; import * as bcrypt from 'bcrypt'; import { User } from './entities/user.entity'; import { CreateUserDto } from './dto/create-user.dto'; import { UpdateUserDto } from './dto/update-user.dto';
@Injectable() export class UsersService { constructor( @InjectRepository(User) private usersRepository: Repository<User>, ) {}
async create(createUserDto: CreateUserDto): Promise<User> { const hashedPassword = await bcrypt.hash(createUserDto.password, 10); const user = this.usersRepository.create({ ...createUserDto, password: hashedPassword, }); return this.usersRepository.save(user); }
async findAll(): Promise<User[]> { return this.usersRepository.find(); }
async findOne(id: number): Promise<User> { const user = await this.usersRepository.findOne({ where: { id } }); if (!user) { throw new NotFoundException(`用户 #${id} 不存在`); } return user; }
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 this.usersRepository.save(user); }
async remove(id: number): Promise<void> { const user = await this.findOne(id); await this.usersRepository.remove(user); }
async findByUsername(username: string): Promise<User | undefined> { return this.usersRepository.findOne({ where: { username } }); } }
|
8.3 使用NestJS CLI快速生成CRUD模块
NestJS CLI提供了强大的资源生成命令,可以快速创建完整的CRUD模块:
1 2
| nest g resource module/users --no-spec
|
这个命令会自动生成以下文件:
1 2 3 4 5 6 7 8 9
| src/module/users/ ├── users.controller.ts # 控制器 ├── users.service.ts # 服务 ├── entities/ │ └── user.entity.ts # 实体类 ├── dto/ │ ├── create-user.dto.ts # 创建DTO │ └── update-user.dto.ts # 更新DTO └── users.module.ts # 模块文件
|
命令参数说明:
resource: 生成完整的资源模块
module/users: 指定模块路径和名称
--no-spec: 不生成测试文件(如果不需要测试)
生成的文件结构:
src/module/users/users.module.ts:
1 2 3 4 5 6 7 8 9 10 11 12 13
| import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { UsersService } from './users.service'; import { UsersController } from './users.controller'; import { User } from './entities/user.entity';
@Module({ imports: [TypeOrmModule.forFeature([User])], controllers: [UsersController], providers: [UsersService], exports: [UsersService], }) export class UsersModule {}
|
其他有用的CLI命令:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| nest g controller module/users/users --no-spec
nest g service module/users/users --no-spec
nest g module module/users
nest g class module/users/entities/user.entity --no-spec
nest g class module/users/dto/create-user.dto --no-spec
|
优势:
- 快速开发:一键生成完整模块结构
- 标准化:遵循NestJS最佳实践
- 一致性:所有模块结构统一
- 可维护性:便于团队协作和代码维护
注意事项:
- 生成后需要根据实际需求调整实体字段
- 可能需要添加额外的验证规则
- 根据业务逻辑完善服务层方法
9. 文件上传功能
9.1 安装文件上传依赖
1
| npm install multer @types/multer
|
9.2 创建文件上传配置
src/config/multer.ts:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| import { diskStorage } from 'multer'; import { extname } from 'path'; import { v4 as uuidv4 } from 'uuid';
export const multerConfig = { storage: diskStorage({ destination: './uploads', filename: (req, file, callback) => { const uniqueName = `${uuidv4()}${extname(file.originalname)}`; callback(null, uniqueName); }, }), fileFilter: (req, file, callback) => { const allowedMimes = [ 'image/jpeg', 'image/png', 'image/gif', 'application/pdf', 'application/msword', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', ]; if (allowedMimes.includes(file.mimetype)) { callback(null, true); } else { callback(new Error('不支持的文件类型'), false); } }, limits: { fileSize: 10 * 1024 * 1024, }, };
|
9.3 创建文件上传模块
src/module/upload/upload.controller.ts:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| import { Controller, Post, UseInterceptors, UploadedFile, HttpException, HttpStatus } from '@nestjs/common'; import { FileInterceptor } from '@nestjs/platform-express'; import { ApiTags, ApiOperation, ApiConsumes, ApiBody } from '@nestjs/swagger'; import { multerConfig } from '../../config/multer';
@ApiTags('文件上传') @Controller('upload') export class UploadController { @Post('file') @ApiOperation({ summary: '单文件上传' }) @ApiConsumes('multipart/form-data') @ApiBody({ schema: { type: 'object', properties: { file: { type: 'string', format: 'binary', }, }, }, }) @UseInterceptors(FileInterceptor('file', multerConfig)) uploadFile(@UploadedFile() file: Express.Multer.File) { if (!file) { throw new HttpException('文件上传失败', HttpStatus.BAD_REQUEST); } return { filename: file.filename, originalname: file.originalname, size: file.size, mimetype: file.mimetype, path: `/uploads/${file.filename}`, }; } }
|
10. 大文件分片上传
10.1 安装大文件上传依赖
1
| npm install tus-node-server
|
10.2 创建大文件上传服务
src/module/upload/large-file.service.ts:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| import { Injectable } from '@nestjs/common'; import { Server } from 'tus-node-server'; import { join } from 'path';
@Injectable() export class LargeFileService { private tusServer: Server;
constructor() { this.tusServer = new Server({ path: '/files', datastore: new tus.FileSystemStore({ directory: join(process.cwd(), 'uploads/large-files'), }), }); }
getServer(): Server { return this.tusServer; } }
|
10.3 创建大文件上传控制器
src/module/upload/large-file.controller.ts:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| import { Controller, Post, Req, Res } from '@nestjs/common'; import { Request, Response } from 'express'; import { ApiTags, ApiOperation } from '@nestjs/swagger'; import { LargeFileService } from './large-file.service';
@ApiTags('大文件上传') @Controller('upload/large') export class LargeFileController { constructor(private readonly largeFileService: LargeFileService) {}
@Post('*') @ApiOperation({ summary: '大文件分片上传' }) async handleUpload(@Req() req: Request, @Res() res: Response) { return new Promise<void>((resolve, reject) => { this.largeFileService.getServer().handle(req, res, (error) => { if (error) { reject(error); } else { resolve(); } }); }); } }
|
11. 路由守卫与拦截器
11.1 日志拦截器
src/common/interceptor/logger.interceptor.ts:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common'; import { Observable } from 'rxjs'; import { tap } from 'rxjs/operators';
@Injectable() export class LoggingInterceptor implements NestInterceptor { intercept(context: ExecutionContext, next: CallHandler): Observable<any> { const request = context.switchToHttp().getRequest(); const { method, url, ip, headers } = request; const userAgent = headers['user-agent'] || ''; const now = Date.now(); return next.handle().pipe( tap(() => { const response = context.switchToHttp().getResponse(); const delay = Date.now() - now; console.log(`${method} ${url} ${response.statusCode} ${delay}ms - ${ip} ${userAgent}`); }), ); } }
|
11.2 超时拦截器
src/common/interceptor/timeout.interceptor.ts:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| import { Injectable, NestInterceptor, ExecutionContext, CallHandler, RequestTimeoutException } from '@nestjs/common'; import { Observable, throwError, TimeoutError } from 'rxjs'; import { catchError, timeout } from 'rxjs/operators';
@Injectable() export class TimeoutInterceptor implements NestInterceptor { intercept(context: ExecutionContext, next: CallHandler): Observable<any> { return next.handle().pipe( timeout(10000), catchError(err => { if (err instanceof TimeoutError) { return throwError(() => new RequestTimeoutException()); } return throwError(() => err); }), ); } }
|
12. 日志系统
12.1 安装日志依赖
1
| npm install nest-winston winston winston-daily-rotate-file
|
12.2 创建日志配置
src/config/logger.ts:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| import { utilities as nestWinstonModuleUtilities } from 'nest-winston'; import * as winston from 'winston'; import 'winston-daily-rotate-file';
export const winstonConfig = { transports: [ new winston.transports.Console({ format: winston.format.combine( winston.format.timestamp(), winston.format.ms(), nestWinstonModuleUtilities.format.nestLike('NestJS', { colors: true, prettyPrint: true, }), ), }), new winston.transports.DailyRotateFile({ filename: 'logs/application-%DATE%.log', datePattern: 'YYYY-MM-DD', zippedArchive: true, maxSize: '20m', maxFiles: '14d', format: winston.format.combine( winston.format.timestamp(), winston.format.json(), ), }), ], };
|
13. 环境配置管理
13.1 创建环境配置
.env.production:
1 2 3 4 5 6 7 8 9 10 11 12 13
| NODE_ENV=production PORT=3000
# 生产环境数据库 DB_TYPE=mysql DB_HOST=production-db-host DB_PORT=3306 DB_USER=prod_user DB_PASSWORD=prod_password DB_DATABASE=prod_nest_serve
JWT_SECRET=production-secret-key JWT_EXPIRES_IN=1d
|
.env.test:
1 2 3 4 5 6 7 8 9
| NODE_ENV=test PORT=3001
# 测试环境数据库 DB_TYPE=sqlite DB_DATABASE=test-data/test.db
JWT_SECRET=test-secret-key JWT_EXPIRES_IN=1h
|
14. 部署与生产环境
14.1 Docker配置
创建 Dockerfile:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
RUN npm run build
EXPOSE 3000
CMD ["npm", "run", "start:prod"]
|
14.2 Docker Compose配置
创建 docker-compose.yml:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| version: '3.8'
services: app: build: . ports: - "3000:3000" environment: - NODE_ENV=production depends_on: - db restart: unless-stopped
db: image: mysql:8.0 environment: MYSQL_ROOT_PASSWORD: root_password MYSQL_DATABASE: nest_serve MYSQL_USER: app_user MYSQL_PASSWORD: app_password ports: - "3306:3306" volumes: - db_data:/var/lib/mysql restart: unless-stopped
volumes: db_data:
|
14.3 生产环境启动脚本
在 package.json 中添加:
1 2 3 4 5 6
| { "scripts": { "start:prod": "cross-env NODE_ENV=production node dist/main", "build:prod": "npm run build && npm prune --production" } }
|
总结
本教程详细介绍了NestJS项目从零到一的完整开发流程,涵盖了:
- 项目初始化 - 脚手架创建和项目结构规划
- 数据库配置 - 多数据库连接和实体定义
- 数据验证 - DTO封装和验证规则
- 认证授权 - JWT实现和角色权限控制
- 接口文档 - Swagger自动生成API文档
- 文件处理 - 普通上传和大文件分片上传
- 中间件 - 拦截器、守卫、过滤器的使用
- 日志系统 - 结构化日志记录和日志轮转
- 环境配置 - 多环境配置管理
- 部署运维 - Docker容器化部署
学习建议
- 循序渐进:按照教程顺序逐步学习,不要跳跃
- 动手实践:每个章节都要实际编写代码并运行测试
- 理解原理:不仅要会使用,还要理解背后的设计思想
- 查阅文档:遇到问题时查阅NestJS官方文档
- 代码规范:养成良好的代码编写习惯
常见问题解决
Q: 数据库连接失败
A: 检查数据库服务是否启动,配置信息是否正确
Q: JWT令牌无效
A: 检查令牌是否过期,密钥配置是否正确
Q: 文件上传失败
A: 检查上传目录权限,文件大小限制
Q: Swagger文档无法访问
A: 检查Swagger配置路径是否正确
扩展学习
完成本教程后,可以进一步学习:
- 微服务架构 - 使用NestJS构建微服务
- GraphQL - 替代REST API的查询语言
- WebSocket - 实时通信功能
- 性能优化 - 缓存、数据库优化等
- 测试驱动开发 - 单元测试和集成测试
资源推荐
结语
本教程通过实际项目案例,系统性地讲解了NestJS的核心概念和最佳实践。通过跟随教程学习,你将能够:
✅ 掌握NestJS框架的核心概念和使用方法
✅ 理解模块化开发的思想和实践
✅ 学会数据库连接和ORM的使用
✅ 掌握认证授权和安全防护
✅ 学会API文档的自动生成
✅ 掌握文件上传和大文件处理
✅ 了解生产环境部署和运维
希望本教程能够帮助你快速上手NestJS开发,构建高质量的Node.js应用!
Happy Coding! 🚀