@shadow-library/fastify 1.1.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -14,6 +14,7 @@ A powerful TypeScript-first Fastify wrapper featuring decorator-based routing, a
14
14
  - 📊 **Type Safety**: Full TypeScript support with schema generation
15
15
  - 🎨 **Templating Ready**: Built-in support for templating engines
16
16
  - ⚡ **Dynamic Module**: Configurable module with `forRoot()` and `forRootAsync()` methods
17
+ - 🔢 **API Versioning**: Built-in support for prefix-based API versioning
17
18
 
18
19
  ## Installation
19
20
 
@@ -129,6 +130,7 @@ bootstrap();
129
130
  @Delete(path?: string) // DELETE requests
130
131
  @Options(path?: string) // OPTIONS requests
131
132
  @Head(path?: string) // HEAD requests
133
+ @Version(version: number) // Set API version for the route
132
134
  ```
133
135
 
134
136
  #### Parameter Decorators
@@ -183,6 +185,205 @@ export class ProtectedController {
183
185
  }
184
186
  ```
185
187
 
188
+ ### API Versioning
189
+
190
+ The framework provides a powerful versioning system that allows you to version your APIs using URL path prefixes. This is useful for maintaining multiple versions of your API simultaneously.
191
+
192
+ #### Enabling Versioning
193
+
194
+ To enable versioning, set `prefixVersioning: true` in your module configuration:
195
+
196
+ ```typescript
197
+ @Module({
198
+ imports: [
199
+ FastifyModule.forRoot({
200
+ controllers: [UserController],
201
+ port: 3000,
202
+
203
+ // Enable prefix-based versioning
204
+ prefixVersioning: true,
205
+ }),
206
+ ],
207
+ })
208
+ export class AppModule {}
209
+ ```
210
+
211
+ #### Using the @Version Decorator
212
+
213
+ Use the `@Version` decorator on your controllers to specify the API version:
214
+
215
+ ```typescript
216
+ import { HttpController, Get, Post, Version, Body } from '@shadow-library/fastify';
217
+
218
+ @HttpController('/api/users')
219
+ @Version(1)
220
+ export class UserV1Controller {
221
+ @Get()
222
+ async getUsers() {
223
+ return [{ id: 1, name: 'John Doe' }];
224
+ }
225
+ }
226
+
227
+ @HttpController('/api/users')
228
+ @Version(2)
229
+ export class UserV2Controller {
230
+ @Get()
231
+ async getUsers() {
232
+ // v2 with additional fields
233
+ return [{ id: 1, name: 'John Doe', email: 'john@example.com', createdAt: new Date() }];
234
+ }
235
+
236
+ @Post()
237
+ async createUser(@Body() userData: CreateUserDto) {
238
+ // New features in v2
239
+ return { id: 2, ...userData };
240
+ }
241
+ }
242
+ ```
243
+
244
+ **Result**:
245
+
246
+ - v1 endpoints: `GET /v1/api/users`
247
+ - v2 endpoints: `GET /v2/api/users`, `POST /v2/api/users`
248
+
249
+ #### Default Version
250
+
251
+ If versioning is enabled but no `@Version` decorator is specified, routes default to `v1`:
252
+
253
+ ```typescript
254
+ @HttpController('/api/products')
255
+ export class ProductController {
256
+ @Get()
257
+ async getProducts() {
258
+ return [];
259
+ }
260
+ }
261
+ // Results in: GET /v1/api/products
262
+ ```
263
+
264
+ #### Method-Level Versioning
265
+
266
+ You can also apply versioning at the method level for more granular control:
267
+
268
+ ```typescript
269
+ @HttpController('/api/data')
270
+ export class DataController {
271
+ @Get('/stats')
272
+ @Version(1)
273
+ async getStatsV1() {
274
+ return { totalUsers: 100 };
275
+ }
276
+
277
+ @Get('/stats')
278
+ @Version(2)
279
+ async getStatsV2() {
280
+ return { totalUsers: 100, activeUsers: 75, newUsers: 10 };
281
+ }
282
+
283
+ @Get('/info')
284
+ async getInfo() {
285
+ // This defaults to v1 when versioning is enabled
286
+ return { version: 'v1' };
287
+ }
288
+ }
289
+ ```
290
+
291
+ **Result**:
292
+
293
+ - `GET /v1/api/data/stats` → `getStatsV1()`
294
+ - `GET /v2/api/data/stats` → `getStatsV2()`
295
+ - `GET /v1/api/data/info` → `getInfo()`
296
+
297
+ #### Versioning Best Practices
298
+
299
+ 1. **Maintain Backward Compatibility**: Keep older versions running while you migrate clients to newer versions
300
+ 2. **Version Breaking Changes**: Only increment versions for breaking changes in your API
301
+ 3. **Document Version Differences**: Clearly document what changes between versions
302
+ 4. **Deprecation Strategy**: Communicate deprecation timelines for older API versions
303
+ 5. **Consistent Versioning**: Use consistent version numbers across related endpoints
304
+
305
+ #### Example: Complete Versioned API
306
+
307
+ ```typescript
308
+ import { Module } from '@shadow-library/app';
309
+ import { FastifyModule, HttpController, Get, Post, Version } from '@shadow-library/fastify';
310
+
311
+ // Version 1 Controllers
312
+ @HttpController('/api/users')
313
+ @Version(1)
314
+ class UserV1Controller {
315
+ @Get()
316
+ async list() {
317
+ return [{ id: 1, name: 'John' }];
318
+ }
319
+ }
320
+
321
+ @HttpController('/api/posts')
322
+ @Version(1)
323
+ class PostV1Controller {
324
+ @Get()
325
+ async list() {
326
+ return [{ id: 1, title: 'Hello' }];
327
+ }
328
+ }
329
+
330
+ // Version 2 Controllers - with enhanced features
331
+ @HttpController('/api/users')
332
+ @Version(2)
333
+ class UserV2Controller {
334
+ @Get()
335
+ async list() {
336
+ return [{ id: 1, name: 'John', email: 'john@example.com', role: 'admin' }];
337
+ }
338
+
339
+ @Post('/bulk')
340
+ async bulkCreate(@Body() users: CreateUserDto[]) {
341
+ // New feature in v2
342
+ return { created: users.length };
343
+ }
344
+ }
345
+
346
+ @HttpController('/api/posts')
347
+ @Version(2)
348
+ class PostV2Controller {
349
+ @Get()
350
+ async list() {
351
+ return [{ id: 1, title: 'Hello', content: 'World', tags: ['news'] }];
352
+ }
353
+ }
354
+
355
+ @Module({
356
+ imports: [
357
+ FastifyModule.forRoot({
358
+ controllers: [UserV1Controller, UserV2Controller, PostV1Controller, PostV2Controller],
359
+ prefixVersioning: true,
360
+ port: 3000,
361
+ }),
362
+ ],
363
+ })
364
+ export class AppModule {}
365
+
366
+ // Available endpoints:
367
+ // GET /v1/api/users
368
+ // GET /v1/api/posts
369
+ // GET /v2/api/users
370
+ // POST /v2/api/users/bulk (new in v2)
371
+ // GET /v2/api/posts
372
+ ```
373
+
374
+ #### Disabling Versioning
375
+
376
+ If you don't need versioning, simply omit the `prefixVersioning` option or set it to `false`:
377
+
378
+ ```typescript
379
+ FastifyModule.forRoot({
380
+ controllers: [UserController],
381
+ prefixVersioning: false, // or omit entirely
382
+ port: 3000,
383
+ });
384
+ // Routes will be: GET /api/users (no version prefix)
385
+ ```
386
+
186
387
  ### Error Handling
