nestify-js 0.2.13 → 0.3.1

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.
Files changed (2) hide show
  1. package/README.md +132 -26
  2. package/package.json +3 -3
package/README.md CHANGED
@@ -165,32 +165,73 @@ class UserModule {}
165
165
 
166
166
  ### Middleware System
167
167
 
168
+ There are four kinds of middleware: **Guards**, **Interceptors**, **Pipes** and **Filters**.
169
+
170
+ Execution order of a single request:
171
+
172
+ ```
173
+ Request → Interceptor(enter) → Guard → Pipe → Controller method → Interceptor(leave)
174
+ └────────── Exception → Filter ──────────┘
175
+ ```
176
+
177
+ #### Registration Rules (Important)
178
+
179
+ - **Built-in middlewares are auto-registered**: the framework's preset pipes (`PipeBody` / `PipeQuery` / `PipeParams` / `PipeIp` / `PipeRaw` / `PipeFile`) and `JwtGuard` are automatically instantiated during `apply()`. They work out of the box, no configuration needed.
180
+ - **Custom middlewares must be registered**: like NestJS, classes decorated by `@Guard()` / `@Interceptor()` / `@Pipe()` / `@Filter()` must appear in some module's `providers`, otherwise route registration fails with `Cannot find class for token`.
181
+ - **Where to apply**: `@UseGuards` / `@UseInterceptors` / `@UsePipes` / `@UseFilters` can be applied on a **controller class** (affects all its routes) or on a **method** (affects only that route). Middlewares of the same kind run in order: global → controller → method.
182
+ - Middleware classes are `Injectable` too, so `@Inject` property injection works inside them.
183
+
168
184
  #### Guards
169
185
 
170
- Guards control access to routes:
186
+ Guards control access to routes. Returning `false` or throwing from `canActivate` aborts the request:
171
187
 
172
188
  ```typescript
173
189
  @Guard()
174
190
  class AuthGuard implements InjecoratorGuard {
175
- canActivate(context: ExecutionContext): boolean {
191
+ // Dependency injection works
192
+ @Inject(AuthService)
193
+ authService: AuthService;
194
+
195
+ canActivate(context: ExecutionContext): boolean | Promise<boolean> {
176
196
  const request = context.switchToHttp().getRequest();
177
- return request.headers.authorization != null;
197
+ return request.headers.authorization === 'Bearer valid-token';
198
+ // Or throw new UnauthorizedException() for a specific error
178
199
  }
179
200
  }
180
201
 
202
+ // Must be registered in providers before use
203
+ @Module({ controllers: [AdminController], providers: [AuthGuard] })
204
+ class AdminModule {}
205
+
181
206
  @Controller('/admin')
182
- @UseGuards(AuthGuard)
207
+ @UseGuards(AuthGuard) // Controller level: applies to all routes
183
208
  class AdminController {
184
209
  @Get('/dashboard')
185
210
  getDashboard() {
186
211
  return { data: 'sensitive' };
187
212
  }
213
+
214
+ @Get('/stats')
215
+ @UseGuards(AnotherGuard) // Method level: appended after controller-level guards
216
+ getStats() {
217
+ return { data: 'stats' };
218
+ }
188
219
  }
189
220
  ```
190
221
 
222
+ Register a global guard with the `APP_GUARD` token to guard every route (each kind of global middleware can only be registered once):
223
+
224
+ ```typescript
225
+ @Module({
226
+ controllers: [AppController],
227
+ providers: [{ provide: APP_GUARD, useClass: AuthGuard }],
228
+ })
229
+ class AppModule {}
230
+ ```
231
+
191
232
  #### Interceptors
192
233
 
193
- Interceptors can modify request/response flow:
234
+ Interceptors run before the controller method; the returned function is called after the method finishes (useful for logging, timing, response wrapping). The returned function receives the handler's return value (or the caught error):
194
235
 
195
236
  ```typescript
196
237
  @Interceptor()
@@ -199,8 +240,9 @@ class LoggingInterceptor implements InjecoratorInterceptor {
199
240
  const start = Date.now();
200
241
  console.log('Request started');
201
242
 
202
- return () => {
243
+ return (result: any) => {
203
244
  console.log(`Request completed in ${Date.now() - start}ms`);
245
+ return result; // Can be modified; the return value becomes the final response
204
246
  };
205
247
  }
206
248
  }
@@ -215,59 +257,89 @@ class ApiController {
215
257
  }
216
258
  ```
