Skip to content

Commit 070b81d

Browse files
committed
feat(common/core): api versioning
adding the ability to have different versions of routes to support changing applications that still need to support legacy consumers closes nestjs#5065
1 parent f95d37f commit 070b81d

39 files changed

Lines changed: 20435 additions & 19 deletions

packages/common/constants.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,3 +28,4 @@ export const HEADERS_METADATA = '__headers__';
2828
export const REDIRECT_METADATA = '__redirect__';
2929
export const RESPONSE_PASSTHROUGH_METADATA = '__responsePassthrough__';
3030
export const SSE_METADATA = '__sse__';
31+
export const VERSION_METADATA = '__version__';

packages/common/decorators/core/controller.decorator.ts

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,18 @@ import {
22
HOST_METADATA,
33
PATH_METADATA,
44
SCOPE_OPTIONS_METADATA,
5+
VERSION_METADATA,
56
} from '../../constants';
67
import { ScopeOptions } from '../../interfaces/scope-options.interface';
8+
import { VersionOptions } from '../../interfaces/version-options.interface';
79
import { isString, isUndefined } from '../../utils/shared.utils';
810

911
/**
1012
* Interface defining options that can be passed to `@Controller()` decorator
1113
*
1214
* @publicApi
1315
*/
14-
export interface ControllerOptions extends ScopeOptions {
16+
export interface ControllerOptions extends ScopeOptions, VersionOptions {
1517
/**
1618
* Specifies an optional `route path prefix`. The prefix is pre-pended to the
1719
* path specified in any request decorator in the class.
@@ -97,10 +99,14 @@ export function Controller(prefix: string | string[]): ClassDecorator;
9799
* more details.
98100
* - `prefix` - string that defines a `route path prefix`. The prefix
99101
* is pre-pended to the path specified in any request decorator in the class.
102+
* - `version` - string, array of strings, or Symbol that defines the version
103+
* of all routes in the class. [See Versioning](https://docs.nestjs.com/techniques/versioning)
104+
* for more details.
100105
*
101106
* @see [Routing](https://docs.nestjs.com/controllers#routing)
102107
* @see [Controllers](https://docs.nestjs.com/controllers)
103108
* @see [Microservices](https://docs.nestjs.com/microservices/basics#request-response)
109+
* @see [Versioning](https://docs.nestjs.com/techniques/versioning)
104110
*
105111
* @publicApi
106112
*/
@@ -128,11 +134,15 @@ export function Controller(options: ControllerOptions): ClassDecorator;
128134
* more details.
129135
* - `prefix` - string that defines a `route path prefix`. The prefix
130136
* is pre-pended to the path specified in any request decorator in the class.
137+
* - `version` - string, array of strings, or Symbol that defines the version
138+
* of all routes in the class. [See Versioning](https://docs.nestjs.com/techniques/versioning)
139+
* for more details.
131140
*
132141
* @see [Routing](https://docs.nestjs.com/controllers#routing)
133142
* @see [Controllers](https://docs.nestjs.com/controllers)
134143
* @see [Microservices](https://docs.nestjs.com/microservices/basics#request-response)
135144
* @see [Scope](https://docs.nestjs.com/fundamentals/injection-scopes#usage)
145+
* @see [Versioning](https://docs.nestjs.com/techniques/versioning)
136146
*
137147
* @publicApi
138148
*/
@@ -141,19 +151,23 @@ export function Controller(
141151
): ClassDecorator {
142152
const defaultPath = '/';
143153

144-
const [path, host, scopeOptions] = isUndefined(prefixOrOptions)
145-
? [defaultPath, undefined, undefined]
154+
const [path, host, scopeOptions, versionOptions] = isUndefined(
155+
prefixOrOptions,
156+
)
157+
? [defaultPath, undefined, undefined, undefined]
146158
: isString(prefixOrOptions) || Array.isArray(prefixOrOptions)
147-
? [prefixOrOptions, undefined, undefined]
159+
? [prefixOrOptions, undefined, undefined, undefined]
148160
: [
149161
prefixOrOptions.path || defaultPath,
150162
prefixOrOptions.host,
151163
{ scope: prefixOrOptions.scope },
164+
prefixOrOptions.version,
152165
];
153166

154167
return (target: object) => {
155168
Reflect.defineMetadata(PATH_METADATA, path, target);
156169
Reflect.defineMetadata(HOST_METADATA, host, target);
157170
Reflect.defineMetadata(SCOPE_OPTIONS_METADATA, scopeOptions, target);
171+
Reflect.defineMetadata(VERSION_METADATA, versionOptions, target);
158172
};
159173
}

packages/common/decorators/core/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,4 @@ export * from './use-guards.decorator';
1111
export * from './use-interceptors.decorator';
1212
export * from './use-pipes.decorator';
1313
export * from './apply-decorators';
14+
export * from './version.decorator';
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { VERSION_METADATA } from '../../constants';
2+
import { VersionValue } from '../../interfaces/version-options.interface';
3+
4+
/**
5+
* Sets the version of the endpoint to the passed version
6+
*
7+
* @publicApi
8+
*/
9+
export function Version(version: VersionValue): MethodDecorator {
10+
return (
11+
target: any,
12+
key: string | symbol,
13+
descriptor: TypedPropertyDescriptor<any>,
14+
) => {
15+
Reflect.defineMetadata(VERSION_METADATA, version, descriptor.value);
16+
return descriptor;
17+
};
18+
}

packages/common/enums/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
export * from './request-method.enum';
22
export * from './http-status.enum';
33
export * from './shutdown-signal.enum';
4+
export * from './version-type.enum';
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
/**
2+
* @publicApi
3+
*/
4+
export enum VersioningType {
5+
URI,
6+
HEADER,
7+
MEDIA_TYPE,
8+
}

packages/common/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@ export {
5252
Type,
5353
ValidationError,
5454
ValueProvider,
55+
VersioningOptions,
56+
VERSION_NEUTRAL,
5557
WebSocketAdapter,
5658
WsExceptionFilter,
5759
WsMessageHandler,

packages/common/interfaces/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,4 @@ export * from './nest-microservice.interface';
2525
export * from './scope-options.interface';
2626
export * from './type.interface';
2727
export * from './websockets/web-socket-adapter.interface';
28+
export * from './version-options.interface';

packages/common/interfaces/nest-application.interface.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
} from './index';
1414
import { INestApplicationContext } from './nest-application-context.interface';
1515
import { WebSocketAdapter } from './websockets/web-socket-adapter.interface';
16+
import { VersioningOptions } from './version-options.interface';
1617

1718
/**
1819
* Interface defining the core NestApplication object.
@@ -35,6 +36,14 @@ export interface INestApplication extends INestApplicationContext {
3536
*/
3637
enableCors(options?: CorsOptions | CorsOptionsDelegate<any>): void;
3738

39+
/**
40+
* Enables Versioning for the application.
41+
*
42+
* @param {VersioningOptions} options
43+
* @returns {this}
44+
*/
45+
enableVersioning(options: VersioningOptions): this;
46+
3847
/**
3948
* Starts the application.
4049
*
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import { VersioningType } from '../enums/version-type.enum';
2+
3+
/**
4+
* Indicates that this will work for any version passed in the request, or no version.
5+
*
6+
* @publicApi
7+
*/
8+
export const VERSION_NEUTRAL = Symbol('VERSION_NEUTRAL');
9+
10+
export type VersionValue = string | string[] | typeof VERSION_NEUTRAL;
11+
12+
/**
13+
* @publicApi
14+
*/
15+
export interface VersionOptions {
16+
/**
17+
* Specifies an optional API Version. When configured, methods
18+
* withing the controller will only be routed if the request version
19+
* matches the specified value.
20+
*
21+
* @see [Versioning](https://docs.nestjs.com/techniques/versioning)
22+
*/
23+
version?: VersionValue;
24+
}
25+
26+
export interface HeaderVersioningOptions {
27+
type: VersioningType.HEADER;
28+
/**
29+
* The name of the Request Header that contains the version.
30+
*/
31+
header: string;
32+
}
33+
34+
export interface UriVersioningOptions {
35+
type: VersioningType.URI;
36+
/**
37+
* Optional prefix that will prepend the version within the URI.
38+
*
39+
* Defaults to `v`.
40+
*
41+
* Ex. Assuming a version of `1`, for `/api/v1/route`, `v` is the prefix.
42+
*/
43+
prefix?: string | false;
44+
}
45+
46+
export interface MediaTypeVersioningOptions {
47+
type: VersioningType.MEDIA_TYPE;
48+
/**
49+
* The key within the Media Type Header to determine the version from.
50+
*
51+
* Ex. For `application/json;v=1`, the key is `v=`.
52+
*/
53+
key: string;
54+
}
55+
56+
/**
57+
* @publicApi
58+
*/
59+
export type VersioningOptions =
60+
| HeaderVersioningOptions
61+
| UriVersioningOptions
62+
| MediaTypeVersioningOptions;

0 commit comments

Comments
 (0)