Here is a use-case:
1) I have
@Controller('users')
class UsersController ...
2) Then I have a separate router that handles specific users information, like
@Controller('users/products')
class ProductsController ...
Same applies to URL REST versioning when you need to define couple controllers under the same path prefix:
/v1/users
/v1/orders
/v1/products
This particular case can be resolved with app.setGlobalPrefix('api/v1');
But then we need to support couple API versions, and some of the routes should be reused from the previous version
/v1/users
/v2/orders
/v2/products
How can that be resolved?
Hi @rychkog,
It's not possible now and honestly, I have no idea if there's a good solution to resolve this.
@kamilmysliwiec this looks like a major use case.
Hey guys !
Manage API versions seems to be an API management feature.
I don't think that a software architecture framework should manage this...
It smells more to be an infrastructure problem.
I don't know if I'm right but you could build / use a Load Balancer or Nginx front of your application servers that expose different API versions, and use traffic rule redirection to use the right instance (or docker container) regarding the version in path.
In that case:
Hi @ThomRick, Nice angle to look at it, for API versioning.
However I think @rychkog proposed feature is relevant, there is a perfect use-case where you want to do something like /v1/users/products
. It's Synonymous to Angular's Child Routes. Where you need products
to own it's own controller
I've worked on several express apps that use controller trees like this. Child routes know nothing of their parent routers, which allows moving controllers around or even applying the same controllers to different routes, etc.
So instead of some controller binding to /some/path/foo
, it can just be /foo
, but bound as a child to whatever provided /some/path
. For example:
const express = require('express');
const app = express();
const foo = express.Router();
foo.get('/', (req, res) => res.send('Child route'));
const somePath = express.Router();
somePath.get('/', (req, res) => res.send('Parent route'));
somePath.use('/foo', foo);
app.use('/some/path', somePath);
app.listen(3000);
... and so on.
I have a very large JS express project that has a lot of routes set up like this using express' Router, and if nest
could provide the same sort of functionality, moving the project over to typescript (as daunting of a rewrite as it will be) would be very appealing.
Hey ! I know a similar user case, to resolve this problem, we use policies, when a user arrived on it the user controller for example, the policies has it own logic and fetch the user to put in the request object, like that is a part of the context. Then, when a user want to fetch /users/:id/product/:id the same process is used like that you have access to the user in the product controller because it becoming a part of the context and then it's like hierarchy controller.
I hope that can help you.
@ThomRick @adrien2p great answers, 100% agree, I couldn't describe it better. In express routers create some kind of 'routers tree'. With Nest every controller is a router in fact, but it's not possible to create nested controllers.
@kamilmysliwiec Would it be possible to define route prefix for whole module ? Something like
@Module({
routePrefix: '/api',
modules: [...],
controllers: [CatsController]
})
export class ApiModule{}
Then the controller
@Controller("/cats")
export class CatsController {
@Get()
async list(){
// ...
}
}
whose list
action would be executed for GET /api/cats
.
Since modules are already hierarchical, one could use that to build hierarchical routes. Child modules would append their prefix to parent.
@Module({
routePrefix: '/api',
modules: [ApiV1Module]
})
export class ApiModule{}
@Module({
routePrefix: '/v1',
controllers: [CatsController]
})
export class ApiV1Module{}
Then the controller's list
action would be executed for GET /api/v1/cats
.
Edit: please note I used API versioning just as a simplest example of prefixing larger parts of ones application. The issue is relevant for variety of other use cases as well.
Hi @br0wn,
I don't think that @Module
decorator is a good place for routePrefix
property since modules are completely independent of http server and may be used without it.
modules are completely independent of http server
@kamilmysliwiec I'm not sure exactly what you meant by this, could you please elaborate? Since controllers are specified in @Module
decorator and they provide routing I was under assumption that modules are in some extent aware of controllers.
My feature request would be to group controllers under common route prefix. Module seems to be prefect place for that as it groups a number of controllers together.
At the moment I'm doing this by having multiple instances of nest app:
async function bootstrap() {
const server = express();
const config = require('../../etc/config.js');
const apiFactory = new NestFactoryStatic();
const api = await apiFactory.create(ApiModule, server);
api.setGlobalPrefix("/api/v1");
await api.init();
const adminFactory = new NestFactoryStatic();
const admin = await adminFactory.create(AdminModule, server);
admin.setGlobalPrefix("/admin");
await admin.init();
http.createServer(server).listen(config.server.port);
}
bootstrap();
I wonder what is wrong with handling the versions as follows?
@ApiUseTags('cats')
@Controller()
export class CatsController {
constructor(private readonly catsService: CatsService) {}
@Post('/v2/cats')
async createV2(@Body() createCatDtoV2: CreateCatDtoV2) {
this.catsService.createV2(createCatDtoV2);
}
@Post('/v1/cats')
async create(@Body() createCatDto: CreateCatDto) {
this.catsService.create(createCatDto);
}
@Get('/v1/cats')
async findAll(): Promise<Cat[]> {
return this.catsService.findAll();
}
@Get('/v2/cats')
async findAll_v2(): Promise<CatV2[]> {
return this.catsService.findAll_v2();
}
}
This thread has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.
Most helpful comment
@kamilmysliwiec this looks like a major use case.