从零打造 AI 全栈应用(八):NestJS 静态资源服务器的正确打开方式

10 阅读3分钟

本文是「从零打造 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,而是理解服务器运行模型。


最后

静态资源服务看起来很简单,但:

  • 设计错了,后期迁移成本极高
  • 路径一乱,前后端都会崩

这一篇,是在帮你把“容易被忽略的地基”打牢 。