187
388
 
188
389
  ```typescript
@@ -336,6 +537,9 @@ The module provides two configuration methods:
336
537
  // Security
337
538
  maskSensitiveData: true,
338
539
 
540
+ // API Versioning
541
+ prefixVersioning: true,
542
+
339
543
  // Request ID generation
340
544
  requestIdLogLabel: 'rid',
341
545
  genReqId: () => uuid(),
@@ -4,3 +4,4 @@ export * from './http-output.decorator.js';
4
4
  export * from './http-route.decorator.js';
5
5
  export * from './middleware.decorator.js';
6
6
  export * from './sensitive.decorator.js';
7
+ export * from './version.decorator.js';
@@ -20,3 +20,4 @@ __exportStar(require("./http-output.decorator.js"), exports);
20
20
  __exportStar(require("./http-route.decorator.js"), exports);
21
21
  __exportStar(require("./middleware.decorator.js"), exports);
22
22
  __exportStar(require("./sensitive.decorator.js"), exports);
23
+ __exportStar(require("./version.decorator.js"), exports);
@@ -0,0 +1,11 @@
1
+ import { Integer } from 'type-fest';
2
+ /**
3
+ * Importing user defined packages
4
+ */
5
+ /**
6
+ * Defining types
7
+ */
8
+ /**
9
+ * Declaring the constants
10
+ */
11
+ export declare function Version<T extends number>(version: Integer<T>): ClassDecorator & MethodDecorator;
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Version = Version;
4
+ /**
5
+ * Importing npm packages
6
+ */
7
+ const app_1 = require("@shadow-library/app");
8
+ /**
9
+ * Importing user defined packages
10
+ */
11
+ /**
12
+ * Defining types
13
+ */
14
+ /**
15
+ * Declaring the constants
16
+ */
17
+ function Version(version) {
18
+ return (0, app_1.Route)({ version });
19
+ }
@@ -16,6 +16,7 @@ declare module '@shadow-library/app' {
16
16
  interface RouteMetadata extends Omit<RouteShorthandOptions, 'config'> {
17
17
  method?: HttpMethod;
18
18
  path?: string;
19
+ version?: number;
19
20
  schemas?: RouteInputSchemas & {
20
21
  response?: Record<number | string, JSONSchema>;
21
22
  };
@@ -50,6 +50,11 @@ export interface FastifyConfig extends FastifyServerOptions {
50
50
  * @default true
51
51
  */
52
52
  maskSensitiveData?: boolean;
53
+ /**
54
+ * Enables prefix-based versioning for all routes in the Fastify instance.
55
+ * @default false
56
+ */
57
+ prefixVersioning?: boolean;
53
58
  }
54
59
  export interface FastifyModuleOptions extends Partial<FastifyConfig> {
55
60
  /**
@@ -119,8 +119,14 @@ let FastifyRouter = class FastifyRouter extends app_1.Router {
119
119
  const { instance, metadata, metatype } = controller;
120
120
  const basePath = metadata.path ?? '/';
121
121
  for (const route of controller.routes) {
122
+ /** Prepare path with versioning if enabled */
122
123
  const routePath = route.metadata.path ?? '';
123
- const path = basePath + routePath;
124
+ let path = basePath + routePath;
125
+ if (this.config.prefixVersioning) {
126
+ const version = route.metadata.version ?? 1;
127
+ const connector = path.startsWith('/') ? '' : '/';
128
+ path = '/v' + version + connector + path;
129
+ }
124
130
  const parsedController = { ...route, instance, metatype };
125
131
  parsedController.metadata.path = path;
126
132
  parsedControllers.routes.push(parsedController);
@@ -4,3 +4,4 @@ export * from './http-output.decorator.js';
4
4
  export * from './http-route.decorator.js';
5
5
  export * from './middleware.decorator.js';
6
6
  export * from './sensitive.decorator.js';
7
+ export * from './version.decorator.js';
@@ -4,3 +4,4 @@ export * from './http-output.decorator.js';
4
4
  export * from './http-route.decorator.js';
5
5
  export * from './middleware.decorator.js';
6
6
  export * from './sensitive.decorator.js';
7
+ export * from './version.decorator.js';
@@ -0,0 +1,11 @@
1
+ import { Integer } from 'type-fest';
2
+ /**
3
+ * Importing user defined packages
4
+ */
5
+ /**
6
+ * Defining types
7
+ */
8
+ /**
9
+ * Declaring the constants
10
+ */
11
+ export declare function Version<T extends number>(version: Integer<T>): ClassDecorator & MethodDecorator;
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Importing npm packages
3
+ */
4
+ import { Route } from '@shadow-library/app';
5
+ /**
6
+ * Importing user defined packages
7
+ */
8
+ /**
9
+ * Defining types
10
+ */
11
+ /**
12
+ * Declaring the constants
13
+ */
14
+ export function Version(version) {
15
+ return Route({ version });
16
+ }
@@ -16,6 +16,7 @@ declare module '@shadow-library/app' {
16
16
  interface RouteMetadata extends Omit<RouteShorthandOptions, 'config'> {
17
17
  method?: HttpMethod;
18
18
  path?: string;
19
+ version?: number;
19
20
  schemas?: RouteInputSchemas & {
20
21
  response?: Record<number | string, JSONSchema>;
21
22
  };
@@ -50,6 +50,11 @@ export interface FastifyConfig extends FastifyServerOptions {
50
50
  * @default true
51
51
  */
52
52
  maskSensitiveData?: boolean;
53
+ /**
54
+ * Enables prefix-based versioning for all routes in the Fastify instance.
55
+ * @default false
56
+ */
57
+ prefixVersioning?: boolean;
53
58
  }
54
59
  export interface FastifyModuleOptions extends Partial<FastifyConfig> {
55
60
  /**
@@ -113,8 +113,14 @@ let FastifyRouter = class FastifyRouter extends Router {
113
113
  const { instance, metadata, metatype } = controller;
114
114
  const basePath = metadata.path ?? '/';
115
115
  for (const route of controller.routes) {
116
+ /** Prepare path with versioning if enabled */
116
117
  const routePath = route.metadata.path ?? '';
117
- const path = basePath + routePath;
118
+ let path = basePath + routePath;
119
+ if (this.config.prefixVersioning) {
120
+ const version = route.metadata.version ?? 1;
121
+ const connector = path.startsWith('/') ? '' : '/';
122
+ path = '/v' + version + connector + path;
123
+ }
118
124
  const parsedController = { ...route, instance, metatype };
119
125
  parsedController.metadata.path = path;
120
126
  parsedControllers.routes.push(parsedController);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@shadow-library/fastify",
3
3
  "type": "module",
4
- "version": "1.1.0",
4
+ "version": "1.2.0",
5
5
  "sideEffects": false,
6
6
  "description": "A Fastify wrapper featuring decorator-based routing, middleware and error handling",
7
7
  "repository": {