I have an issue when I need to support multiple versions of an API and its routes while running within the same service. Currently it could be accomplished with a prefix on a controller that would end up in the route URI, but I have a requirement where it needs to be done via "Accept Header" versioning.
The solution I'm envisioning would:
Users would need to do 2 things to enable versioning:
After that, they would be able to create versioned controllers by developing new controllers that are configured for the version they want to make it available for.
I know this has been mentioned in several other issues (https://github.com/nestjs/nest/issues/1268, https://github.com/nestjs/nest/issues/2654), but those mainly focused on URI versioning, which consumers can already use. However, my company has a specific requirement of being able to version a (REST) API via Request headers (i.e. Accept: application/json;v=1
). While I know that URI versioning can be handled on the consumer end fairly easily, the others cannot.
I've gotten a POC created in a fork, it currently only supports Accept header versioning.
I will add a comment with the technical design i've created for implementation of the entire feature. I do have some open questions on what consumption would look like that I wanted to get people's thoughts on. Also, I'm more than happy to contribute this feature if the design is approved!
Goal:
Support 3 types of versioning
High Level Flow (Scenarios):
GET /
.
- Request is made to v1. Request gets routed to v1.
- Request is made to v2. Request gets routed to v2.
- Request is made to v3. 404 Response.Open Questions for edge cases:
Versioning will be added at a Controller level, using the decorator options. This will set a metadata value “version” with whatever the consumer configured. This will be used w/in the Router when resolving.
Open Question: What should the key be called: version
, versions
, or something else?
Ex: One Version
@Controller({
version: '2'
})
Ex: Two Versions
@Controller({
version: [‘1, ’2’]
})
In addition to the Decorator config, the consumer would need to specify the versioning type, with potential extra config.
Versioning types:
Open Question: Where should the versioning type config be
1. App Module Decorator config?
2. Nest Factory Config?
For all versioning types, there will be changes made to RouteResolver and RouteExplorer that will detect the versioned routes and process the accordingly.
The processing would be done by statefully grouping routes between the controllers, so that similar routes in different controllers can be referenced in the same function.
this/is/a/prefix/v2/route
)Accept: application/json;v=1
=> key is “v”)Versioning will be added at a Controller level, using the decorator options. This will set a metadata value “version” with whatever the consumer configured. This will be used w/in the Router when resolving.
Open Question: What should the key be called: version, versions, or something else?
What's the difference between this new, proposed property vs explicitly adding this prefix to the path in the @Controller()
?
Versioning will be added at a Controller level, using the decorator options. This will set a metadata value “version” with whatever the consumer configured. This will be used w/in the Router when resolving.
Open Question: What should the key be called: version, versions, or something else?What's the difference between this new, proposed property vs explicitly adding this prefix to the path in the
@Controller()
?
@kamilmysliwiec For URI versioning, it would be functionally the same As setting the prefix. My thought process would be to offer both for consistency with their versioning feature.
Header based versioning is very important feature IMO.
It is much more convenient for the clients and easier to maintain. We may want to just bump version of the one endpoint to v1.1 so there is no point to create whole prefixed controller etc.
When using headers we could just do similar stuff like Net Core do, so just add the ApiVersion decorator on the Get1_1 so the resolver based on the provided header would go to one of the implementations.
I generally think that API versioning could be heavily benefit from the Net Core MVC Versioning which is just great.
You may take a look at this or similar articles about it:
https://dev.to/99darshan/restful-web-api-versioning-with-asp-net-core-1e8g
any update about this?
I looked at the link that @murbanowicz shared and really liked some of the patterns that .NET uses. I've incorporated some of the patterns in that doc and have made some small revisions to the design, see below for the revised design.
As far as contribution goes, as long as @kamilmysliwiec (or anyone else that's interested) has no objections, I'm good to contribute this feature. I'm working with my company's open source office so I can contribute it through them, otherwise it's going to take longer. Given that the Holidays coming up, I'm hoping to have everything finished by early January.
Click to expand!
Support 3 types of versioning:
this/is/a/prefix/v2/route
)Note: URI versioning can already be accomplished with the controller prefix
option. It would be included within this feature for purely a consistency pattern. Either the controller prefix, or version pattern could be used.
Accept: application/json;v=1
=> key is v
)Versioning will be added at a Controller level, using the decorator options. This will set a metadata value version
with whatever the consumer configured. This will be used w/in the Router when resolving.
There will also be an option to specify a controller as "Version Neutral" which indicates that it does not depend on the version (i.e. a Health Check Endpoint). A controller tagged with Version Neutral will work for any version passed, or if no version is passed.
Ex: One Version
@Controller({
version: '2'
})
Ex: Multiple Versions
@Controller({
version: ['1', '2']
})
Ex. Version Neutral
@Controller({
version: VERSION_NEUTRAL // VERSION_NEUTRAL will be an exported const (Symbol?)
})
For specific handlers within a controller, a new decorator (@Version
) will be created allow that one handler to go to a specific version.
This will override whatever version is set at the controller level.
It will allow for finer granularity for versioning and reduce potential code duplication
Ex.
@Controller({
version: '1'
})
class TestController {
...
@Get('/test')
test_v1() {
...
}
@Version('2')
@Get('/test')
test_v2() {
...
}
}
In addition to the Decorator config, the consumer would need to specify the versioning type with their corresponding required config.
The consumer would specify this in their main.ts
(or equivalent) file using a new class method on the NestApplication
class.
The new method would be called enableVersioning
and would take an options object that would have several properties:
type
: Specifies the type of the versioning (uri, header, media type).defaultVersion
: Optional prop, if present, it would allow incoming requests that don't have the version specified to get mapped to a default version.These options would be extended in the future to allow for more features. An example of a future features could be Reporting API Versions (I don't plan on including that in the initial contribution).
Ex: URI
const app = await NestFactory.create(AppModule);
app.enableVersioning({
type: VERSION.URI
})
Ex: Header
const app = await NestFactory.create(AppModule);
app.enableVersioning({
type: VERSION.HEADER
header: 'X-version'
})
Ex: Media Type (Accept)
const app = await NestFactory.create(AppModule);
app.enableVersioning({
type: VERSION.MEDIA_TYPE
key: 'v'
})
For all versioning types, there will be changes made to RouteResolver and RouteExplorer that will detect the versioned routes and process the accordingly.
The processing would be done by statefully grouping routes between the controllers, so that similar routes in different controllers can be referenced in the same function, which will use the versioning configuration to determine which route the request will be routed to.
GET /
.GET /test
/test
for any version. 404 Response.GET /neutral
/neutral
for any version. Response from controller./neutral
that does not specify version. Response from controller.GET /
. v1 is specified as the default@Controller
decorator@Version
decoratorenableVersioning
to the NestApplication class
Most helpful comment
NestJS Versioning Design
Goal:
Support 3 types of versioning
High Level Flow (Scenarios):
GET /
. - Request is made to v1. Request gets routed to v1. - Request is made to v2. Request gets routed to v2. - Request is made to v3. 404 Response.Open Questions for edge cases:
Adding Versioning
Versioning will be added at a Controller level, using the decorator options. This will set a metadata value “version” with whatever the consumer configured. This will be used w/in the Router when resolving.
Open Question: What should the key be called:
version
,versions
, or something else?Ex: One Version
Ex: Two Versions
In addition to the Decorator config, the consumer would need to specify the versioning type, with potential extra config.
Versioning types:
Open Question: Where should the versioning type config be
1. App Module Decorator config?
2. Nest Factory Config?
Route Resolving
For all versioning types, there will be changes made to RouteResolver and RouteExplorer that will detect the versioned routes and process the accordingly.
The processing would be done by statefully grouping routes between the controllers, so that similar routes in different controllers can be referenced in the same function.
URI Versioning
this/is/a/prefix/v2/route
)Custom Header/Accept Header
Accept: application/json;v=1
=> key is “v”)