Closed StickNitro closed 1 year ago
I like the idea of inmplementing this in routing-controllers. What way would you want to send the version information?
Would like to request adding support for API versions, similar to ASP.Net Core
Some of us doesn't know ASP.Net or Spring. Could you describe your proposal with more details? How the API should look and how it should behave? 😉
You can implement versioning in Express at the moment by adding two different routes that will use two different callback methods to service the request and using URL path based version, query string versioning or HTTP header base versioning. I believe that all three could be supported and the appropriate configuration and decorators provided to configure routing-controllers
I would propose that the routing-controllers configuration support the ability to enable versions and allow services to specify say a default version along with other metadata.
useExpressServer(app, {
routePrefix: "/api",
controllers: [
__dirname + "/controllers/**/*{.js,.ts}"
],
**apiVersioning: {
enabled: true,
assumeDefaultVersionWhenUnspecified: true|false,
defaultApiVersion: "1.0",
reportApiVersions: true|false,
supportedVersions: ["~1.0", "^2.0"]
}**
});
I would then propose the addition of a decorator that could be added to either a class or an individual method, the decorator could be called ApiVersion
Applied for the whole controller (based on the consumer providing the version in the HTTP Header)
/*
HTTP Head
X-API-Version: 1.1
*/
@ApiVersion("1.0")
@ApiVersion("1.1")
@JsonController("/hello")
export class HelloWorldController {
}
But this could also be achieved using the URL path method
/*
http://localhost/api/hello/1.0/world/
*/
@ApiVersion("1.0")
@JsonController("/hello/:apiVersion/world")
export class HelloWorldv1Controller {
}
/*
http://localhost/api/hello/2.0/world/
*/
@ApiVersion("2.0")
@JsonController("/hello/:apiVersion")
export class HelloWorldv2Controller {
}
Applied at the controller method level
@ApiVersion("~1.0") // could even support NPM version styles as well
@JsonController("/hello")
export class HelloWorldController {
@ApiVersion("1.0")
@Get("/:id")
async readOne10(...) {
return "Hello from version 1.0";
}
@ApiVersion("1.1")
@Get("/:id")
async readOne11(...) {
return "Hello from version 1.1";
}
}
There could also be a decorator to allow versioning of a single method
@ApiVersion("1.0")
@ApiVersion("1.1", {deprecated: true})
@ApiVersion("2.0")
@JsonController("/hello")
export class HelloWorldController {
@Get("/:id")
async readOne(...) {
return "Hello World (v1)";
}
**@MapToApiVersion("2.0")**
@Get("/:id")
async readOnev2(...) {
return "Hello World (v2)";
}
}
Routing controllers would then register the routes in express, consumers would opt-in to a version by specifying a HTTP header in the request (or using URL path based, etc.), if assumeDefaultVersionWhenUnspecified
is true and no version is specified in the request then routing-controllers would forward the request to the defaultApiVersion
route. If an invalid version is specified of no route exists then a 404 can be thrown (this could include a message stating that the version is not supported)
If reportApiVersions
is true then a header can be included in the response to return the list of supported versions for that route as defined by the ApiVersion
decorator and the MapToApiVersion
decorator along with a header of any deprecated versions. The output HTTP headers could be:
Response Headers
api-supported-versions: 1.0, 1.1, 2.0
api-deprecated-version: 1.1
Not sure whether this would be relevant but you could also have a decorator similar to the @CurrentUser
decorator that will allow a service method to inject the version as a parameter into the controller method, eg. @CurrentVersion() apiVersion: string
Hope that all makes sense :)
I'm not talking about whether this feature should be or not to be developed.
I don't think it's a good idea to contain multiple versions of one API into one branch. You should use nginx to proxy different version of servers into different path.
Different version of APIs may have different logics and data structures. Put them in the same branch, one day will be a mess.
@StickNitro Ok, I get the idea but I have some doubts about details.
could even support NPM version styles as well
This is semver actually. But I don't find it useful in this case: When you have v1.0 api and introduces some changes in v1.1, it can't modify the existing routes due to no breaking changes. It can only add new routes or deprecate the old in favour of new. If you need to change something in the routes, like some resource has changed query options names or returned entity, it needs to be v2.0 api as breaking changes.
So my question is what are the rules on mapping request to actions. You gave an example:
/*
HTTP Head
X-API-Version: 1.1
*/
@ApiVersion("1.0")
@ApiVersion("1.1")
@JsonController("/hello")
export class HelloWorldController {
}
But 1.1 shouldn't be compatible with 1.0? So all 1.0 routes are available for 1.1 API request? Or we can overwrite the older route (it may be hard to implement and considered bad practice)
How 2.0 should work then? Only actions decorated with @ApiVersion("2.0")
are available, right?
About details:
There could also be a decorator to allow versioning of a single method
We don't need separate decorator, @ApiVersion
can be universal and applied on class and method level.
enabled: true,
Not needed - config object means true, just like validation
works now.
supportedVersions: ["~1.0", "^2.0"]
Shouldn't this be auto generated from decorators?
assumeDefaultVersionWhenUnspecified: true|false, defaultApiVersion: "1.0",
It should be just defaultApiVersion
option - if not provided, we can assume that assumeDefaultVersionWhenUnspecified
is false.
But this could also be achieved using the URL path method
I am against dynamic api version in path, it should be global prefix like we can specify /api
. It can check then the path, the query string and header in the end.
Not sure whether this would be relevant but you could also have a decorator similar to the @CurrentUser decorator that will allow a service method to inject the version as a parameter into the controller method, eg. @CurrentVersion() apiVersion: string
Yes, it might help someone and it's not hard to implement. However I'm not sure about universal route with dynamic handling the api version.
I don't think it's a good idea to contain multiple versions of one API into one branch. You should use nginx to proxy different version of servers into different path.
Different version of APIs may have different logics and data structures. Put them in the same branch, one day will be a mess.
So you have your public API and need to change something in one route for your new version of mobile app. And you propose to copy paste the whole code and db and then make changes and map to nginx? Isn't it simpler to have additional route for new api version and the rest of the app would work normally?
@19majkel94 I will try to answer all your points as best I can
could even support NPM version styles as well
This was just a thought I had when submitting the request and not essential, the basic principle around the use of Major.Minor was that API consumers could "opt-in" to a version of the API. This would allow the API to develop at a different pace to any connected UI, the UI would then be able to "opt-in" to an available version. The example I provided (in particular the ability to have two methods for the same route but for differing versions (e.g. 1.0 and 1.1)) was following a minor release to the API to add a non-breaking change that a UI could choose to opt-in to when ready (although I appreciate this could also conceivably be classed as a breaking change). The idea being that consumers could always use the latest API version available through the defaultApiVersion
but could equally lock the UI to a specific version of the API
There could also be a decorator to allow versioning of a single method
That sounds fine
supportedVersions: ["~1.0", "^2.0"] Shouldn't this be auto generated from decorators?
Yes it could be rather than manually configured, makes more sense if can be automated
assumeDefaultVersionWhenUnspecified: true|false, defaultApiVersion: "1.0", It should be just defaultApiVersion option - if not provided, we can assume that assumeDefaultVersionWhenUnspecified is false.
Makes sense
But this could also be achieved using the URL path method I am against dynamic api version in path, it should be global prefix like we can specify /api. It can check then the path, the query string and header in the end.
I agree that providing the version in the URL path is not a good way to achieve this, again this was an idea at the time of requesting and is primarily based around a consumer opting into a version as opposed to being configured on the API
The premise here and in my comment at the beginning being that both the API and the UI may have different teams working on them and be working at different speeds. The API team would be able to release new features (be they major changes or minor enhancements). The UI team(s) would be able to include features in the UI and opt-in to new API features when ready simply by providing the correct version in the HTTP header
Not sure whether this would be relevant but you could also have a decorator similar to the >>@currentuser decorator that will allow a service method to inject the version as a parameter into the >>controller method, eg. @currentversion() apiVersion: string Yes, it might help someone and it's not hard to implement. However I'm not sure about universal route >with dynamic handling the api version.
I see this as exposing the version provided in the HTTP header, perhaps this is the way (and possibly the best way) of providing which version of an API you are calling.
At my working place we have a Proxy API created with NodeJS & ExpressJS. In the beginning we used api-level versioning, which we quickly found out was not flexible enough.
So I began searching for an alternative solution and found this library which allowed us to version per route:
With this library, we could now have two versions of the same route:
router.get(1, '/hello', function(req, res, next) {
...
});
router.get(42, '/hello', function(req, res, next) {
...
});
This way, our clients (we have 3) could consume the new version of a route at different paces and avoided breaking changes.
Based on what we went through at my working place, I agree with the proposal from @StickNitro.
If this feature request will be implemented I hope you will decide to support versioning at the method level because this will give the optimal level of flexibility.
@19majkel94
Looking in the project code, I think that it might be a lot more easy to add the API version to the "action" decorator. For instance, with @Get :
export function Get(route?: string|RegExp, version?: string = ''): Function {
return function (object: Object, methodName: string) {
getMetadataArgsStorage().actions.push({
type: "get",
target: object.constructor,
method: methodName,
route: route,
version: version
});
};
}
Then deal with this new version attribut to register the action.
Usage :
@Get("/:id", "1.0")
async readOne10(...) {
return "Hello from version 1.0";
}
@Get("/:id", "1.1")
async readOne11(...) {
return "Hello from version 1.1";
}
To manage multiple versions for one route, it could be done with a versions array.
Hello. Would be nice to have this feature. Cast @jotamorais
Stale issue message
Closing this as stale.
If the issue still persists, you may open a new Q&A in the discussions tab and someone from the community may be able to help.
This issue has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.
Would like to request adding support for API versions, similar to ASP.Net Core where you could specify the default version in configuration and then using a decorator on a controller or controller method specify which version it supports, including being able to map say two or more different versions of a get method to the same get.