@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 +204 -0
- package/cjs/decorators/index.d.ts +1 -0
- package/cjs/decorators/index.js +1 -0
- package/cjs/decorators/version.decorator.d.ts +11 -0
- package/cjs/decorators/version.decorator.js +19 -0
- package/cjs/interfaces/server-metadata.interface.d.ts +1 -0
- package/cjs/module/fastify-module.interface.d.ts +5 -0
- package/cjs/module/fastify-router.js +7 -1
- package/esm/decorators/index.d.ts +1 -0
- package/esm/decorators/index.js +1 -0
- package/esm/decorators/version.decorator.d.ts +11 -0
- package/esm/decorators/version.decorator.js +16 -0
- package/esm/interfaces/server-metadata.interface.d.ts +1 -0
- package/esm/module/fastify-module.interface.d.ts +5 -0
- package/esm/module/fastify-router.js +7 -1
- package/package.json +1 -1
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(),
|
package/cjs/decorators/index.js
CHANGED
|
@@ -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
|
-
|
|
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);
|
package/esm/decorators/index.js
CHANGED
|
@@ -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
|
-
|
|
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