Nestjs 常见问题和最佳解决方案(一)

一、如何 serve 静态资源

若是 koa 我们可以通过 koa-static

ts 复制代码
const serve = require('koa-static');

const buildFolder = path.resolve(__dirname, '../client/dist');

app.use(serve(buildFolder));

nestjs 需要通过 module 引入。

解法 @nestjs/serve-static

ts 复制代码
// nestjs-server\src\client-app.module.ts
import { Module } from '@nestjs/common';
import path from 'node:path';
import { ServeStaticModule } from '@nestjs/serve-static';

const buildFolder = path.resolve(__dirname, '../../client/dist');

@Module({
  imports: [
    ServeStaticModule.forRoot({
      rootPath: buildFolder,
      renderPath: '/',
    }),
    
    // 你可以 serve 多个
    ServeStaticModule.forRoot({
      rootPath: mediaFolder,
      serveRoot: '/media',
    }),
  ],
})
export class ClientAppModule {}

记得注入到 \src\app.module.ts 的 imports

  • rootPath:被 serve 的本地文件目录,即文件在磁盘上的目录。
  • serveRoot: 浏览器访问时加在路径前面的虚拟 前缀。如设置 /static
    • 访问 http://localhost:3000/static/js/app.js
    • 实际读取的是 dist/js/app.js
  • renderPath: 指定浏览器访问的 URL 路径模式 ,决定哪些请求会被当作静态文件(或 SPA 的 index.html)处理。通过哪个 url path 进入可以访问当静态资源。如果是 /build 则需要从 http:localhost:xxx/build 进入才会 serve。默认 * 即任意 404 path 进入都会返回 dist/index.html。我们设置 / 则只有根目录才会 serve。否则返回:
json 复制代码
{
    "message": "Cannot GET /xxx",
    "error": "Not Found",
    "statusCode": 404
}

serveRoot 用途详解

serveRoot 用途

我们本地开发前端代码 client port 为 3001,服务端 port 为 3002,/api/images 返回的图片 url 列表,比如 ['foo.png', ...] 此时如果前端代码直接将其当做 img 的 src 会找不到图片。因为浏览器将相对路径解析成 http://localhost:3001/foo.png,而图片是被服务器 serve 的并不在客户端代码内,正确路径:http://localhost:3002/foo.png。有两种解法:

解决方案 1:前端解决
ts 复制代码
srcs = list.map(src => `http://localhost:3002/` + src)

适配生产环境:

ts 复制代码
// 拼接当前 host + 根据 process.env.NODE_ENV 只有当本地才加 3002
const withoutPort = location.origin.replace(/:\d+$/, '')
const port = isDev ? `:3002` : ''
srcs = list.map(src => `${withoutPort}${port}/` + src)

优点代码量少 & 纯前端。缺点写死了服务端 port。

解决方案 2:前端 proxy

渲染的时候增加"临时"前缀 /photos/

ts 复制代码
const prefix = isDev ? '/photos/' : ''
srcs = list.map(src => prefix + src)

上述代码并未指定 port 而是通过 dev server 的 proxy 来将其 proxy 到服务端,当然得 rewrite 将"临时"前缀去除,因为实际上服务端是没有这个前缀的。

ts 复制代码
'/photos': {
    target: `http://localhost:${SERVER_PORT}`,
    changeOrigin: true,
    rewrite: (path) => path.replace(/^\/photos/, ''),
  },

完整代码:client\vite.config.ts

ts 复制代码
import react from '@vitejs/plugin-react';
import { defineConfig } from 'vite';

// pnpm dev:server port
const SERVER_PORT = 6834;

// https://vite.dev/config/
export default defineConfig({
  plugins: [react()],
  server: {
    proxy: {
      '/api': {
        target: `http://localhost:${SERVER_PORT}`,
        changeOrigin: true,
      },
      '/photos': {
        target: `http://localhost:${SERVER_PORT}`,
        changeOrigin: true,
        rewrite: (path) => path.replace(/^\/photos/, ''),
      },
      '/videos': {
        target: `http://localhost:${SERVER_PORT}`,
        changeOrigin: true,
        rewrite: (path) => path.replace(/^\/videos/, ''),
      },
    },
  },
});
解决方案 3:前端+服务端:proxy + serveRoot

之前我们讲过:

serveRoot: 浏览器访问时加在路径前面的虚拟 前缀。如设置 /static

  • 访问 http://localhost:3000/static/js/app.js
  • 实际读取的是 dist/js/app.js

也就是无需前端 proxy rewrite 因为服务端可以自动去除。 这样前端渲染代码无需改,proxy 也无需加。

第一步:前端渲染时

无需区分环境统一加前缀。

diff 复制代码
- const prefix = isDev ? '/photos/' : ''
+ const prefix = '/photos/'
srcs = list.map(src => prefix + src)
第二步:前端 proxy
diff 复制代码
'/photos': {
    target: `http://localhost:${SERVER_PORT}`,
    changeOrigin: true,
-   rewrite: (path) => path.replace(/^\/photos/, ''),
},
第三步:服务端配置 serverRoot

nestjs-server\src\client-app.module.ts

ts 复制代码
import { Module } from '@nestjs/common';
import path from 'node:path';
import { ServeStaticModule } from '@nestjs/serve-static';
import { mediaFolder } from './utils/program';

const buildFolder = path.resolve(__dirname, '../../client/dist');

@Module({
  imports: [
    // 图片资源
    ServeStaticModule.forRoot({
      rootPath: mediaFolder,
      serverRoot: '/public/'
    }),
  ],
})
export class ClientAppModule {}

这样开发环境访问 localhost:3001/photos/foo.png 将 proxy 到 localhost:3002/photos/foo.png。

生产环境访问 localhost:3002/photos/foo.png @nestjs/serve-static 将自动对应到 localhost:3002/foo.png 即正确访问到磁盘上面的图片资源。

二、如何统一给 api 增加前缀

nestjs 如何给所有的 api 统一增加前缀。比如 /api/images, /api/videos 我们知道可以通过 @Controller('/api/videos') 但是这样改太麻烦了 如果有多个controller 得逐个修改

解法 app.setGlobalPrefix('api');

nestjs-server\src\main.ts

ts 复制代码
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);

  app.setGlobalPrefix('api');

  await app.listen(process.env.PORT ?? 3000);
}

// eslint-disable-next-line @typescript-eslint/no-floating-promises
bootstrap();

如果需要排除路径可以:

ts 复制代码
app.setGlobalPrefix('api', {
  exclude: ['health', 'metrics'], // 这些路由不会添加前缀
});
相关推荐
亮子AI16 天前
【NestJS】为什么return不返回客户端?
前端·javascript·git·nestjs
小p17 天前
nestjs学习2:利用typescript改写express服务
nestjs
Eric_见嘉23 天前
NestJS 🧑‍🍳 厨子必修课(九):API 文档 Swagger
前端·后端·nestjs
XiaoYu20021 个月前
第3章 Nest.js拦截器
前端·ai编程·nestjs
XiaoYu20021 个月前
第2章 Nest.js入门
前端·ai编程·nestjs
实习生小黄1 个月前
NestJS 调试方案
后端·nestjs
当时只道寻常1 个月前
NestJS 如何配置环境变量
nestjs
濮水大叔2 个月前
VonaJS是如何做到文件级别精确HMR(热更新)的?
typescript·node.js·nestjs
ovensi2 个月前
告别笨重的 ELK,拥抱轻量级 PLG:NestJS 日志监控实战指南
nestjs