NestJS - 默认(通配符)路由?

7
在我的Angular应用程序中,如果我加载主页/,然后导航到/products,它可以正常工作(这是一个懒加载的模块)。但是如果现在重新加载页面,则浏览器会向服务器发出GET /products调用,结果返回404错误。
解决方案是发送index.html文件,Angular应用程序就可以正确运行了。所以在Express中,我使用app.all("*", (req,res) => { res.sendFile("index.html") })代码进行设置,它就可以正常工作。
如何在Nest中实现相同的功能?
Nest中有一个@All装饰器,但是每个控制器都处理给定组件内的子路由,例如@Controller("cats")将匹配/cats路径,因此如果我在该控制器中添加@All,它只会匹配/cats/*,而不是*
我真的需要为此创建一个完整的独立模块和控制器吗?那就是我所做的事情。
@Controller() // Matches "/"
export class GenericController {

    @All() // Matches "*" on all methods GET, POST...
    genericFunction(){
        console.log("Generic route reached")
    }
}

在我的主模块中:

@Module({
    imports: [
        ItemsModule, // Other routes like /items
        GenericModule, // Generic "*" route last
    ],
})

它可以运作,但似乎有点过度。这是正确的方法还是有更简单的技巧?


这里有一个好的样板:https://github.com/Innovic-io/angular-nestjs-rendering。你也可以使用 angular-cli。由于它使用了 angular-cli,前端应用程序使用单独的 express 实例,并且已经配置好处理未找到的请求... - Yerkon
哦,我一直在使用Angular CLI。但是你说的“前端应用程序使用单独的Express实例”是什么意思?Express是服务器端的。 - Jeremy Thille
我的意思是 angular-cli 使用单独的 express 实例。 - Yerkon
但那是 ng serve,不是吗?这是CLI的内置服务器。我没有使用 ng serve,我正在构建自己的服务器。无论如何,你的答案起作用了。谢谢! - Jeremy Thille
是的,你说得对。这是CLI内置的服务器,用于本地调试应用程序。当然,在生产中,您需要自己的服务器,在您的情况下使用NestJS - Yerkon
3个回答

6

因此,最好使用全局作用域的异常过滤器(global-scoped)。

async function bootstrap() {
  const app = await NestFactory.create(ApplicationModule);
  app.useGlobalFilters(new NotFoundExceptionFilter());
  await app.listen(3000);
}
bootstrap();

NotFoundExceptionFilter:

import { ExceptionFilter, Catch, NotFoundException } from '@nestjs/common';
import { HttpException } from '@nestjs/common';

@Catch(NotFoundException)
export class NotFoundExceptionFilter implements ExceptionFilter {
    catch(exception: HttpException, host: ArgumentsHost) {
        const ctx = host.switchToHttp();
        const response = ctx.getResponse();
        // here return `index.html`
    }
}

可能不起作用,稍后测试


3
这基本上是有效的,但问题是返回404的有效路由(例如GET /items/<无效ID>)将返回index.html而不是404。问题是如何处理不匹配的路由。 - TheRennen

3
你不需要创建一个单独的GenericModule。但是,GenericController是完全有效的,你的方法肯定是好的。问题在于你想使用这个通用路由来实现什么目的。如果处理“路由未找到”错误是你的要求,更好的选择是异常过滤器。

1
太荣幸了。就是他本人 :) 我想做的是 sendFile("index.html")。让我解释一下。在我的 Angular 应用程序中,如果我加载主页 / 然后导航到 /products,它可以正常工作(这是一个延迟加载的模块)。但是,如果现在重新加载页面,则浏览器会向服务器发出 GET /products 调用,结果是 404。解决方案是发送 index.html,Angular 应用程序就恢复正常了。所以在 Express 中,我执行 app.all("*", (req,res) => { res.sendFile("index.html") }),然后它就可以工作了。如何在没有模块的情况下实现 GenericController?我还没有想出来。 - Jeremy Thille
1
@JeremyThille 的意思类似于 https://angular.io/guide/deployment#routed-apps-must-fallback-to-indexhtml - Yerkon
@JeremyThille,我认为你的问题可以用你上面的评论来代替。 - Yerkon
这实际上是真的,我已经这样做了,谢谢你的建议 :) - Jeremy Thille

1
2022年的另一种解决方案。
我通过按照希望评估路由的顺序来指定路由来解决了这个问题。在我的实例中,我使用回退路由来捕获所有请求,但如果我需要自定义处理,我希望创建一个超越回退路由的路由。
然而,在AppController中定义一个捕获所有路由/api/:resource时,我发现回退路由会覆盖所有其他路由。
我的解决方案是在它自己的模块中定义回退路由,并确保将其附加到模块列表中。这样它就会最后创建,并且只会捕获穿透的内容。 #router.ts
import {RouterModule} from '@nestjs/core/router';
import {ContentBlockModule} from './content_block/content_block.module';
import {FallbackModule} from './fallback/fallback.module';

const APIRoutesWithFallbackRoute = RouterModule.register([
  {
    // This lets me avoid prepending my routes with /api prefixes 
    path: 'api',
    
    // Overload the /api/content_blocks route and foward it to the custom module
    children: [
      {
        path: 'content_blocks',
        module: ContentBlockModule,
      },
    ],
  },
  { //Fallback Route catches any post to /api/:resource
    path: 'api',
    module: FallbackModule,
  },
]);

#app.module 应用程序模块导入了后备模块。重要提示:确保FallbackModule是最后一个被声明的模块,否则它将覆盖在其后包含的路由。

import {Module} from '@nestjs/common';

import {AppService} from './app.service';
import {APIRoutesWithFallbackRoute} from './APIRoutesWithFallbackRoute';
import {ContentBlockModule} from './content_block/content_block.module';
import {FallbackModule} from './fallback/fallback.module';

// APIRoutes include first, Fallback Routes prepended.
@Module({
  imports: [APIRoutesWithFallbackRoute, ContentBlockModule, FallbackModule],
  controllers: [],
  providers: [AppService],
})
export class AppModule {}

FallbackController

import {Controller, Post, Req, Res} from '@nestjs/common';
import {defaultHandler} from 'ra-data-simple-prisma';

import {FallbackService} from './fallback.service';

@Controller()
export class FallbackController {
  constructor(private readonly prisma: FallbackService) {}

  @Post(':resource')
  fallback(@Req() req, @Res() res) {
    // return this.appService.getData();
    console.log('executing from the default fallback route');

    return defaultHandler(req, res, this.prisma);
  }
}

ContentBlockController

内容块控制器,这里仅包含完整性。

@Controller()
export class ContentBlockController {
  constructor(
    private readonly contentBlockService: ContentBlockService,
    private readonly prisma: PrismaService,
  ) {}

  @Post()
  async create(
    @Body() contentBlock: content_blocks,
    @Req() req: Request,
    @Res() res: Response,
  ): Promise<void> {
    console.log('executing from the resource specific route');
 
    // lean on my service to do heavy business logic
    const [model, values] = await this.contentBlockService.createContentBlock(
      contentBlock,
    );

    // inject custom logic...
    const alteredRequest: CreateRequest = {
      ...req,
      body: {
        ...req.body,
        params: {
          data: values,
        },
      },
    };
   

    return createHandler(alteredRequest, res, model);
  } 
}

使用这个系统,我能够定义一个单一的路由来处理90%的路由,以便将我的Prisma模型暴露给我的私有API。如果需要自定义逻辑,我就拥有完全的控制权。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接