NestJS 怎么实现依靠 URL 前缀 '/admin' 和 '/app' 分别挂载两个不同 Module 从而实现区分 api 合集的逻辑? - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
gzlock

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

  •  
  •   gzlock 2020 年 6 月 25 日 4956 次点击
    这是一个创建于 2126 天前的主题,其中的信息可能已经有所发展或是发生改变。

    目前的 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 年 6 月 25 日
    @Livid node.js 的分区主题会导致 markdown 代码区域的文字出现黑色背景哎
    用“```ts”和“```typescript”开头都试过了,还是一样的效果
    gzlock
        2
    gzlock  
    OP
       2020 年 6 月 25 日
    @gzlock #1 好吧是不支持 ts 或 typescript 样式的问题,改成 Javascript 就可以了
    Livid
        3
    Livid  
    MOD
    PRO
       2020 年 6 月 25 日
    @gzlock 收到。我看一下如何可以支持 ts 。谢谢。
    noe132
        4
    noe132  
       2020 年 6 月 25 日
    老哥你是不是误入歧途了。。
    Controller 类的 decorator 可以设置 url prefix 的
    https://docs.nestjs.com/controllers
    建议把 nest 文档看一看
    gzlock
        5
    gzlock  
    OP
       2020 年 6 月 25 日 via iPhone
    我知道的,但我不能将 /app/* 下的所有接口都放在一个 controller 下吧
    想把例如 /app/user/*相关的放在一个 module 里
    那这个 module controller 的 path 是不是需要写绝对路径为 /app/user ?
    @noe132
    noe132
        6
    noe132  
       2020 年 6 月 25 日
    B3C933r4qRb1HyrL
        7
    B3C933r4qRb1HyrL  
       2020 年 6 月 25 日
    @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 年 6 月 25 日
    如果不希望 api 是 /app/api,/app/admin 开头的话,app.setGlobalPrefix('/'),然后 appmodule 里引入其他子模块。
    比如 user module 里 @Controller('user'),那路径就是 host/user
    B3C933r4qRb1HyrL
        9
    B3C933r4qRb1HyrL  
       2020 年 6 月 25 日
    FakerLeung
        10
    FakerLeung  
       2020 年 6 月 25 日
    @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 年 6 月 25 日
    @FakerLeung #10

    @cuvii #7

    不使用 library 分开两种逻辑的话,admin 的 module 和 app 的 module 都挤在一个 src 文件夹里,会不会弄得 module 文件夹数量很庞大,也难以查找呢?
    B3C933r4qRb1HyrL
        12
    B3C933r4qRb1HyrL  
       2020 年 6 月 25 日
    @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 年 6 月 25 日
    @FakerLeung #10

    @cuvii #12

    刚刚实践了一下 https://github.com/gzlock/nest_js
    的确每个 controller 都需要写绝对路径,如果可以支持相对路径就真的完美了
    不过也解决了需要开两个单独 application 的问题
    FakerLeung
        14
    FakerLeung  
       2020 年 6 月 25 日
    @gzlock #13
    对的,确实需要写绝对路径,我也在想如何才能在一个顶部的 module 中,写好了 prefix,下面的所有 module 中的所有 controller 都能继承这个 prefix 。
    B3C933r4qRb1HyrL
        15
    B3C933r4qRb1HyrL  
       2020 年 6月 25 日
    @gzlock 好吧,路由这块可能我的理解有问题,你可以看看 https://github.com/nestjsx/nest-router,不知道能不能解决你的问题,我个人感觉用 router 会更麻烦一点...
    gzlock
        16
    gzlock  
    OP
       2020 年 6 月 25 日
    @FakerLeung #14 官方 issue 好像有讨论过这个问题,有人说因为 module 有可能被其他 module 引用所以 module 不适合放 path 参数
    FakerLeung
        17
    FakerLeung  
       2020 年 6 月 25 日
    @gzlock #16
    谁说都有道理。
    kid740246048
        18
    kid740246048  
       2020 年 6 月 26 日 via Android
    关注一波,我也觉得在 module 里面写死绝对路径不方便复用
    rikka
        19
    rikka  
       2020 年 11 月 3 日   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 年 7 月 4 日
    管理端 admin 和 C 端 customer 分别创建一个应用,可以独立启动,然后创建一个 monoapp 应用,导入这两个应用的根模块。两个应用的跟模块使用动态模块,接收一个 context 参数作为统一前缀,用官方的 RouteModule 来为所有子模块添加统一前缀

    monoapp: http://picbed.catfoodworks.com/WechatIMG522.jpeg
    admin: http://picbed.catfoodworks.com/WechatIMG523.jpeg
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     928 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 52ms UTC 20:45 PVG 04:45 LAX 13:45 JFK 16:45
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86