基础项目结构(适合小型项目):
│
nest-project/
├── src/
│ ├── main.ts # 应用入口
│ ├── app.module.ts # 根模块
│ ├── app.controller.ts # 根控制器
│ ├── app.controller.spec.ts # 测试文件
│ ├── app.service.ts # 根服务
│ └── app.service.spec.ts # 测试文件
├── test/ # E2E 测试
├── nest-cli.json # CLI 配置
├── tsconfig.json # TS 配置
└── package.json # 依赖管理
特点:
- 结构简单,适合入门
- CLI 自动生成
- 单模块应用
适用场景:
- 小型项目
- 学习阶段
- 单体应用微服务项目结构(适合中大型项目):
│
nest-microservice/
├── apps/ # 微服务应用
│ ├── user-service/ # 用户服务
│ │ ├── src/
│ │ │ ├── main.ts
│ │ │ ├── user.module.ts
│ │ │ ├── user.controller.ts
│ │ │ ├── user.service.ts
│ │ │ ├── user.entity.ts # 实体类
│ │ │ └── user.dto.ts # DTO
│ │ └── test/
│ │
│ ├── order-service/ # 订单服务
│ │ ├── src/
│ │ │ ├── main.ts
│ │ │ ├── order.module.ts
│ │ │ ├── order.controller.ts
│ │ │ ├── order.service.ts
│ │ │ ├── order.entity.ts
│ │ │ └── order.dto.ts
│ │ └── test/
│ │
│ └── api-gateway/ # API 网关
│ ├── src/
│ └── test/
│
├── libs/ # 共享库
│ ├── common/ # 公共模块
│ │ ├── src/
│ │ │ ├── decorators/ # 装饰器
│ │ │ ├── filters/ # 过滤器
│ │ │ ├── guards/ # 守卫
│ │ │ ├── interceptors/ # 拦截器
│ │ │ ├── pipes/ # 管道
│ │ │ └── interfaces/ # 接口定义
│ │ └── index.ts
│ │
│ └── shared/ # 共享模块
│ ├── src/
│ │ ├── database/ # 数据库配置
│ │ ├── config/ # 配置
│ │ └── utils/ # 工具函数
│ └── index.ts
│
├── nest-cli.json
├── tsconfig.json
└── package.json
特点:
- 微服务架构
- 多服务独立部署
- 共享库复用
- 按服务划分
适用场景:
- 中大型项目
- 微服务架构
- 团队协作企业级项目结构(适合复杂业务):
│
nest-enterprise/
├── src/
│ ├── main.ts # 应用入口
│ ├── app.module.ts # 根模块
│ │
│ ├── common/ # 公共模块
│ │ ├── decorators/ # 装饰器
│ │ │ ├── current-user.decorator.ts
│ │ │ ├── is-unique.decorator.ts
│ │ │ └── index.ts
│ │ │
│ │ ├── filters/ # 过滤器
│ │ │ ├── http-exception.filter.ts
│ │ │ ├── all-exceptions.filter.ts
│ │ │ └── index.ts
│ │ │
│ │ ├── guards/ # 守卫
│ │ │ ├── jwt-auth.guard.ts
│ │ │ ├── roles.guard.ts
│ │ │ └── index.ts
│ │ │
│ │ ├── interceptors/ # 拦截器
│ │ │ ├── logging.interceptor.ts
│ │ │ ├── transform.interceptor.ts
│ │ │ └── index.ts
│ │ │
│ │ ├── pipes/ # 管道
│ │ │ ├── validation.pipe.ts
│ │ │ └── index.ts
│ │ │
│ │ ├── dto/ # 公共 DTO
│ │ │ ├── pagination.dto.ts
│ │ │ └── index.ts
│ │ │
│ │ ├── interfaces/ # 接口定义
│ │ │ ├── user.interface.ts
│ │ │ └── index.ts
│ │ │
│ │ ├── entities/ # 基础实体
│ │ │ ├── base.entity.ts
│ │ │ └── index.ts
│ │ │
│ │ ├── exceptions/ # 自定义异常
│ │ │ ├── business.exception.ts
│ │ │ └── index.ts
│ │ │
│ │ ├── constants/ # 常量定义
│ │ │ ├── error-code.constant.ts
│ │ │ └── index.ts
│ │ │
│ │ └── utils/ # 工具函数
│ │ ├── date.util.ts
│ │ ├── string.util.ts
│ │ └── index.ts
│ │
│ ├── config/ # 配置模块
│ │ ├── app.config.ts # 应用配置
│ │ ├── database.config.ts # 数据库配置
│ │ ├── jwt.config.ts # JWT 配置
│ │ └── index.ts
│ │
│ ├── modules/ # 业务模块(推荐)
│ │ ├── auth/ # 认证模块
│ │ │ ├── dto/
│ │ │ │ ├── login.dto.ts
│ │ │ │ ├── register.dto.ts
│ │ │ │ └── index.ts
│ │ │ ├── auth.controller.ts
│ │ │ ├── auth.service.ts
│ │ │ ├── auth.module.ts
│ │ │ ├── auth.strategy.ts # JWT 策略
│ │ │ └── auth.spec.ts
│ │ │
│ │ ├── users/ # 用户模块
│ │ │ ├── dto/
│ │ │ │ ├── create-user.dto.ts
│ │ │ │ ├── update-user.dto.ts
│ │ │ │ └── index.ts
│ │ │ ├── entities/
│ │ │ │ ├── user.entity.ts
│ │ │ │ └── index.ts
│ │ │ ├── users.controller.ts
│ │ │ ├── users.service.ts
│ │ │ ├── users.module.ts
│ │ │ └── users.spec.ts
│ │ │
│ │ ├── posts/ # 文章模块
│ │ │ ├── dto/
│ │ │ ├── entities/
│ │ │ ├── posts.controller.ts
│ │ │ ├── posts.service.ts
│ │ │ ├── posts.module.ts
│ │ │ └── posts.spec.ts
│ │ │
│ │ ├── comments/ # 评论模块
│ │ │ ├── dto/
│ │ │ ├── entities/
│ │ │ ├── comments.controller.ts
│ │ │ ├── comments.service.ts
│ │ │ ├── comments.module.ts
│ │ │ └── comments.spec.ts
│ │ │
│ │ └── categories/ # 分类模块
│ │ ├── dto/
│ │ ├── entities/
│ │ ├── categories.controller.ts
│ │ ├── categories.service.ts
│ │ ├── categories.module.ts
│ │ └── categories.spec.ts
│ │
│ ├── database/ # 数据库配置
│ │ ├── migrations/ # 迁移文件
│ │ ├── seeds/ # 种子数据
│ │ └── database.module.ts
│ │
│ └── types/ # 类型定义
│ ├── express.d.ts # Express 类型扩展
│ └── index.d.ts
│
├── test/ # E2E 测试
│ ├── app.e2e-spec.ts
│ ├── auth.e2e-spec.ts
│ └── users.e2e-spec.ts
│
├── .env # 环境变量
├── .env.example # 环境变量示例
├── .eslintrc.js # ESLint 配置
├── .prettierrc # Prettier 配置
├── nest-cli.json # NestJS CLI 配置
├── tsconfig.json # TypeScript 配置
├── tsconfig.build.json # 构建配置
└── package.json # 项目依赖
特点:
- 结构清晰,分层明确
- 公共模块提炼
- 业务模块独立
- 配置集中管理
- 易于维护和扩展
适用场景:
- 中大型项目
- 企业级应用
- 复杂业务逻辑
- 长期维护项目目录设计原则:
│
├── 原则一:按功能模块划分
│ ├── 每个模块独立成文件夹
│ ├── 模块内部包含:controller、service、dto、entity
│ └── 模块之间低耦合,高内聚
│
├── 原则二:公共代码提炼
│ ├── common 文件夹存放共享代码
│ ├── decorators、guards、filters、pipes 等
│ └── 避免重复代码,提高复用性
│
├── 原则三:分层清晰
│ ├── 表现层:Controller(控制器)
│ ├── 业务层:Service(服务)
│ ├── 数据层:Entity、Repository(实体、仓库)
│ └── 传输层:DTO(数据传输对象)
│
├── 原则四:目录层级不要太深
│ ├── 公共代码扁平化(直接放 src 根目录)
│ ├── 引用路径简洁
│ └── 避免嵌套过深(不超过 3-4 层)
│
└── 原则五:配置集中管理
├── config 文件夹统一管理配置
├── 数据库、JWT、应用配置分离
└── 使用 @nestjs/config 管理common/ 目录详解:
│
├── decorators/ # 自定义装饰器
│ ├── current-user.decorator.ts # 获取当前用户
│ ├── is-unique.decorator.ts # 唯一性验证
│ └── index.ts # 统一导出
│
├── filters/ # 异常过滤器
│ ├── http-exception.filter.ts # HTTP 异常
│ ├── all-exceptions.filter.ts # 全局异常
│ └── index.ts
│
├── guards/ # 守卫
│ ├── jwt-auth.guard.ts # JWT 认证
│ ├── roles.guard.ts # 角色权限
│ └── index.ts
│
├── interceptors/ # 拦截器
│ ├── logging.interceptor.ts # 日志记录
│ ├── transform.interceptor.ts # 响应转换
│ └── index.ts
│
├── pipes/ # 管道
│ ├── validation.pipe.ts # 验证管道
│ └── index.ts
│
├── dto/ # 公共 DTO
│ ├── pagination.dto.ts # 分页 DTO
│ └── index.ts
│
├── interfaces/ # 接口定义
│ ├── user.interface.ts
│ └── index.ts
│
├── entities/ # 基础实体
│ ├── base.entity.ts # 基础实体类
│ └── index.ts
│
├── exceptions/ # 自定义异常
│ ├── business.exception.ts
│ └── index.ts
│
├── constants/ # 常量定义
│ ├── error-code.constant.ts
│ └── index.ts
│
└── utils/ # 工具函数
├── date.util.ts
├── string.util.ts
└── index.ts// ===== src/common/decorators/current-user.decorator.ts =====
import { createParamDecorator, ExecutionContext } from '@nestjs/common';
export const CurrentUser = createParamDecorator(
(data: string, ctx: ExecutionContext) => {
const request = ctx.switchToHttp().getRequest();
const user = request.user;
return data ? user?.[data] : user;
},
);
// 使用示例:
@Controller('users')
export class UsersController {
@Get('profile')
getProfile(@CurrentUser() user: User) {
return user;
}
@Get('profile/id')
getUserId(@CurrentUser('id') id: number) {
return { id };
}
}
// ===== src/common/filters/http-exception.filter.ts =====
import {
ExceptionFilter,
Catch,
ArgumentsHost,
HttpException,
} 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();
response.status(status).json({
statusCode: status,
timestamp: new Date().toISOString(),
path: request.url,
message: exception.message,
});
}
}
// ===== src/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;
message: string;
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) => ({
code: 200,
message: 'Success',
data,
})),
);
}
}
// ===== src/common/entities/base.entity.ts =====
import {
PrimaryGeneratedColumn,
CreateDateColumn,
UpdateDateColumn,
Column,
} from 'typeorm';
export abstract class BaseEntity {
@PrimaryGeneratedColumn()
id: number;
@CreateDateColumn({ name: 'created_at' })
createdAt: Date;
@UpdateDateColumn({ name: 'updated_at' })
updatedAt: Date;
@Column({ name: 'is_deleted', default: false })
isDeleted: boolean;
}
// 使用示例:
@Entity('users')
export class User extends BaseEntity {
@Column()
name: string;
@Column()
email: string;
}modules/ 目录详解:
│
├── auth/ # 认证模块
│ ├── dto/
│ │ ├── login.dto.ts # 登录 DTO
│ │ ├── register.dto.ts # 注册 DTO
│ │ └── index.ts
│ ├── auth.controller.ts # 控制器
│ ├── auth.service.ts # 服务
│ ├── auth.module.ts # 模块定义
│ ├── auth.strategy.ts # JWT 策略
│ └── auth.spec.ts # 单元测试
│
├── users/ # 用户模块
│ ├── dto/
│ │ ├── create-user.dto.ts
│ │ ├── update-user.dto.ts
│ │ └── index.ts
│ ├── entities/
│ │ ├── user.entity.ts
│ │ └── index.ts
│ ├── users.controller.ts
│ ├── users.service.ts
│ ├── users.module.ts
│ └── users.spec.ts
│
└── posts/ # 文章模块
├── dto/
├── entities/
├── posts.controller.ts
├── posts.service.ts
├── posts.module.ts
└── posts.spec.ts// ===== src/modules/users/users.module.ts =====
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { UsersController } from './users.controller';
import { UsersService } from './users.service';
import { User } from './entities/user.entity';
@Module({
imports: [TypeOrmModule.forFeature([User])],
controllers: [UsersController],
providers: [UsersService],
exports: [UsersService], // 导出供其他模块使用
})
export class UsersModule {}
// ===== src/modules/users/users.controller.ts =====
import {
Controller,
Get,
Post,
Body,
Param,
ParseIntPipe,
} from '@nestjs/common';
import { UsersService } from './users.service';
import { CreateUserDto } from './dto/create-user.dto';
@Controller('users')
export class UsersController {
constructor(private readonly usersService: UsersService) {}
@Post()
create(@Body() createUserDto: CreateUserDto) {
return this.usersService.create(createUserDto);
}
@Get()
findAll() {
return this.usersService.findAll();
}
@Get(':id')
findOne(@Param('id', ParseIntPipe) id: number) {
return this.usersService.findOne(id);
}
}
// ===== src/modules/users/users.service.ts =====
import { Injectable, 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';
@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();
}
async findOne(id: number): Promise<User> {
const user = await this.usersRepository.findOne({ where: { id } });
if (!user) {
throw new NotFoundException(`User with id ${id} not found`);
}
return user;
}
}
// ===== src/modules/users/entities/user.entity.ts =====
import { Entity, Column } from 'typeorm';
import { BaseEntity } from '@/common/entities/base.entity';
@Entity('users')
export class User extends BaseEntity {
@Column()
name: string;
@Column({ unique: true })
email: string;
@Column({ select: false }) // 默认不查询密码
password: string;
}
// ===== src/modules/users/dto/create-user.dto.ts =====
import { IsString, IsEmail, MinLength } from 'class-validator';
export class CreateUserDto {
@IsString()
@MinLength(2, { message: '姓名至少2个字符' })
name: string;
@IsEmail({}, { message: '邮箱格式不正确' })
email: string;
@IsString()
@MinLength(6, { message: '密码至少6个字符' })
password: string;
}// ===== src/config/app.config.ts =====
export default () => ({
port: parseInt(process.env.PORT, 10) || 3000,
nodeEnv: process.env.NODE_ENV || 'development',
});
// ===== src/config/database.config.ts =====
export default () => ({
type: process.env.DB_TYPE || 'mysql',
host: process.env.DB_HOST || 'localhost',
port: parseInt(process.env.DB_PORT, 10) || 3306,
username: process.env.DB_USERNAME || 'root',
password: process.env.DB_PASSWORD || 'password',
database: process.env.DB_DATABASE || 'test',
synchronize: process.env.NODE_ENV !== 'production',
});
// ===== src/config/jwt.config.ts =====
export default () => ({
secret: process.env.JWT_SECRET || 'secret-key',
expiresIn: process.env.JWT_EXPIRES_IN || '7d',
});
// ===== src/app.module.ts - 使用配置 =====
import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import appConfig from './config/app.config';
import databaseConfig from './config/database.config';
import jwtConfig from './config/jwt.config';
@Module({
imports: [
ConfigModule.forRoot({
isGlobal: true,
load: [appConfig, databaseConfig, jwtConfig],
envFilePath: ['.env'],
}),
// ... 其他模块
],
})
export class AppModule {}代码组织总则:
│
├── 文件原则
│ ├── 单一职责:每个文件只定义一样东西
│ ├── 文件大小:建议不超过 400 行代码
│ ├── 功能完整:一个文件包含一个完整功能
│ └── 职责清晰:文件名反映其功能
│
├── 函数原则
│ ├── 函数大小:建议不超过 75 行代码
│ ├── 单一功能:一个函数只做一件事
│ ├── 参数控制:参数不超过 3-4 个
│ └── 返回明确:有明确的返回值
│
├── 复用原则
│ ├── 公共代码:提炼到 common 文件夹
│ ├── 函数复用:通过函数形式复用
│ ├── 组件复用:通过模块形式复用
│ └── 避免重复:DRY(Don't Repeat Yourself)
│
└── 可读性原则
├── 命名清晰:变量名、函数名见名知意
├── 注释适度:复杂逻辑添加注释
├── 代码格式:统一缩进、换行
└── 逻辑清晰:先定义变量、再处理逻辑、最后返回文件命名规范(小写 + 短横线):
│
├── 正确示例
│ ├── user.controller.ts # 控制器
│ ├── user.service.ts # 服务
│ ├── user.module.ts # 模块
│ ├── user.entity.ts # 实体
│ ├── create-user.dto.ts # DTO
│ ├── user.interface.ts # 接口
│ ├── jwt-auth.guard.ts # 守卫
│ ├── validation.pipe.ts # 管道
│ ├── logging.interceptor.ts # 拦截器
│ └── http-exception.filter.ts # 过滤器
│
└── 错误示例
├── UserController.ts # 不用大驼峰
├── userController.ts # 不用小驼峰
├── user_controller.ts # 不用下划线
└── usercontroller.ts # 单词未分隔文件后缀约定:
│
├── 控制器:*.controller.ts
│ └── 例:user.controller.ts
│
├── 服务:*.service.ts
│ └── 例:user.service.ts
│
├── 模块:*.module.ts
│ └── 例:user.module.ts
│
├── 实体:*.entity.ts
│ └── 例:user.entity.ts
│
├── DTO:*.dto.ts
│ └── 例:create-user.dto.ts
│
├── 接口:*.interface.ts
│ └── 例:user.interface.ts
│
├── 守卫:*.guard.ts
│ └── 例:jwt-auth.guard.ts
│
├── 管道:*.pipe.ts
│ └── 例:validation.pipe.ts
│
├── 拦截器:*.interceptor.ts
│ └── 例:logging.interceptor.ts
│
├── 过滤器:*.filter.ts
│ └── 例:http-exception.filter.ts
│
├── 装饰器:*.decorator.ts
│ └── 例:current-user.decorator.ts
│
└── 测试:*.spec.ts(单元测试)、*.e2e-spec.ts(E2E 测试)
└── 例:user.service.spec.ts// ===== 正确示例:类名使用大驼峰 =====
// 控制器
@Controller('users')
export class UsersController {
// ...
}
// 服务
@Injectable()
export class UsersService {
// ...
}
// 模块
@Module({})
export class UsersModule {
// ...
}
// 实体
@Entity('users')
export class User {
// ...
}
// DTO
export class CreateUserDto {
// ...
}
// 守卫
@Injectable()
export class JwtAuthGuard implements CanActivate {
// ...
}
// 管道
@Injectable()
export class ValidationPipe implements PipeTransform {
// ...
}
// 拦截器
@Injectable()
export class LoggingInterceptor implements NestInterceptor {
// ...
}
// 过滤器
@Catch(HttpException)
export class HttpExceptionFilter implements ExceptionFilter {
// ...
}// ===== 正确示例:函数名使用小驼峰 =====
export class UsersService {
// 获取所有用户
findAll(): Promise<User[]> {
return this.usersRepository.find();
}
// 获取单个用户
findOne(id: number): Promise<User> {
return this.usersRepository.findOne({ where: { id } });
}
// 创建用户
create(createUserDto: CreateUserDto): Promise<User> {
const user = this.usersRepository.create(createUserDto);
return this.usersRepository.save(user);
}
// 更新用户
update(id: number, updateUserDto: UpdateUserDto): Promise<User> {
return this.usersRepository.update(id, updateUserDto);
}
// 删除用户
remove(id: number): Promise<void> {
return this.usersRepository.delete(id);
}
// 检查邮箱是否存在
checkEmailExists(email: string): Promise<boolean> {
return this.usersRepository.exists({ where: { email } });
}
// 生成 JWT token
generateJwtToken(user: User): string {
return this.jwtService.sign({ id: user.id, email: user.email });
}
}// ===== 正确示例:变量名使用小驼峰 =====
// 普通变量
const userName = '张三';
const userId = 123;
const isActive = true;
// 常量(全大写 + 下划线)
const MAX_PAGE_SIZE = 100;
const DEFAULT_PAGE = 1;
const JWT_SECRET = 'secret-key';
// 私有变量(下划线前缀)
export class UsersService {
private readonly userRepository: Repository<User>;
private _cache: Map<string, any>;
}
// 接口属性(小驼峰)
interface User {
id: number;
userName: string;
emailAddress: string;
createdAt: Date;
}
// 枚举(大驼峰 + 大驼峰值)
enum UserRole {
Admin = 'Admin',
User = 'User',
Guest = 'Guest',
}
// 枚举(大驼峰 + 全大写下划线值)
enum HttpStatus {
OK = 200,
BAD_REQUEST = 400,
NOT_FOUND = 404,
INTERNAL_SERVER_ERROR = 500,
}前端 vs 后端命名规范对比:
│
├── 前端(Vue/React)
│ ├── 组件文件:大驼峰(UserCard.vue)
│ ├── 页面文件:小驼峰(userProfile.vue)
│ ├── 样式文件:小写短横线
│ └── 原因:组件类似类,使用大驼峰
│
└── 后端
├── 所有文件:小写短横线
│ ├── user.controller.ts
│ ├── user.service.ts
│ └── user.module.ts
├── 类名:大驼峰
│ ├── UsersController
│ ├── UsersService
│ └── UsersModule
└── 原因:后端偏传统,文件名小写更规范// .eslintrc.js
module.exports = {
parser: '@typescript-eslint/parser',
parserOptions: {
project: 'tsconfig.json',
sourceType: 'module',
},
plugins: ['@typescript-eslint/eslint-plugin'],
extends: [
'plugin:@typescript-eslint/recommended',
'plugin:prettier/recommended',
],
root: true,
env: {
node: true,
jest: true,
},
ignorePatterns: ['.eslintrc.js'],
rules: {
'@typescript-eslint/interface-name-prefix': 'off',
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
},
};.prettierrc:
{
"singleQuote": true,
"trailingComma": "all",
"tabWidth": 2,
"semi": true,
"printWidth": 100,
"arrowParens": "always",
"endOfLine": "auto"
}package.json 中添加的 scripts:
{
"scripts": {
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\""
}
}完整项目结构(推荐):
│
nest-project/
├── src/
│ ├── main.ts # 应用入口
│ ├── app.module.ts # 根模块
│ │
│ ├── common/ # 公共模块
│ │ ├── decorators/ # 装饰器
│ │ ├── filters/ # 过滤器
│ │ ├── guards/ # 守卫
│ │ ├── interceptors/ # 拦截器
│ │ ├── pipes/ # 管道
│ │ ├── dto/ # 公共 DTO
│ │ ├── interfaces/ # 接口
│ │ ├── entities/ # 基础实体
│ │ ├── exceptions/ # 异常
│ │ ├── constants/ # 常量
│ │ └── utils/ # 工具函数
│ │
│ ├── config/ # 配置
│ │ ├── app.config.ts
│ │ ├── database.config.ts
│ │ └── jwt.config.ts
│ │
│ ├── modules/ # 业务模块
│ │ ├── auth/
│ │ ├── users/
│ │ ├── posts/
│ │ └── comments/
│ │
│ ├── database/ # 数据库
│ │ ├── migrations/
│ │ ├── seeds/
│ │ └── database.module.ts
│ │
│ └── types/ # 类型定义
│
├── test/ # 测试
├── .env # 环境变量
├── .eslintrc.js # ESLint
├── .prettierrc # Prettier
└── package.json# ===== 创建用户模块完整流程 =====
# 1. 创建模块
nest g module modules/users
# 2. 创建控制器
nest g controller modules/users --module=modules/users
# 3. 创建服务
nest g service modules/users --module=modules/users
# 4. 创建 DTO
nest g class modules/users/dto/create-user.dto --flat
nest g class modules/users/dto/update-user.dto --flat
# 5. 创建实体
nest g class modules/users/entities/user.entity --flat
# 6. 测试文件(*.spec.ts)在生成代码时自动创建(不加 --no-spec 时)
# 最终结构:
src/modules/users/
├── dto/
│ ├── create-user.dto.ts
│ └── update-user.dto.ts
├── entities/
│ └── user.entity.ts
├── users.controller.ts
├── users.controller.spec.ts
├── users.service.ts
├── users.service.spec.ts
└── users.module.ts| 问题 | 原因 | 解决方案 |
|---|---|---|
| 目录结构混乱 | 缺乏规划 | 参考推荐结构,按模块划分 |
| 文件命名不一致 | 未统一规范 | 使用小写+短横线,遵循后缀约定 |
| 类名命名不规范 | 混淆前后端规范 | 类名统一使用大驼峰 |
| 代码行数过多 | 单一职责不明确 | 拆分函数,提炼公共逻辑 |
| 公共代码重复 | 未提炼公共模块 | 创建 common 文件夹,统一管理 |
| 配置散乱 | 未集中管理 | 使用 config 文件夹 + @nestjs/config |
| 导入路径复杂 | 目录层级太深 | 扁平化公共代码,使用别名 |
目录结构检查清单:
│
├── 是否按功能模块划分?
├── 公共代码是否提炼到 common?
├── 配置是否集中管理?
├── 目录层级是否超过 3-4 层?
└── 测试文件是否齐全?
│
命名规范检查清单:
│
├── 文件名是否使用小写+短横线?
├── 类名是否使用大驼峰?
├── 函数名是否使用小驼峰?
├── 常量是否使用全大写+下划线?
└── 后缀是否符合约定?
│
代码质量检查清单:
│
├── 文件行数是否超过 400 行?
├── 函数行数是否超过 75 行?
├── 是否存在重复代码?
├── 函数职责是否单一?
└── 变量命名是否清晰?| 类型 | 后缀 | 示例 |
|---|---|---|
| 控制器 | *.controller.ts | user.controller.ts |
| 服务 | *.service.ts | user.service.ts |
| 模块 | *.module.ts | user.module.ts |
| 实体 | *.entity.ts | user.entity.ts |
| DTO | *.dto.ts | create-user.dto.ts |
| 接口 | *.interface.ts | user.interface.ts |
| 守卫 | *.guard.ts | jwt-auth.guard.ts |
| 管道 | *.pipe.ts | validation.pipe.ts |
| 拦截器 | *.interceptor.ts | logging.interceptor.ts |
| 过滤器 | *.filter.ts | http-exception.filter.ts |
| 装饰器 | *.decorator.ts | current-user.decorator.ts |
| 单元测试 | *.spec.ts | user.service.spec.ts |
| E2E 测试 | *.e2e-spec.ts | user.e2e-spec.ts |
| 类型 | 规范 | 示例 |
|---|---|---|
| 控制器 | 大驼峰 + Controller | UsersController |
| 服务 | 大驼峰 + Service | UsersService |
| 模块 | 大驼峰 + Module | UsersModule |
| 实体 | 大驼峰 | User |
| DTO | 大驼峰 + Dto | CreateUserDto |
| 接口 | 大驼峰 | User |
| 守卫 | 大驼峰 + Guard | JwtAuthGuard |
| 管道 | 大驼峰 + Pipe | ValidationPipe |
| 拦截器 | 大驼峰 + Interceptor | LoggingInterceptor |
| 过滤器 | 大驼峰 + Filter | HttpExceptionFilter |
| 类型 | 规范 | 示例 |
|---|---|---|
| 普通变量 | 小驼峰 | userName, userId |
| 常量 | 全大写+下划线 | MAX_PAGE_SIZE |
| 私有变量 | 下划线前缀 | _cache |
| 函数 | 小驼峰 | findAll, checkEmailExists |
| 枚举 | 大驼峰 | UserRole, HttpStatus |
start:dev 的作用概念说明
start:dev 用于开发环境启动,支持监听文件变化并自动重启服务,适合边写边调试。
语法/用法
pnpm run start:dev
# 或 npm run start:dev代码示例
{
"scripts": {
"start:dev": "nest start --watch"
}
}注意事项
start:dev。@Controller + @Get概念说明
控制器通过装饰器声明路由入口:@Controller('api') 定义控制器前缀,@Get('app') 定义具体路径。
语法/用法
控制器前缀 + 方法路径@Get('hello') 和 @Get('/hello') 效果一致(推荐不写前导 /)代码示例
import { Controller, Get } from '@nestjs/common';
@Controller('api')
export class AppController {
@Get('app')
getApp(): string {
return "hello, let's NestJS";
}
}注意事项
/app 变为 /api/app。概念说明
NestJS 会自动序列化对象/数组为 JSON,无需手动 JSON.stringify。
语法/用法
代码示例
@Get('hello')
getHello(): { code: number; data: string; message: string } {
return {
code: 0,
data: 'hello James',
message: '请求成功',
};
}注意事项
any 只是教学简化,实际项目建议声明明确返回类型。setGlobalPrefix 实战概念说明
全局前缀用于统一接口命名、版本隔离和网关转发约定。
语法/用法
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.setGlobalPrefix('api/v1');
await app.listen(3000);
}
bootstrap();代码示例
GET /usersGET /api/v1/users注意事项
api/v1、api/v2。概念说明
Nest CLI 会自动生成标准文件结构,并在 app.module.ts / 对应模块中自动注册。
语法/用法
# 创建模块
nest g module user
# 创建控制器(不生成测试文件)
nest g controller user --no-spec
# 创建服务(不生成测试文件)
nest g service user --no-spec
# 预演模式(不落盘)
nest g controller user --no-spec -d代码示例
src/user/
├── user.module.ts
├── user.controller.ts
└── user.service.ts注意事项
nest g、--no-spec。-d 预览,再执行真实生成命令。概念说明
Controller 负责接收请求和返回响应;Service 负责业务逻辑。
语法/用法
代码示例
// user.controller.ts
import { Controller, Get, Post } from '@nestjs/common';
import { UserService } from './user.service';
@Controller('user')
export class UserController {
constructor(private readonly userService: UserService) {}
@Get()
getUsers() {
return this.userService.getUsers();
}
@Post()
addUser() {
return this.userService.addUser();
}
}
// user.service.ts
import { Injectable } from '@nestjs/common';
@Injectable()
export class UserService {
getUsers() {
return {
code: 0,
data: [{ id: 1, name: 'Tom' }],
message: '请求用户列表成功',
};
}
addUser() {
return {
code: 0,
data: { id: 2, name: 'Jerry' },
message: '添加用户成功',
};
}
}注意事项
private readonly userService: UserService 是 TS 参数属性语法,不是手动 new。概念说明
使用环境变量可在 DEV/PRO 间快速切换,避免手动改 URL。
语法/用法
baseUrl{{baseUrl}}/api/v1/user代码示例
DEV: baseUrl = http://localhost:3000
PRO: baseUrl = https://api.example.com注意事项
GET /range/:number概念说明
传入 number,返回从 1 到 number 的字符串数组。例如:50 -> ["1", "2", ..., "50"]。
语法/用法
@Param('number')ParseIntPipe + 自定义范围判断string[](不是 number[])代码示例
import {
BadRequestException,
Controller,
Get,
Param,
ParseIntPipe,
} from '@nestjs/common';
@Controller('range')
export class RangeController {
@Get(':number')
getRange(@Param('number', ParseIntPipe) number: number) {
if (number <= 0 || number > 10000) {
throw new BadRequestException('number 必须在 1~10000 之间');
}
const data = Array.from({ length: number }, (_, index) => String(index + 1));
return {
code: 0,
data,
message: '请求成功',
};
}
}注意事项
data 返回成数字数组。实现一个 user 模块,提供:
GET /api/v1/user:查询用户列表POST /api/v1/user:新增用户{ code, data, message }// src/main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.setGlobalPrefix('api/v1');
await app.listen(3000);
}
bootstrap();
// src/user/user.module.ts
import { Module } from '@nestjs/common';
import { UserController } from './user.controller';
import { UserService } from './user.service';
@Module({
controllers: [UserController],
providers: [UserService],
})
export class UserModule {}
// src/user/user.service.ts
import { Injectable } from '@nestjs/common';
@Injectable()
export class UserService {
private users = [{ id: 1, name: 'Tom' }];
getUsers() {
return {
code: 0,
data: this.users,
message: '请求用户列表成功',
};
}
addUser() {
const newUser = { id: Date.now(), name: 'New User' };
this.users.push(newUser);
return {
code: 0,
data: newUser,
message: '添加用户成功',
};
}
}
// src/user/user.controller.ts
import { Controller, Get, Post } from '@nestjs/common';
import { UserService } from './user.service';
@Controller('user')
export class UserController {
constructor(private readonly userService: UserService) {}
@Get()
getUsers() {
return this.userService.getUsers();
}
@Post()
addUser() {
return this.userService.addUser();
}
}| 代码位置 | 关键点 | 说明 |
|---|---|---|
main.ts | setGlobalPrefix('api/v1') | 统一接口访问前缀,便于版本化管理 |
user.module.ts | controllers/providers | 注册控制器与服务,建立依赖关系 |
user.controller.ts | @Get() / @Post() | 映射 HTTP 方法到具体业务入口 |
user.controller.ts | 构造器注入 UserService | 通过 DI 获取业务能力,不手动 new |
user.service.ts | getUsers/addUser | 承载核心业务逻辑与数据处理 |
| 全部响应 | { code, data, message } | 统一返回结构,便于前端消费与错误处理 |
| 问题 | 原因 | 解决方案 |
|---|---|---|
| 请求路径 404 | 忘记加 api/v1 前缀 | 检查 main.ts 的 setGlobalPrefix 设置 |
Post 装饰器报未定义 | 未导入 Post | import { Post } from '@nestjs/common' |
| 浏览器无法测试 POST | 浏览器地址栏只适合 GET | 使用 Postman/Apifox 发 POST |
| CLI 命令生成了多余测试文件 | 没加 --no-spec | 命令追加 --no-spec |
| 返回结构不统一 | Controller 各自返回格式随意 | 统一为 { code, data, message } 规范 |
range 返回数字数组 | 忽略“字符串数组”要求 | String(index + 1) 做显式转换 |
@Controller + @Get/@Post 是 NestJS 接口开发最核心入口。-d 预演。setGlobalPrefix('api/v1') 是接口版本治理的关键基础动作。range 的重点在“参数校验 + 字符串数组返回 + 路由准确性”。POST /user 增加 DTO + ValidationPipe 校验range 改造成 GET /math/range/:number 独立模块DELETE /user/:id,并返回统一错误结构概念说明
MVC(Model-View-Controller)是一种软件架构模式,用于将应用程序分离为三个核心组件。
三大组成部分
MVC 三层架构:
│
├── Model(模型)
│ ├── 核心业务逻辑
│ ├── 数据处理
│ ├── 数据库操作
│ └── 数据状态管理
│
├── View(视图)
│ ├── 用户界面
│ ├── 数据展示
│ └── 用户交互入口
│
└── Controller(控制器)
├── 请求解析
├── 路由分发
├── 调用 Model
└── 返回 View传统 MVC 流程
传统 MVC 工作流程:
┌──────────┐
│ 用户 │
└────┬─────┘
│ 操作
▼
┌──────────┐ 1. 用户操作 ┌──────────┐
│ View │ ───────────────────> │Controller│
│ 视图层 │ │ 控制器 │
└────┬─────┘ └─────┬────┘
│ │
│ 4. 数据变化通知 │ 2. 调用业务逻辑
│ │
▼ ▼
┌──────────┐ ┌──────────┐
│ 用户 │ │ Model │
│ 看到更新 │ <─────────────────── │ 模型 │
└──────────┘ 3. 数据处理完成 └──────────┘
流程说明:
1. 用户在 View 层进行操作(点击按钮、提交表单)
2. Controller 接收请求,调用对应的 Model
3. Model 处理业务逻辑,操作数据库
4. Model 处理完成后,通知 View 更新现代前后端分离 MVC
现代前后端分离 MVC:
前端(View 层):
│
├── React / Vue / Angular
├── 用户界面
├── 用户交互
└── 发送 HTTP 请求
│ HTTP 请求
▼
后端(Controller + Model):
│
├── Controller(控制器)
│ ├── 接收请求
│ ├── 解析参数
│ ├── 调用 Service
│ └── 返回响应
│
└── Model(模型)
├── Service 业务逻辑
├── Repository 数据访问
└── Entity 数据实体
│ SQL 查询
▼
数据库:
└── MySQL / PostgreSQL / MongoDB
流程说明:
1. 前端发送 HTTP 请求到后端
2. Controller 接收请求并解析
3. Controller 调用 Model 处理业务
4. Model 操作数据库并返回数据
5. Controller 将数据返回给前端
6. 前端 View 层渲染数据MVC 架构优势:
│
├── 分层清晰
│ ├── 职责分离
│ ├── 代码组织清晰
│ └── 易于理解
│
├── 可复用性
│ ├── Model 可被多个 View 使用
│ ├── Controller 可复用
│ └── 减少重复代码
│
├── 可测试性
│ ├── 各层独立测试
│ ├── 易于模拟依赖
│ └── 提高测试覆盖率
│
├── 可维护性
│ ├── 修改不影响其他层
│ ├── 易于扩展
│ └── 降低维护成本
│
└── 团队协作
├── 前后端分离开发
├── 并行开发
└── 提高开发效率NestJS 三层对应关系
NestJS 中的 MVC 实现:
MVC 概念 NestJS 实现
│ │
├── View(视图) ────> ├── 前端项目(React/Vue)
│ ├── 模板引擎(Handlebars/EJS)
│ └── 或不实现(前后端分离)
│
├── Controller ───────> ├── @Controller 装饰器
│ ├── 处理 HTTP 请求
│ └── 调用 Service
│
└── Model(模型) ────> ├── Service(业务逻辑)
├── Repository(数据访问)
├── Entity(数据实体)
└── ORM(TypeORM/Prisma)模板引擎集成
// ========== 安装模板引擎 ==========
// npm install hbs
// ========== 配置模板引擎 ==========
// src/main.ts
import { NestFactory } from '@nestjs/core';
import { NestExpressApplication } from '@nestjs/platform-express';
import { join } from 'path';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create<NestExpressApplication>(AppModule);
// 配置模板引擎
app.useStaticAssets(join(__dirname, '..', 'public'));
app.setBaseViewsDir(join(__dirname, '..', 'views'));
app.setViewEngine('hbs'); // 使用 Handlebars 模板引擎
await app.listen(3000);
}
bootstrap();
// ========== 控制器渲染视图 ==========
// src/app.controller.ts
import { Controller, Get, Render } from '@nestjs/common';
@Controller()
export class AppController {
@Get()
@Render('index') // 渲染 index.hbs 模板
root() {
return { message: 'Hello World!' }; // 传递数据给模板
}
@Get('users')
@Render('users')
getUsers() {
return {
users: [
{ id: 1, name: '张三' },
{ id: 2, name: '李四' },
],
};
}
}
// ========== 模板文件 ==========
<!-- views/index.hbs -->
<!DOCTYPE html>
<html>
<head>
<title>NestJS MVC</title>
</head>
<body>
<h1>{{ message }}</h1>
</body>
</html>
<!-- views/users.hbs -->
<!DOCTYPE html>
<html>
<head>
<title>用户列表</title>
</head>
<body>
<h1>用户列表</h1>
<ul>
{{#each users}}
<li>{{this.id}} - {{this.name}}</li>
{{/each}}
</ul>
</body>
</html>常用模板引擎
| 模板引擎 | 说明 | 适用场景 |
|---|---|---|
| Handlebars (hbs) | 简单、轻量 | 传统服务端渲染 |
| EJS | 嵌入式 JavaScript | 简单项目 |
| Pug (Jade) | 缩进语法 | 追求简洁 |
| Nunjucks | 功能强大 | 复杂模板 |
现代推荐方式
前后端分离架构:
前端项目(独立):
│
├── React / Vue / Angular
├── 单独部署(静态资源服务器)
├── 调用后端 API
└── 用户界面渲染
│ RESTful API / GraphQL
▼
后端项目(NestJS):
│
├── Controller(API 接口)
├── Service(业务逻辑)
├── Repository(数据访问)
└── Entity(数据实体)
│ SQL
▼
数据库
优势:
• 前后端独立开发
• 前端可使用现代化框架
• 后端只提供 API
• 部署灵活概念说明
DTO(Data Transfer Object,数据传输对象)用于在不同层之间传输数据,重点在于"传输"过程。
核心特点
DTO 核心特点:
│
├── 数据传输
│ ├── 前端 → 后端(请求 DTO)
│ ├── 后端 → 前端(响应 DTO)
│ └── 服务层 → 数据层
│
├── 数据筛选
│ ├── 只接收部分字段
│ ├── 过滤敏感字段
│ └── 控制数据暴露
│
├── 数据校验
│ ├── 字段类型验证
│ ├── 字段格式验证
│ └── 字段约束验证
│
└── 属性约束
└── 属性 ≤ 实体属性(部分数据)三大核心作用
DTO 三大核心作用:
1⃣ 接收部分数据
│
├── 场景:创建用户
│ ├── 实体字段:id, name, email, password, createdAt, updatedAt
│ ├── DTO 字段:name, email, password
│ └── 说明:id、时间戳由后端生成,不需要前端传
2⃣ 数据筛选
│
├── 场景:更新用户
│ ├── 危险:允许前端传 id
│ │ └── A 用户可以修改 B 用户数据(传 B 的 id)
│ ├── 安全:DTO 不包含 id
│ │ └── 从 Token 获取当前用户 id
│ └── 说明:敏感字段不能通过 DTO 传递
3⃣ 数据校验
│
├── 场景:创建用户
│ ├── name:必填,长度 2-100
│ ├── email:必填,邮箱格式
│ └── password:必填,长度 ≥ 6
│ └── 说明:在 DTO 中定义校验规则| 维度 | DTO(数据传输对象) | Entity(实体类) |
|---|---|---|
| 用途 | 层间数据传输 | 数据库映射 |
| 字段 | 部分字段 | 完整字段 |
| 包含敏感信息 | 不包含 | 包含 |
| 数据校验 | 强校验 | 可选 |
| 对应关系 | API 接口 | 数据库表 |
| 示例 | CreateUserDto | User Entity |
代码示例对比
// ========== Entity(实体类)==========
// src/users/entities/user.entity.ts
import { Entity, Column, PrimaryGeneratedColumn, CreateDateColumn, UpdateDateColumn } from 'typeorm';
@Entity('users')
export class User {
@PrimaryGeneratedColumn()
id: number; // 数据库自动生成
@Column({ length: 100 })
name: string;
@Column({ unique: true })
email: string;
@Column()
password: string; // 敏感字段
@Column({ default: true })
isActive: boolean;
@CreateDateColumn()
createdAt: Date; // 数据库自动生成
@UpdateDateColumn()
updatedAt: Date; // 数据库自动生成
}
// ========== DTO(数据传输对象)==========
// src/users/dto/create-user.dto.ts
import { IsString, IsEmail, MinLength, MaxLength, IsOptional } from 'class-validator';
export class CreateUserDto {
@IsString()
@MinLength(2, { message: '用户名至少 2 个字符' })
@MaxLength(100, { message: '用户名最多 100 个字符' })
name: string;
@IsEmail({}, { message: '邮箱格式不正确' })
email: string;
@IsString()
@MinLength(6, { message: '密码至少 6 个字符' })
password: string;
// 不包含 id(数据库自动生成)
// 不包含 createdAt(数据库自动生成)
// 不包含 updatedAt(数据库自动生成)
}
// src/users/dto/update-user.dto.ts
import { PartialType, OmitType } from '@nestjs/mapped-types';
import { CreateUserDto } from './create-user.dto';
// PartialType:所有字段变为可选
// OmitType:排除某些字段
export class UpdateUserDto extends PartialType(
OmitType(CreateUserDto, ['password'] as const),
) {
// 更新时不允许修改密码
// 密码修改应该有专门的接口
}
// src/users/dto/response-user.dto.ts
import { Exclude, Expose } from 'class-transformer';
export class ResponseUserDto {
id: number;
name: string;
email: string;
isActive: boolean;
createdAt: Date;
@Exclude() // 响应时不返回密码
password: string;
constructor(partial: Partial<ResponseUserDto>) {
Object.assign(this, partial);
}
}
// ========== 使用示例 ==========
// src/users/users.controller.ts
import { Controller, Post, Body, Get, Param } from '@nestjs/common';
import { UsersService } from './users.service';
import { CreateUserDto } from './dto/create-user.dto';
import { UpdateUserDto } from './dto/update-user.dto';
import { ResponseUserDto } from './dto/response-user.dto';
@Controller('users')
export class UsersController {
constructor(private readonly usersService: UsersService) {}
@Post()
async create(@Body() createUserDto: CreateUserDto): Promise<ResponseUserDto> {
// DTO 自动验证字段
// 只接收 name, email, password
const user = await this.usersService.create(createUserDto);
// 响应 DTO 过滤敏感字段
return new ResponseUserDto(user);
}
}安装依赖
npm install class-validator class-transformer完整 DTO 示例
// src/users/dto/create-user.dto.ts
import {
IsString,
IsEmail,
MinLength,
MaxLength,
IsInt,
Min,
Max,
IsOptional,
IsBoolean,
Matches,
} from 'class-validator';
export class CreateUserDto {
// 用户名:必填,2-100 字符
@IsString({ message: '用户名必须是字符串' })
@MinLength(2, { message: '用户名至少 2 个字符' })
@MaxLength(100, { message: '用户名最多 100 个字符' })
name: string;
// 邮箱:必填,邮箱格式
@IsEmail({}, { message: '邮箱格式不正确' })
email: string;
// 密码:必填,至少 6 位,包含数字和字母
@IsString()
@MinLength(6, { message: '密码至少 6 个字符' })
@Matches(/^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d]{6,}$/, {
message: '密码必须包含数字和字母',
})
password: string;
// 年龄:可选,0-150
@IsOptional()
@IsInt({ message: '年龄必须是整数' })
@Min(0, { message: '年龄不能小于 0' })
@Max(150, { message: '年龄不能大于 150' })
age?: number;
// 是否激活:可选,布尔值
@IsOptional()
@IsBoolean({ message: 'isActive 必须是布尔值' })
isActive?: boolean;
}
// ========== 启用全局验证 ==========
// src/main.ts
import { NestFactory } from '@nestjs/core';
import { ValidationPipe } from '@nestjs/common';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
// 全局启用验证管道
app.useGlobalPipes(
new ValidationPipe({
whitelist: true, // 过滤未定义的属性
forbidNonWhitelisted: true, // 如果有未定义属性,抛出错误
transform: true, // 自动转换类型
transformOptions: {
enableImplicitConversion: true,
},
}),
);
await app.listen(3000);
}
bootstrap();
// ========== 验证效果 ==========
// 请求示例(验证失败)
POST /users
{
"name": "A", // 至少 2 个字符
"email": "invalid-email", // 邮箱格式不正确
"password": "123456", // 必须包含数字和字母
"age": -5, // 年龄不能小于 0
"hobby": "coding" // 未定义的字段
}
// 响应
{
"statusCode": 400,
"message": [
"用户名至少 2 个字符",
"邮箱格式不正确",
"密码必须包含数字和字母",
"年龄不能小于 0",
"property hobby should not exist"
],
"error": "Bad Request"
}概念说明
DAO(Data Access Object,数据访问对象)用于封装数据库访问逻辑,重点在于"访问"数据库。
核心特点
DAO 核心特点:
│
├── 数据库访问
│ ├── 封装数据库操作
│ ├── 隐藏 SQL 细节
│ └── 提供统一接口
│
├── 封装性
│ ├── 不暴露数据库内部信息
│ ├── 对外提供方法
│ └── 降低耦合度
│
├── 单一职责
│ ├── 只负责数据访问
│ ├── 不包含业务逻辑
│ └── 可复用性强
│
└── 可替换性
├── 更换数据库只需修改 DAO
├── 不影响上层业务
└── 灵活切换实现DAO 层的职责
DAO 层职责:
┌──────────────────────────────────────┐
│ Service 层(业务逻辑) │
│ 调用 DAO 方法,不关心数据库细节 │
└──────────────┬───────────────────────┘
│ 调用
▼
┌──────────────────────────────────────┐
│ DAO 层(数据访问) │
│ ┌────────────────────────────────┐ │
│ │ • 实体类(Entity) │ │
│ │ • 数据库操作(CRUD) │ │
│ │ • 数据校验 │ │
│ │ • 错误处理 │ │
│ └────────────────────────────────┘ │
└──────────────┬───────────────────────┘
│ SQL 查询
▼
┌──────────┐
│ 数据库 │
└──────────┘
DAO 层包含:
│
├── Entity(实体类)
│ └── 对应数据库表
│
├── Repository(仓库)
│ └── 数据库 CRUD 操作
│
├── Validation(校验)
│ └── 数据库约束校验
│
└── Error Handling(错误处理)
└── 数据库异常处理DAO 层的多种实现方式
NestJS 中 DAO 层的实现:
1⃣ TypeORM(推荐)
│
├── Entity:实体类
├── Repository:数据访问
└── 支持:MySQL, PostgreSQL, MongoDB, SQLite
2⃣ Prisma
│
├── Schema:数据模型
├── Client:数据访问
└── 支持:MySQL, PostgreSQL, MongoDB, SQLite
3⃣ Mongoose(MongoDB)
│
├── Schema:数据模型
├── Model:数据访问
└── 支持:MongoDB
4⃣ Sequelize
│
├── Model:数据模型
├── 支持:MySQL, PostgreSQL, SQLite
└── 传统的 ORM 库完整示例
// ========== 安装依赖 ==========
// npm install @nestjs/typeorm typeorm mysql2
// ========== 配置数据库连接 ==========
// src/app.module.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { UsersModule } from './users/users.module';
@Module({
imports: [
TypeOrmModule.forRoot({
type: 'mysql',
host: 'localhost',
port: 3306,
username: 'root',
password: 'password',
database: 'test',
entities: [], // 需在此列出实体类;配合 forFeature 时可改用 autoLoadEntities: true 自动加载
synchronize: true, // 自动同步表结构(生产环境设为 false)
}),
UsersModule,
],
})
export class AppModule {}
// ========== Entity(实体类)==========
// src/users/entities/user.entity.ts
import { Entity, Column, PrimaryGeneratedColumn, CreateDateColumn, UpdateDateColumn, Index } from 'typeorm';
@Entity('users') // 对应数据库表名
export class User {
@PrimaryGeneratedColumn()
id: number;
@Column({ length: 100 })
name: string;
@Index() // 添加索引
@Column({ unique: true }) // 唯一约束
email: string;
@Column()
password: string;
@Column({ default: true })
isActive: boolean;
@CreateDateColumn()
createdAt: Date;
@UpdateDateColumn()
updatedAt: Date;
}
// ========== Repository(数据访问层)==========
// src/users/users.repository.ts(可选,使用 TypeORM Repository)
import { Repository } from 'typeorm';
import { User } from './entities/user.entity';
// 注意:@EntityRepository 装饰器是 TypeORM 0.2.x 的旧写法,0.3+ 已废弃,
// 现行写法是继承 Repository 并通过 DataSource 构造(见下方注释)
export class UsersRepository extends Repository<User> {
constructor(dataSource: import('typeorm').DataSource) {
super(User, dataSource.manager);
}
// 自定义查询方法
async findActiveUsers(): Promise<User[]> {
return this.createQueryBuilder('user')
.where('user.isActive = :isActive', { isActive: true })
.getMany();
}
async findByEmail(email: string): Promise<User | undefined> {
return this.findOne({ where: { email } });
}
async findWithPagination(page: number, limit: number): Promise<[User[], number]> {
return this.findAndCount({
skip: (page - 1) * limit,
take: limit,
order: { createdAt: 'DESC' },
});
}
}
// ========== Service(业务逻辑层)==========
// src/users/users.service.ts
import { Injectable, 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';
@Injectable()
export class UsersService {
constructor(
@InjectRepository(User)
private readonly userRepository: Repository<User>,
) {}
// 创建用户
async create(createUserDto: CreateUserDto): Promise<User> {
const user = this.userRepository.create(createUserDto);
return this.userRepository.save(user);
}
// 查询所有用户
async findAll(): Promise<User[]> {
return this.userRepository.find();
}
// 查询单个用户
async findOne(id: number): Promise<User> {
const user = await this.userRepository.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);
Object.assign(user, updateUserDto);
return this.userRepository.save(user);
}
// 删除用户
async remove(id: number): Promise<void> {
const user = await this.findOne(id);
await this.userRepository.remove(user);
}
}
// ========== Module(模块注册)==========
// src/users/users.module.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { UsersController } from './users.controller';
import { UsersService } from './users.service';
import { User } from './entities/user.entity';
@Module({
imports: [TypeOrmModule.forFeature([User])], // 注册实体
controllers: [UsersController],
providers: [UsersService],
exports: [UsersService],
})
export class UsersModule {}DAO 层优势:
1⃣ 封装数据库细节
│
├── 上层不知道数据库类型
├── 上层不知道 SQL 语句
└── 更换数据库只需修改 DAO
2⃣ 提高可测试性
│
├── 可以模拟 DAO 层
├── Service 层独立测试
└── 不需要真实数据库
3⃣ 代码复用
│
├── 多个 Service 可复用 DAO
├── 统一的数据访问接口
└── 减少重复代码
4⃣ 易于维护
│
├── 数据库操作集中管理
├── 修改影响范围小
└── 降低维护成本DTO vs DAO 核心区别:
┌──────────────────────────────────────────┐
│ HTTP 请求 │
└────────────────┬─────────────────────────┘
│
▼
┌──────────────────────────────────────────┐
│ DTO(数据传输对象) │
│ ┌────────────────────────────────────┐ │
│ │ • 接收请求数据 │ │
│ │ • 数据校验 │ │
│ │ • 数据筛选 │ │
│ │ • 部分字段 │ │
│ └────────────────────────────────────┘ │
└────────────────┬─────────────────────────┘
│
▼
┌──────────────────────────────────────────┐
│ Controller 控制器 │
└────────────────┬─────────────────────────┘
│
▼
┌──────────────────────────────────────────┐
│ Service 业务逻辑 │
└────────────────┬─────────────────────────┘
│
▼
┌──────────────────────────────────────────┐
│ DAO(数据访问对象) │
│ ┌────────────────────────────────────┐ │
│ │ • 数据库访问 │ │
│ │ • CRUD 操作 │ │
│ │ • 实体类映射 │ │
│ │ • 完整字段 │ │
│ └────────────────────────────────────┘ │
└────────────────┬─────────────────────────┘
│
▼
┌──────────┐
│ 数据库 │
└──────────┘| 维度 | DTO | DAO |
|---|---|---|
| 全称 | Data Transfer Object | Data Access Object |
| 关键词 | Transfer(传输) | Access(访问) |
| 位置 | Controller 层 | Service → 数据库层 |
| 作用 | 数据传输、校验、筛选 | 数据库访问、CRUD |
| 字段 | 部分字段 | 完整字段 |
| 对应关系 | API 接口 | 数据库表 |
| 包含敏感信息 | 不包含 | 包含 |
| NestJS 实现 | class-validator | TypeORM/Prisma |
| 示例 | CreateUserDto | User Entity + Repository |
从请求到数据库的完整流程
// ========== 1. HTTP 请求 ==========
// 前端发送请求
POST /users
{
"name": "张三",
"email": "zhangsan@example.com",
"password": "abc123"
}
// ========== 2. DTO 验证 ==========
// src/users/dto/create-user.dto.ts
export class CreateUserDto {
@IsString()
@MinLength(2)
name: string; // 接收
@IsEmail()
email: string; // 接收
@IsString()
@MinLength(6)
password: string; // 接收
// 不接收 id、createdAt、updatedAt
}
// ========== 3. Controller 处理 ==========
@Post()
async create(@Body() createUserDto: CreateUserDto) {
// DTO 已验证,调用 Service
return this.usersService.create(createUserDto);
}
// ========== 4. Service 业务逻辑 ==========
async create(createUserDto: CreateUserDto): Promise<User> {
// 业务逻辑:密码加密
const hashedPassword = await this.hashPassword(createUserDto.password);
// 调用 DAO 层
const user = this.userRepository.create({
...createUserDto,
password: hashedPassword,
});
return this.userRepository.save(user);
}
// ========== 5. DAO 数据访问 ==========
// Entity 映射到数据库表
@Entity('users')
export class User {
@PrimaryGeneratedColumn()
id: number; // 数据库生成
@Column()
name: string; // 完整字段
@Column()
email: string; // 完整字段
@Column()
password: string; // 完整字段
@CreateDateColumn()
createdAt: Date; // 数据库生成
@UpdateDateColumn()
updatedAt: Date; // 数据库生成
}
// Repository 执行 SQL
INSERT INTO users (name, email, password, createdAt, updatedAt)
VALUES ('张三', 'zhangsan@example.com', 'hashed_password', NOW(), NOW());
// ========== 6. 数据库存储 ==========
// users 表
+----+------+---------------------------+----------------+-----------+---------------------+---------------------+
| id | name | email | password | isActive | createdAt | updatedAt |
+----+------+---------------------------+----------------+-----------+---------------------+---------------------+
| 1 | 张三 | zhangsan@example.com | hashed_****** | 1 | 2026-03-07 10:00:00 | 2026-03-07 10:00:00 |
+----+------+---------------------------+----------------+-----------+---------------------+---------------------+概念说明
ORM(Object-Relational Mapping,对象关系映射)是一种技术,用于在面向对象编程语言和关系型数据库之间建立映射关系。
ORM 的作用
ORM 的作用:
传统方式(SQL):
│
├── 编写 SQL 语句
│ └── INSERT INTO users (name, email) VALUES ('张三', '...')
│
├── 手动映射结果
│ └── const user = { id: result[0].id, name: result[0].name }
│
└── 数据库差异
└── MySQL、PostgreSQL 语法不同
ORM 方式:
│
├── 操作对象
│ └── const user = await userRepository.save({ name: '张三', email: '...' })
│
├── 自动映射
│ └── 返回 User 对象
│
└── 数据库无关
└── 切换数据库无需修改代码
优势:
• 不用写 SQL
• 代码可读性强
• 类型安全
• 数据库无关| ORM 库 | 支持数据库 | 特点 | 适用场景 |
|---|---|---|---|
| TypeORM | MySQL, PostgreSQL, MongoDB, SQLite | TypeScript 优先,装饰器语法 | NestJS 官方推荐 |
| Prisma | MySQL, PostgreSQL, MongoDB, SQLite | 类型安全,自动生成客户端 | 现代项目 |
| Sequelize | MySQL, PostgreSQL, SQLite | 成熟稳定,社区活跃 | 传统项目 |
| Mongoose | MongoDB | MongoDB 专用 | MongoDB 项目 |
TypeORM 示例
// ========== TypeORM ==========
// 定义实体
@Entity('users')
export class User {
@PrimaryGeneratedColumn()
id: number;
@Column()
name: string;
}
// 使用
const user = await this.userRepository.findOne({ where: { id: 1 } });Prisma 示例
// ========== Prisma ==========
// 定义 Schema(schema.prisma)
model User {
id Int @id @default(autoincrement())
name String
}
// 使用
const user = await prisma.user.findUnique({ where: { id: 1 } });// 推荐:DTO 与 Entity 分离
// Entity:数据库映射
@Entity('users')
export class User {
@PrimaryGeneratedColumn()
id: number;
@Column()
name: string;
@Column()
password: string; // 敏感字段
}
// DTO:API 接口
export class CreateUserDto {
@IsString()
name: string;
@IsString()
@MinLength(6)
password: string;
// 不包含 id
}
// Response DTO:响应过滤
export class ResponseUserDto {
id: number;
name: string;
@Exclude()
password: string; // 过滤敏感字段
}// 推荐:Repository 模式
// 自定义 Repository 方法(TypeORM 0.3+ 写法:继承 Repository 并通过 DataSource 构造;
// 0.2.x 的 @EntityRepository 装饰器已废弃)
import { Injectable } from '@nestjs/common';
import { DataSource, Repository } from 'typeorm';
@Injectable()
export class UsersRepository extends Repository<User> {
constructor(dataSource: DataSource) {
super(User, dataSource.manager);
}
async findActiveUsers(): Promise<User[]> {
return this.createQueryBuilder('user')
.where('user.isActive = :isActive', { isActive: true })
.getMany();
}
}
// Service 调用 Repository
@Injectable()
export class UsersService {
constructor(
@InjectRepository(User)
private readonly userRepository: Repository<User>,
) {}
async getActiveUsers() {
return this.userRepository.find({ where: { isActive: true } });
}
}分层设计原则:
│
├── Controller 层
│ ├── 只处理 HTTP 请求/响应
│ ├── 不包含业务逻辑
│ └── 调用 Service
│
├── Service 层
│ ├── 业务逻辑
│ ├── 数据处理
│ └── 调用 Repository
│
├── Repository 层(DAO)
│ ├── 数据库操作
│ ├── 不包含业务逻辑
│ └── 返回 Entity
│
└── DTO 层
├── 数据传输
├── 数据校验
└── 数据过滤| 问题 | 原因 | 解决方案 |
|---|---|---|
| DTO 验证不生效 | 未启用 ValidationPipe | 在 main.ts 中启用全局管道 |
| DTO 字段过多 | 未区分创建和更新 | 创建 CreateUserDto 和 UpdateUserDto |
| 密码泄露 | 响应返回敏感字段 | 使用 Response DTO 过滤字段 |
| 数据库操作重复 | 未使用 Repository | 提取公共方法到 Repository |
| SQL 注入 | 直接拼接 SQL | 使用 ORM 或参数化查询 |
| 类型不匹配 | DTO 与 Entity 字段不一致 | 明确区分 DTO 和 Entity |
| 循环依赖 | DTO 相互引用 | 使用 OmitType 或 PartialType |
| 更新覆盖字段 | 更新 DTO 包含所有字段 | 使用 PartialType 使字段可选 |
快速记忆:
MVC:
• Model = 业务逻辑 + 数据
• View = 用户界面
• Controller = 路由分发
DTO vs DAO:
• DTO = Transfer = 传输 = API 接口
• DAO = Access = 访问 = 数据库
关键词:
• DTO → 传输 → Controller 层
• DAO → 访问 → 数据库层学习路径:
│
├── 第一阶段:理解概念(1 天)
│ ├── 理解 MVC 架构
│ ├── 理解 DTO 和 DAO 区别
│ └── 理解 ORM 库的作用
│
├── 第二阶段:实践练习(3 天)
│ ├── 创建 Entity 和 DTO
│ ├── 实现数据验证
│ └── 实现 CRUD 操作
│
└── 第三阶段:深入应用(持续)
├── 学习 TypeORM 高级特性
├── 学习 Prisma
└── 性能优化思考题:
│
├── 为什么需要区分 DTO 和 Entity?
├── DTO 和 VO(Value Object)的区别是什么?
├── 如何设计合理的分层架构?
├── TypeORM 和 Prisma 如何选择?
└── 如何防止 SQL 注入?| 概念 | 全称 | 说明 | NestJS 实现 |
|---|---|---|---|
| MVC | Model-View-Controller | 架构模式 | Controller + Service + Entity |
| DTO | Data Transfer Object | 数据传输对象 | class-validator |
| DAO | Data Access Object | 数据访问对象 | TypeORM/Prisma |
| Entity | - | 数据库实体 | @Entity() |
| Repository | - | 数据仓库 | 继承 Repository(配合 DataSource) |
| ORM | Object-Relational Mapping | 对象关系映射 | TypeORM/Prisma |
笔记整理完成时间:2026-03-07
下一章节预告:NestJS 数据库实战(TypeORM)