217
259
 
260
+ Global interceptor: `{ provide: APP_INTERCEPTOR, useClass: LoggingInterceptor }`.
261
+
218
262
  #### Pipes
219
263
 
220
- Pipes transform and validate input data:
264
+ Pipes validate and transform input data. Each pipe's return value becomes the next pipe's `input`.
265
+
266
+ **Custom pipes** are applied via `@UsePipes`, optionally with a validation schema (validation is based on fastify's `validatorCompiler`):
221
267
 
222
268
  ```typescript
223
269
  @Pipe()
224
- class ValidationPipe implements InjecoratorPipe {
225
- transform(context: ExecutionContext, input: any[]) {
226
- // Transform and validate input
227
- return input;
270
+ class TrimPipe implements InjecoratorPipe {
271
+ async transform(context: ExecutionContext, input: any[], schema?: PipeFullSchema) {
272
+ // `input` comes from the previous step; the return value goes to the next pipe or the handler
273
+ return input.map((v) => (typeof v === 'string' ? v.trim() : v));
274
+ }
275
+ }
276
+
277
+ @Controller('/users')
278
+ @UsePipes(TrimPipe) // Also works without a schema (transformation only)
279
+ class UserController {
280
+ @Post('/')
281
+ @UsePipes({
282
+ pipe: TrimPipe,
283
+ schema: { body: { type: 'object', required: ['name'] } }, // PipeOptions: pipe + schema
284
+ })
285
+ createUser() {
286
+ // ...
228
287
  }
229
288
  }
289
+ ```
230
290
 
291
+ **Built-in pipes** (auto-registered, use them directly) extract data from the `request` object and pass it to the handler:
292
+
293
+ ```typescript
231
294
  @Controller('/users')
232
295
  class UserController {
296
+ // @Body(schema?, ok?, other?)
297
+ // - schema: JSON Schema to validate request.body
298
+ // - ok: generates the response.200 schema (for swagger)
299
+ // - other: remaining fastify route schema (e.g. headers, response)
233
300
  @Post('/')
234
301
  @Body({ type: 'object', required: ['name', 'email'] })
235
- createUser(@Body() body: any) {
236
- return { user: body };
302
+ createUser(body: any) {
303
+ return { user: body }; // Handler receives request.body
237
304
  }
238
305
 
239
306
  @Get('/')
240
307
  @Query({ type: 'object' })
241
- getUsers(@Query() query: any) {
242
- return { users: [], query };
308
+ getUsers(query: any) {
309
+ return { query }; // Handler receives request.query
243
310
  }
244
311
 
245
312
  @Get('/:id')
246
313
  @Params({ type: 'object', required: ['id'] })
247
- getUser(@Params() params: any) {
248
- return { user: { id: params.id } };
314
+ getUser(params: any) {
315
+ return { id: params.id }; // Handler receives request.params
249
316
  }
250
317
 
251
318
  @Get('/ip')
252
- getUserIP(@Ip() ip: string) {
253
- return { ip };
319
+ getUserIP(ip: string) {
320
+ return { ip }; // Handler receives request.ip
254
321
  }
255
322
 
256
323
  @Post('/raw')
257
- handleRaw(@Raw() raw: any) {
324
+ handleRaw(raw: any) {
325
+ // @Raw(): handler receives request.raw (the raw Node request)
258
326
  return { received: true };
259
327
  }
260
328
  }
261
329
  ```
262
330
 
331
+ > **Note**: `@Body` / `@Query` / `@Params` / `@Ip` / `@Raw` ignore the previous pipe's return value and always extract from the `request` object. When chaining pipes, put them last or handle the data yourself in a custom pipe.
332
+
333
+ Global pipe: `{ provide: APP_PIPE, useClass: MyPipe }` (or `{ provide: APP_PIPE, useValue: { pipe: MyPipe, schema: {...} } }`).
334
+
263
335
  #### Filters
264
336
 
265
- Filters handle exceptions:
337
+ Filters handle exceptions thrown by routes. Specify the exception classes to catch in the decorator (omit to catch all):
266
338
 
267
339
  ```typescript
268
340
  @Filter(HttpException)
269
341
  class HttpExceptionFilter implements InjecoratorFilter {
270
- catch(exception: HttpException, context: ExecutionContext) {
342
+ catch(context: ExecutionContext, exception: HttpException) {
271
343
  const response = context.switchToHttp().getReply();
272
344
  response.status(exception.status).send({
273
345
  error: exception.message,
@@ -286,6 +358,41 @@ class ApiController {
286
358
  }
287
359
  ```
288
360
 
361
+ Global filter: `{ provide: APP_FILTER, useClass: HttpExceptionFilter }`.
362
+
363
+ #### Built-in JWT Guard
364
+
365
+ The framework ships with `JwtGuard` (auto-registered, no need to add it to providers). It extracts and verifies the token from `Authorization: Bearer <token>` and attaches the decoded payload to the request:
366
+
367
+ ```typescript
368
+ import { JwtGuard, JwtService, jwt } from 'nestify-js';
369
+
370
+ // `jwt` is the default JwtService instance; you can also pass your own: JwtGuard(myJwt)
371
+ @Controller('protected')
372
+ @UseGuards(JwtGuard())
373
+ class ProtectedController {
374
+ @Get('profile')
375
+ async getProfile(request: any) {
376
+ // The first handler argument is the pipe result; you can also read
377
+ // the request in guards/interceptors via context.switchToHttp().getRequest()
378
+ return request;
379
+ }
380
+ }
381
+ ```
382
+
383
+ #### ExecutionContext
384
+
385
+ All middlewares access request information through `context: ExecutionContext`:
386
+
387
+ ```typescript
388
+ const http = context.switchToHttp();
389
+ const request = http.getRequest<FastifyRequest>(); // fastify request object
390
+ const reply = http.getReply<FastifyReply>(); // fastify reply object
391
+
392
+ context.getClass(); // Current controller class
393
+ context.getHandler(); // Current handler method
394
+ ```
395
+
289
396
  ### Application Bootstrap
290
397
 
291
398
  #### `nestify(rootModule, options?)` (recommended)
@@ -306,9 +413,8 @@ const app = await nestify(AppModule, {
306
413
  [staticFiles, { root: './public', prefix: '/' }],
307
414
  ],
308
415
 
309
- // setup callback to register auto-created instances
310
- // - e.g. the built-in `setupBasicPipes` creates preset pipe instances
311
- setup: setupBasicPipes,
416
+ // Setup callback to register auto-created instances (optional)
417
+ // - Built-in pipes and JwtGuard are auto-registered, usually not needed
312
418
 
313
419
  // start listening after all modules are registered
314
420
  // - `true` uses the `PORT` / `HOST` env vars (falling back to 3000 / 0.0.0.0)
@@ -324,7 +430,7 @@ Available options:
324
430
  | `logger` | `FastifyServerOptions['logger']` | Shortcut for `fastify.logger` |
325
431
  | `fastify` | `FastifyServerOptions` | Options passed to the fastify factory (`fastify(options)`) |
326
432
  | `plugins` | `readonly [plugin, options?][]` | Fastify plugins registered before modules are applied (callback-style and async-style are both accepted) |
327
- | `setup` | `(register: (cls: Constructor) => void) => void` | Setup callback to register auto-created instances (e.g. `setupBasicPipes`) |
433
+ | `setup` | `(register: (cls: Constructor) => void) => void` | Setup callback to register auto-created instances (optional; built-in pipes/JwtGuard are auto-registered, usually not needed) |
328
434
  | `listen` | `boolean \| Partial<FastifyListenOptions>` | Start listening after all modules are registered |
329
435
  | `allowCrossModuleCircularReference` | `boolean` | Must be `true` to allow **cross-module** circular dependencies (same-module circular references are always allowed). `@default false` |
330
436
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nestify-js",
3
- "version": "0.2.13",
3
+ "version": "0.3.1",
4
4
  "description": "A NestJS like fastify plugin for dependency injection",
5
5
  "description_zh": "一个类似于 NestJS 的 Fastify 依赖注入插件",
6
6
  "purpose": "npm",
@@ -41,8 +41,8 @@
41
41
  "@fastify/sensible": "^6.0.4",
42
42
  "@fastify/static": "^9.1.3",
43
43
  "fastify": "^5.0.0",
44
- "@nestify-js/core": "0.2.13",
45
- "@nestify-js/shared": "0.2.13"
44
+ "@nestify-js/core": "0.3.1",
45
+ "@nestify-js/shared": "0.3.1"
46
46
  },
47
47
  "peerDependencies": {
48
48
  "@fastify/multipart": "^9.3.0"