nestify-js 0.2.14 → 0.3.2
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 +132 -26
- 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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
225
|
-
transform(context: ExecutionContext, input: any[]) {
|
|
226
|
-
//
|
|
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(
|
|
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(
|
|
242
|
-
return {
|
|
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(
|
|
248
|
-
return {
|
|
314
|
+
getUser(params: any) {
|
|
315
|
+
return { id: params.id }; // Handler receives request.params
|
|
249
316
|
}
|
|
250
317
|
|
|
251
318
|
@Get('/ip')
|
|
252
|
-
getUserIP(
|
|
253
|
-
return { ip };
|
|
319
|
+
getUserIP(ip: string) {
|
|
320
|
+
return { ip }; // Handler receives request.ip
|
|
254
321
|
}
|
|
255
322
|
|
|
256
323
|
@Post('/raw')
|
|
257
|
-
handleRaw(
|
|
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(
|
|
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
|
-
//
|
|
310
|
-
// -
|
|
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 (
|
|
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
|
|
3
|
+
"version": "0.3.2",
|
|
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
|
|
45
|
-
"@nestify-js/shared": "0.2
|
|
44
|
+
"@nestify-js/core": "0.3.2",
|
|
45
|
+
"@nestify-js/shared": "0.3.2"
|
|
46
46
|
},
|
|
47
47
|
"peerDependencies": {
|
|
48
48
|
"@fastify/multipart": "^9.3.0"
|