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'], // 这些路由不会添加前缀
});
相关推荐
今夜星辉灿烂5 天前
nestjs微服务-系列5
后端·nestjs
一生躺平的仔9 天前
NestJS Swagger 使用说明文档
nestjs
plusone9 天前
【Nest指北系列-源码】(七)请求生命周期
nestjs
小山不高10 天前
nest中如何对typeorm 的repo设置总的center
nestjs
bug_marker23 天前
BullMq sleep job是否会阻塞worker中的其他的jobs
nestjs
濮水大叔1 个月前
快来玩玩便捷、高效的Demo练习场
typescript·nodejs·nestjs
木西1 个月前
Nest.js实战:构建聊天室的群聊与私聊模块
前端·后端·nestjs
LannyChung1 个月前
NestJS定时器之@Cron
nestjs