V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
gzlock
V2EX  ›  Node.js

NestJS 怎么实现依靠 URL 前缀 '/admin' 和 '/app' 分别挂载两个不同 Module 从而实现区分 api 合集的逻辑?

  •  
  •   gzlock · 2020-06-25 07:22:20 +08:00 · 4418 次点击
    这是一个创建于 1613 天前的主题,其中的信息可能已经有所发展或是发生改变。

    目前的 NestJS 项目文件夹结构

    libs 中的

    • admin 是管理后台的 api 集合
    • app 是 app 客户端的 api 集合
    • db 是 admin 和 app 共用的 Sequelize 数据库的模型,Sequelize 对象的初始化在 db 里的 db.moudle.ts(admin 和 app 都会初始化一次,感觉很不爽)
    // main.js
    async function bootstrap () {
      const admin = await NestFactory.create(AdminModule)
    
      const app = await NestFactory.create(AppModule)
    
      await Promise.all([
        admin.init(),
        app.init(),
      ])
      await Promise.all([
        admin.listen(3000),
        app.listen(3001),
      ])
    }
    
    • 目前只能通过启动两个不同端口的 Nest Application 来实现这个需求(需要再用 Nginx 做 url 路径的反代)
    • 有好有不好吧
    • 好处是两个独立的 app 其中一个崩了也互不影响
    • 不好的地方是开发时每次 reload 后两个 app 需要重新同步数据库两次,不够完美

    发帖前也搜过这个需求,nestjs 的 github issue 也有类似的需求,但都被关闭了和不可用

    例如2018 年有 25 个👍的回答

    // 需要改成这样
    // main.ts
    async function bootstrap () {
      const server = express()
    
      const appFactory = new NestFactoryStatic()
      const app = await apiFactory.create(AppModule, new ExpressAdapter(server))
      app.setGlobalPrefix('/app')
      await app.init()
    
      const adminFactory = new NestFactoryStatic()
      const admin = await adminFactory.create(AdminModule, new ExpressAdapter(server))
      admin.setGlobalPrefix('/admin')
      await admin.init()
    
      http.createServer(server).listen(3000)
    }
    

    我尝试过了,admin 会覆盖掉 app 的路由,失败

    搜到一个nest-router,但最后 commit 是 12 个月前,我就没尝试

    有没有 NestJS 大佬指教一下该怎么实现

    nestjs 官方是不是不推荐这样弄?不然应该很容易实现的。

    20 条回复    2023-07-04 14:55:54 +08:00
    gzlock
        1
    gzlock  
    OP
       2020-06-25 07:27:34 +08:00
    @Livid node.js 的分区主题会导致 markdown 代码区域的文字出现黑色背景哎
    用“```ts”和“```typescript”开头都试过了,还是一样的效果
    gzlock
        2
    gzlock  
    OP
       2020-06-25 07:32:14 +08:00
    @gzlock #1 好吧是不支持 ts 或 typescript 样式的问题,改成 javascript 就可以了
    Livid
        3
    Livid  
    MOD
       2020-06-25 08:58:15 +08:00
    @gzlock 收到。我看一下如何可以支持 ts 。谢谢。
    noe132
        4
    noe132  
       2020-06-25 09:05:44 +08:00
    老哥你是不是误入歧途了。。
    Controller 类的 decorator 可以设置 url prefix 的
    https://docs.nestjs.com/controllers
    建议把 nest 文档看一看
    gzlock
        5
    gzlock  
    OP
       2020-06-25 09:47:56 +08:00 via iPhone
    我知道的,但我不能将 /app/* 下的所有接口都放在一个 controller 下吧
    想把例如 /app/user/*相关的放在一个 module 里
    那这个 module controller 的 path 是不是需要写绝对路径为 /app/user ?
    @noe132
    noe132
        6
    noe132  
       2020-06-25 10:03:27 +08:00
    B3C933r4qRb1HyrL
        7
    B3C933r4qRb1HyrL  
       2020-06-25 10:23:05 +08:00
    @gzlock
    1. AppModule 通过 setGlobalPrefix('/app'),这样 AppModule 路由就是 /app 了
    2.UserModule 里的 Controller 装饰器 @Controller('user')
    3.AppModule 里 Import UserModule
    这样 UserController 下的路由都是 /app/user/xx 了。
    path 不需要写绝对路径。
    不知道这样有没有解决你的问题。
    B3C933r4qRb1HyrL
        8
    B3C933r4qRb1HyrL  
       2020-06-25 10:28:26 +08:00
    如果不希望 api 是 /app/api,/app/admin 开头的话,app.setGlobalPrefix('/'),然后 appmodule 里引入其他子模块。
    比如 user module 里 @Controller('user'),那路径就是 host/user
    B3C933r4qRb1HyrL
        9
    B3C933r4qRb1HyrL  
       2020-06-25 10:30:17 +08:00
    FakerLeung
        10
    FakerLeung  
       2020-06-25 10:30:23 +08:00
    @gzlock #5
    不用吧。
    我现在的接口是
    /app1/xxx/yyy
    /app2/xxx/yyy
    app1 和 app2 都是两个独立的 module 。
    而且 app1 下又有 xxx,yyy,zzz 三个 module,这 3 个 module 也是独立出去的。只是每个 controller 又要再写一次 /app1/xxx 这样,感觉麻烦。
    gzlock
        11
    gzlock  
    OP
       2020-06-25 16:28:44 +08:00
    @FakerLeung #10

    @cuvii #7

    不使用 library 分开两种逻辑的话,admin 的 module 和 app 的 module 都挤在一个 src 文件夹里,会不会弄得 module 文件夹数量很庞大,也难以查找呢?
    B3C933r4qRb1HyrL
        12
    B3C933r4qRb1HyrL  
       2020-06-25 16:35:43 +08:00
    @gzlock 前面讲的可能有点乱,因为 nest.js 默认的 module 就是 app module 。
    你实际上是可以直接创建一个新的 module,例如 main module 来作为容器 module 。
    ```
    const main = await NestFactory.create(MainModule);
    main.setGlobalPrefix('/');
    ```
    然后 admin 的逻辑写在 admin module 里,app 的逻辑写在 app module 里,然后把这两个 module 导入到 main module 里就可以了。
    至于你说的 module 文件夹数量庞大,我觉得这就看你自己怎么处理文件夹结构了,用 cli 生成的 module 是默认都在 src 下的,你可以根据业务逻辑把 module 放到对应的功能模块下去。
    gzlock
        13
    gzlock  
    OP
       2020-06-25 17:30:47 +08:00
    @FakerLeung #10

    @cuvii #12

    刚刚实践了一下 https://github.com/gzlock/nest_js
    的确每个 controller 都需要写绝对路径😓,如果可以支持相对路径就真的完美了
    不过也解决了需要开两个单独 application 的问题
    FakerLeung
        14
    FakerLeung  
       2020-06-25 17:46:33 +08:00
    @gzlock #13
    对的,确实需要写绝对路径,我也在想如何才能在一个顶部的 module 中,写好了 prefix,下面的所有 module 中的所有 controller 都能继承这个 prefix 。
    B3C933r4qRb1HyrL
        15
    B3C933r4qRb1HyrL  
       2020-06-25 17:52:40 +08:00
    @gzlock 好吧,路由这块可能我的理解有问题,你可以看看 https://github.com/nestjsx/nest-router,不知道能不能解决你的问题,我个人感觉用 router 会更麻烦一点...
    gzlock
        16
    gzlock  
    OP
       2020-06-25 22:26:10 +08:00
    @FakerLeung #14 官方 issue 好像有讨论过这个问题,有人说因为 module 有可能被其他 module 引用所以 module 不适合放 path 参数
    FakerLeung
        17
    FakerLeung  
       2020-06-25 22:33:17 +08:00
    @gzlock #16
    谁说都有道理。
    kid740246048
        18
    kid740246048  
       2020-06-26 08:55:48 +08:00 via Android
    关注一波,我也觉得在 module 里面写死绝对路径不方便复用
    rikka
        19
    rikka  
       2020-11-03 15:05:36 +08:00   ❤️ 2
    遇到同样的问题,搜到这帖,然后自己看了看源码,解决如下

    加个装饰器函数
    //set-module-prefix.decorator.ts

    ```ts
    import { PATH_METADATA, MODULE_METADATA } from '@nestjs/common/constants'

    const { IMPORTS, CONTROLLERS } = MODULE_METADATA

    function resolveController (target, controllers = []) {
    controllers.push(...(Reflect.getMetadata(CONTROLLERS, target) ?? []))
    const imports = Reflect.getMetadata(IMPORTS, target)
    if (imports) {
    imports.forEach(module => {
    resolveController(module, controllers)
    })
    }
    return controllers
    }

    export function setModulePrefix (prefix:string) {
    return target => {
    resolveController(target).forEach(controller => {
    const path = Reflect.getMetadata(PATH_METADATA, controller)
    if (path) {
    Reflect.defineMetadata(PATH_METADATA, prefix + '/' + path, controller)
    }
    })
    }
    }
    ````

    使用,这样就自动给 AdminModule 下的所有控制器加上`admin`这个前缀了
    ```ts
    import { Module } from '@nestjs/common'
    import { AdminController } from './controllers/admin.controller'
    import { setModulePrefix } from '../decorators/set-module-prefix.decorator'

    @setModulePrefix('admin')
    @Module({
    imports: [
    ],
    controllers: [
    AdminController,
    ],
    providers: [
    ],
    })
    export class AdminModule {}

    ```
    cgyrock
        20
    cgyrock  
       2023-07-04 14:55:54 +08:00
    管理端 admin 和 C 端 customer 分别创建一个应用,可以独立启动,然后创建一个 monoapp 应用,导入这两个应用的根模块。两个应用的跟模块使用动态模块,接收一个 context 参数作为统一前缀,用官方的 RouteModule 来为所有子模块添加统一前缀

    monoapp: http://picbed.catfoodworks.com/WechatIMG522.jpeg
    admin: http://picbed.catfoodworks.com/WechatIMG523.jpeg
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2609 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 24ms · UTC 04:57 · PVG 12:57 · LAX 20:57 · JFK 23:57
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.