Reputation: 26390
In my Angular app, if I load the home page /
and then navigate to, say, /products
, it works fine (it's a lazy-loaded module). But if now I reload the page, the browser makes a GET /products
call to the server, which results in a 404.
The solution is to send index.html
and the Angular app is back on rails. So in Express I do app.all("*", (req,res) => { res.sendFile("index.html") })
and it works.
How to do the same thing in Nest?
There is a @All
decorator, but each controller in a given component handles a subroute, for instance @Controller("cats")
will match /cats
routes, so if I add @All
in this controller, it will match only /cats/*
, not *
.
Must I really create a whole separate module with a controller, just for this? That's what I did
@Controller() // Matches "/"
export class GenericController {
@All() // Matches "*" on all methods GET, POST...
genericFunction(){
console.log("Generic route reached")
}
}
And in my main module :
@Module({
imports: [
ItemsModule, // Other routes like /items
GenericModule, // Generic "*" route last
],
})
It works, but it seems overkill. Is this the way to go or is there a simpler trick?
Upvotes: 8
Views: 13418
Reputation: 103
An alternative answer in 2022.
I've solved this by specifying the routes in the order I want them evaluated. In my instance I am using a fallback route to catch all requests, but if I need custom processing I want to create a route which superceeds the fallback route.
However, in defining a catchall route /api/:resource
in the AppController, I found the fallback route would overwrite all other routes.
My solution to this is to define the fallback route in it's own module and ensure that it is appended to the list of modules. This way it is created last and will only catch what falls through.
#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
App module imports the fallback module. Important Ensure FallbackModule
is the last module to be declaired or it will overwrite routes that are included after it.
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 The content block controller, included here for completeness.
@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);
}
}
Using this system I am able to define a single route to handle 90% of the routes necessary to expose my Prisma models to my private API. And if I need custom logic I have full control.
Upvotes: 3
Reputation: 4798
So, will be best to use global-scoped
exception filter.
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`
}
}
Maybe it will not work, will test later
Upvotes: 6
Reputation: 9178
You don't need to create a separated GenericModule
. However, GenericController
is fully valid and you approach is definitely a good one. The question is rather what would you like to achieve using this generic route. If handling "Route not found" error is your requirement, a better choice is an exception filter.
Upvotes: 4