本文是「从零打造 AI 全栈应用」系列第 八 篇。
前面我们已经完成了:
- 后端核心架构(NestJS + Prisma)
- 前端性能优化(图片懒加载)
这一篇,我们来解决一个非常容易被忽略、但几乎所有真实项目都会遇到的问题:
静态资源服务器(Static Assets Server) 。
如果你做的是:
- 用户头像上传
- 文章封面图 / 缩略图
- AI 生成图片 / 文件下载
那么你一定绕不开:
“这些图片到底该怎么访问?”
一、先分清:动态资源 vs 静态资源
这是后端初学者最容易混淆的地方。
1️⃣ 动态资源(Dynamic Resource)
- 由 Controller + Service 提供
- 每次请求都会进入业务逻辑
- 通常是 JSON 数据
例如:
GET /api/posts
GET /api/posts/1
2️⃣ 静态资源(Static Resource)
- 已经存在于服务器磁盘
- 不需要 Controller
- 不需要 Service
- 直接返回文件内容
例如:
/uploads/avatar.png
/uploads/post-cover.jpg
把静态资源再走 Controller,是一种严重的设计错误。
二、为什么后端一定要提供静态资源服务?
很多同学会问:
“不是有 OSS / CDN 吗?为什么还要自己做?”
答案是:
- 本地开发阶段
- 中小项目 / 内网项目
- MVP / Demo / 初期产品
👉 后端直出静态资源是成本最低、效率最高的方案。
等你真正需要 CDN,再无缝迁移即可。
三、NestJS 如何开启静态资源服务器?
NestJS 底层默认基于 Express,因此天然支持静态资源服务。
我们只需要:
把 Nest 当成一个“增强版 Express”。
四、uploads 目录:静态资源的根
在真实项目中,一个非常常见的约定是:
project-root/
├─ src/
├─ uploads/
│ ├─ avatars/
│ ├─ posts/
│ └─ files/
└─ prisma/
uploads:所有用户上传内容- 不进入 git(通常加入
.gitignore) - 由静态服务器直接托管
五、在 main.ts 中启用静态资源服务
1️⃣ 关键前提:使用 NestExpressApplication
import { NestExpressApplication } from '@nestjs/platform-express';
const app = await NestFactory.create<NestExpressApplication>(AppModule);
这一步的意义是:
让 Nest 拥有 Express 的能力。
2️⃣ 使用 app.useStaticAssets
import { join } from 'path';
app.useStaticAssets(join(process.cwd(), 'uploads'), {
prefix: '/uploads',
});
这一行代码做了什么?
- 指定磁盘目录:
<项目根目录>/uploads - 映射访问路径:
/uploads/*
结果就是:
http://localhost:3000/uploads/avatar.png
无需 Controller,无需路由定义。
六、为什么要用 process.cwd()?
这是一个非常专业、但常被忽略的细节。
process.cwd() 是什么?
Current Working Directory(当前工作目录)
它指的是:
- 你执行
node/npm run start时所在的目录
为什么不用 __dirname?
__dirname指向编译后的dist目录- 在生产环境中路径极易出错
而:
join(process.cwd(), 'uploads')
可以保证:
- 本地开发
- 打包后运行
- Docker 容器
路径始终正确。
这是“写得跑”和“写得对”的区别。
七、完整 main.ts 关键配置回顾
const app = await NestFactory.create<NestExpressApplication>(AppModule, {
cors: true,
});
app.setGlobalPrefix('api');
app.useGlobalPipes(new ValidationPipe({
whitelist: true,
forbidNonWhitelisted: true,
transform: true,
}));
app.useStaticAssets(join(process.cwd(), 'uploads'), {
prefix: '/uploads',
});
这套配置体现的是:
- API 与静态资源彻底分离
- 数据接口全部走
/api/* - 文件资源全部走
/uploads/*
这是非常标准的企业级约定。
八、面试官视角:这一块你要怎么讲?
如果我问你:
“你们项目里的图片是怎么访问的?”
一个合格的回答应该包括:
- 静态资源不走 Controller
- NestJS 底层是 Express
- useStaticAssets 的作用
- uploads 目录约定
- process.cwd() 的原因
能讲到这一层,说明你不是只会 CRUD,而是理解服务器运行模型。
最后
静态资源服务看起来很简单,但:
- 设计错了,后期迁移成本极高
- 路径一乱,前后端都会崩
这一篇,是在帮你把“容易被忽略的地基”打牢 。