NestJS 笔记项目文档
一份完整的 NestJS 后端项目实战文档,包含技术选型、分层目录结构、数据库表设计与 API 接口清单、统一错误码规范,以及从启动到调用的完整流程说明与核心代码解析。
项目简介
一个基于 NestJS 的笔记后端系统,支持用户注册登录、笔记增删改查、图片视频上传。
技术栈
| 技术 | 版本 | 用途 |
|---|---|---|
| NestJS | 11 | 后端框架 |
| TypeORM | 1.1 | ORM,操作数据库 |
| MySQL | - | 关系型数据库 |
| JWT (HS256) | - | 登录认证 |
| bcrypt | 6 | 密码加密 |
| Multer | 2 | 文件上传 |
| class-validator | 0.15 | 数据验证 |
项目结构
src/├── users/ # 用户模块│ ├── user.entity.ts # 用户实体│ ├── dto/│ │ └── create-user.dto.ts # 注册 DTO│ ├── users.controller.ts # 用户控制器│ ├── users.service.ts # 用户服务│ └── users.module.ts # 用户模块├── auth/ # 认证模块│ ├── dto/│ │ └── login.dto.ts # 登录 DTO│ ├── auth.controller.ts # 认证控制器│ ├── auth.service.ts # 认证服务│ ├── jwt.strategy.ts # JWT 策略│ ├── jwt-auth.guard.ts # JWT 守卫│ └── auth.module.ts # 认证模块├── notes/ # 笔记模块│ ├── note.entity.ts # 笔记实体│ ├── dto/│ │ ├── create-note.dto.ts # 创建笔记 DTO│ │ └── update-note.dto.ts # 修改笔记 DTO│ ├── notes.controller.ts # 笔记控制器│ ├── notes.service.ts # 笔记服务│ └── notes.module.ts # 笔记模块├── upload/ # 上传模块│ ├── upload.config.ts # Multer 配置│ ├── upload.controller.ts # 上传控制器│ └── upload.module.ts # 上传模块├── logger.middleware.ts # 请求日志中间件├── app.module.ts # 根模块└── main.ts # 入口文件数据库设计
user 表
| 字段 | 类型 | 说明 |
|---|---|---|
| id | int (PK, 自增) | 主键 |
| username | varchar (unique) | 用户名,唯一 |
| password | varchar | 密码(bcrypt 加密) |
| varchar | 邮箱 | |
| createTime | datetime | 创建时间 |
note 表
| 字段 | 类型 | 说明 |
|---|---|---|
| id | int (PK, 自增) | 主键 |
| title | varchar(200) | 标题 |
| content | text | 文字内容 |
| images | varchar | 图片 URL |
| videos | varchar | 视频 URL |
| userId | int (FK) | 外键,关联 user.id |
| createTime | datetime | 创建时间 |
| updateTime | datetime | 更新时间 |
关系
- user 1:N note(一个用户有多条笔记)
API 接口文档
接口地址
http://1.94.33.51:3000
一、Users 模块(无需登录)
POST /users/register — 注册用户
请求体(JSON):
{ "username": "zhangsan", "password": "123456", "email": "zhangsan@test.com"}验证规则:
| 字段 | 规则 |
|---|---|
| username | 2-20 个字符 |
| password | 至少 6 个字符 |
| 合法邮箱格式 |
成功返回(201):
{ "username": "zhangsan", "email": "zhangsan@test.com", "id": 1, "createTime": "2026-08-10T..."}错误返回:
- 409 — 用户名已存在
- 400 — 数据验证不通过
二、Auth 模块(无需登录)
POST /auth/login — 登录
请求体(JSON):
{ "username": "zhangsan", "password": "123456"}成功返回(200):
{ "access_token": "eyJhbGciOiJIUzI1NiIs...", "user": { "id": 1, "username": "zhangsan", "email": "zhangsan@test.com" }}错误返回:
- 401 — 用户名或密码错误
三、Notes 模块(需要登录)
所有接口的 Header 必须带:
Authorization: Bearer <token>
1. POST /notes — 创建笔记
请求体(JSON):
{ "title": "我的笔记", "content": "笔记内容", "images": "/uploads/images/xxx.jpg", "videos": "/uploads/videos/xxx.mp4"}images 和 videos 可选,先上传文件拿到 URL 再填入。
成功返回(201):
{ "id": 1, "title": "我的笔记", "content": "笔记内容", "images": "/uploads/images/xxx.jpg", "videos": null, "userId": 1, "createTime": "2026-08-10T...", "updateTime": "2026-08-10T..."}2. GET /notes — 查询所有笔记
返回当前登录用户的所有笔记,按创建时间倒序。
成功返回(200):
[ { "id": 2, "title": "第二篇", "content": "...", "userId": 1, "createTime": "...", "updateTime": "..." }]3. GET /notes/:id — 查询单个笔记
路径参数: id = 笔记 ID
成功返回(200): 单个笔记对象
错误返回:
- 404 — 笔记不存在
- 403 — 不是自己的笔记
4. PUT /notes/:id — 修改笔记
路径参数: id = 笔记 ID
请求体(JSON,字段都可选):
{ "title": "修改后的标题"}成功返回(200): 修改后的笔记对象
5. DELETE /notes/:id — 删除笔记
成功返回(200):
{ "message": "删除成功"}四、Upload 模块(需要登录,form-data 格式)
Header 必须带:
Authorization: Bearer <token>
1. POST /upload/image — 上传图片
Body → form-data:
| Key | 类型 | Value |
|---|---|---|
| file | File | 选择图片文件 |
限制: jpg / jpeg / png / gif,最大 5MB
成功返回(201):
{ "message": "上传成功", "url": "/uploads/images/1723341234-987654321.jpg", "filename": "1723341234-987654321.jpg", "size": 102400}2. POST /upload/video — 上传视频
Body → form-data:
| Key | 类型 | Value |
|---|---|---|
| file | File | 选择视频文件 |
限制: mp4 / avi / mov,最大 50MB
成功返回(201):
{ "message": "上传成功", "url": "/uploads/videos/1723341234-987654321.mp4", "filename": "1723341234-987654321.mp4", "size": 5242880}五、静态文件访问(无需登录)
GET /uploads/... — 访问上传的文件
直接在浏览器打开:
http://服务器IP:3000/uploads/images/文件名.jpg错误码汇总
| 状态码 | 含义 | 触发场景 |
|---|---|---|
| 200 | 成功 | GET / PUT / DELETE 成功 |
| 201 | 创建成功 | POST 成功 |
| 400 | 参数错误 | 数据验证不通过、JSON 格式错误 |
| 401 | 未授权 | 没带 Token / Token 过期 / Token 无效 |
| 403 | 禁止访问 | 操作别人的笔记 |
| 404 | 不存在 | 笔记不存在 |
| 409 | 冲突 | 用户名已存在 |
| 500 | 服务器错误 | 代码异常 |
完整使用流程
1. POST /users/register → 注册账号2. POST /auth/login → 登录,拿到 token3. POST /upload/image → 上传图片,拿到图片 URL(可选)4. POST /upload/video → 上传视频,拿到视频 URL(可选)5. POST /notes → 创建笔记,填入图片/视频 URL6. GET /notes → 查看所有笔记7. GET /notes/:id → 查看笔记详情8. PUT /notes/:id → 修改笔记9. DELETE /notes/:id → 删除笔记核心代码说明
JWT 认证流程
注册:明文密码 → bcrypt.hash(密码, 10) → 密文存数据库登录:明文密码 → bcrypt.compare(明文, 密文) → 通过 → jwtService.sign({ sub: userId }) → 返回 Token后续请求:前端带 Token → JwtAuthGuard 用 secret 验证 → 通过 → 放行JWT Guard 实现
项目使用自定义 Guard(非 Passport),实现 CanActivate 接口:
- 从 Authorization 头提取 Bearer Token
- 用 JwtService.verify 验证 Token
- 验证通过后将
{ id: payload.sub, username: payload.username }挂到 request.user - Controller 通过
@Request() req获取req.user.id
权限控制
- 所有笔记接口都加了
@UseGuards(JwtAuthGuard) - Service 层每个方法都接收 userId 参数
- 查询/修改/删除时检查
note.userId === userId,不匹配抛 403
文件上传
- 使用 Multer diskStorage 存储到本地
- 文件名:时间戳 + 随机数 + 原始扩展名
- 图片存
uploads/images/,视频存uploads/videos/ - 通过
useStaticAssets配置静态文件访问
开发过程中遇到的问题及解决方案
| 问题 | 原因 | 解决方案 |
|---|---|---|
| pnpm install 失败 | 网络问题 | 换淘宝镜像源 registry.npmmirror.com |
| npm/pnpm 混用报错 | lock 文件冲突 | 统一使用 pnpm |
| ERR_PNPM_IGNORED_BUILDS | bcrypt 需要编译原生代码 | pnpm approve-builds 批准构建 |
| delete user.password 报错 | TS 严格模式不允许 delete 非可选属性 | 改用解构 const { password, ...result } = user |
| JWT 401 未授权 | Passport 策略注册问题 | 改用自定义 Guard 实现 CanActivate |
| 外键约束失败 userId 为空 | Guard 中 payload.sub 未映射为 id | request.user = { id: payload.sub } |
| Express.Multer.File 类型报错 | 缺少类型定义包 | pnpm add -D @types/multer |
后续可扩展方向
- 环境变量管理 — 使用 @nestjs/config,把数据库密码、JWT 密钥放到 .env 文件
- 分页查询 — 笔记列表加
@Query('page')和@Query('limit')分页 - 笔记搜索 — 按标题/内容搜索,使用 QueryBuilder 或全文索引
- 云存储 — 文件上传改为 OSS/COS 云存储,不存本地
- Swagger 文档 — 安装 @nestjs/swagger 自动生成接口文档
- 部署上线 — PM2 管理进程,Nginx 反向代理
- 单元测试 — 编写 spec.ts 测试文件