mongodb-dynamic-api 1.4.1 → 1.4.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/CHANGELOG.md +2 -0
- package/README.md +41 -802
- package/package.json +1 -1
- package/src/version.json +1 -1
- package/tsconfig.tsbuildinfo +1 -1
package/README.md
CHANGED
|
@@ -47,39 +47,35 @@
|
|
|
47
47
|
|
|
48
48
|
---
|
|
49
49
|
|
|
50
|
+
## mongodb-dynamic-api <img src="https://pbs.twimg.com/media/EDoWJbUXYAArclg.png" width="24" height="24" />
|
|
51
|
+
```text
|
|
52
|
+
npm install --save mongodb-dynamic-api
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
50
57
|
<div style="text-align: center; width: 100%;">
|
|
51
58
|
|
|
52
|
-
#
|
|
59
|
+
# Dynamic API Module
|
|
53
60
|
|
|
54
61
|
</div>
|
|
55
62
|
|
|
56
63
|
|
|
57
64
|
<p style="text-align: justify; width: 100%;font-size: 15px;">
|
|
58
65
|
|
|
59
|
-
|
|
66
|
+
In summary, DynamicApiModule is a flexible and configurable module using NestJS 10 that provides dynamic API functionality.
|
|
67
|
+
<br>It must be set up at the root level with global settings and then configured for individual features.
|
|
68
|
+
<br>It has several optional features such as
|
|
69
|
+
Swagger UI,
|
|
70
|
+
Authentication (JWT),
|
|
71
|
+
Authorization (Casl),
|
|
72
|
+
Validation (Class Validator)
|
|
73
|
+
and Caching (cache-manager).
|
|
60
74
|
|
|
61
75
|
</p>
|
|
62
76
|
|
|
63
|
-
---
|
|
64
|
-
|
|
65
|
-
## npm package <img src="https://pbs.twimg.com/media/EDoWJbUXYAArclg.png" width="24" height="24" />
|
|
66
|
-
```text
|
|
67
|
-
npm install --save mongodb-dynamic-api
|
|
68
|
-
```
|
|
69
|
-
|
|
70
77
|
___
|
|
71
78
|
|
|
72
|
-
### Table of Contents
|
|
73
|
-
|
|
74
|
-
[Introduction](https://github.com/MikeDev75015/mongodb-dynamic-api/blob/develop/README.md#how-to-enjoy-it)
|
|
75
|
-
- [Swagger UI](https://github.com/MikeDev75015/mongodb-dynamic-api/blob/develop/README.md#swagger-ui-optional-but-strongly-recommended)
|
|
76
|
-
- [Validation](https://github.com/MikeDev75015/mongodb-dynamic-api/blob/develop/README.md#validation-optional)
|
|
77
|
-
- [Versioning](https://github.com/MikeDev75015/mongodb-dynamic-api/blob/develop/README.md#versioning-optional)
|
|
78
|
-
- [Caching](https://github.com/MikeDev75015/mongodb-dynamic-api/blob/develop/README.md#caching-enabled-by-default)
|
|
79
|
-
- [Authentication](https://github.com/MikeDev75015/mongodb-dynamic-api/blob/develop/README.md#authentication-optional)
|
|
80
|
-
- [Casl](https://github.com/MikeDev75015/mongodb-dynamic-api/blob/develop/README.md#casl-only-with-authentication)
|
|
81
|
-
|
|
82
|
-
---
|
|
83
79
|
### HOW TO ENJOY IT
|
|
84
80
|
|
|
85
81
|
- Start a new [nest](https://docs.nestjs.com/) project with **typescript** (use the `--strict` option)
|
|
@@ -87,7 +83,7 @@ ___
|
|
|
87
83
|
nest new --strict your-project-name
|
|
88
84
|
```
|
|
89
85
|
|
|
90
|
-
- Go to your project root and install the [mongodb-dynamic-api](https://www.npmjs.com/package/mongodb-dynamic-api) package
|
|
86
|
+
- Go to your new project root and install the [mongodb-dynamic-api](https://www.npmjs.com/package/mongodb-dynamic-api) package
|
|
91
87
|
```text
|
|
92
88
|
npm i -S mongodb-dynamic-api
|
|
93
89
|
```
|
|
@@ -116,8 +112,7 @@ export class AppModule {}
|
|
|
116
112
|
- Ok, now let's add our first content with just 2 files. It will be a simple `User` with a `name` and an `email` field.
|
|
117
113
|
- We use the `@Schema` and `@Prop` decorators from the <a href="https://docs.nestjs.com/techniques/mongodb#model-injection" target="_blank">@nestjs/mongoose</a> package to define our MongoDB model.
|
|
118
114
|
|
|
119
|
-
|
|
120
|
-
- You must extend the `BaseEntity` (or `SoftDeletableEntity`) class from the `mongodb-dynamic-api` package **for all your collection models**.
|
|
115
|
+
- You must extend the `BaseEntity` class from the `mongodb-dynamic-api` package **for all your collection models**.
|
|
121
116
|
- Just create a new file `user.ts` and add the following code.
|
|
122
117
|
|
|
123
118
|
```typescript
|
|
@@ -166,8 +161,9 @@ import { UsersModule } from './users/users.module';
|
|
|
166
161
|
|
|
167
162
|
@Module({
|
|
168
163
|
imports: [
|
|
169
|
-
DynamicApiModule.forRoot(
|
|
170
|
-
|
|
164
|
+
DynamicApiModule.forRoot(
|
|
165
|
+
'mongodb-uri', // <- replace by your own MongoDB connection string
|
|
166
|
+
),
|
|
171
167
|
UsersModule,
|
|
172
168
|
],
|
|
173
169
|
controllers: [AppController],
|
|
@@ -179,791 +175,34 @@ export class AppModule {}
|
|
|
179
175
|
**And that's all !** *You now have a fully functional CRUD API for the `User` content at the `/users` path.*
|
|
180
176
|
|
|
181
177
|
|
|
182
|
-
|
|
183
178
|
| Endpoint | Body | Param | Query |
|
|
184
179
|
|:--------------------------------------------------|:----------------------------------------------:|:------------:|:---------------:|
|
|
185
|
-
|
|
|
186
|
-
|
|
|
187
|
-
|
|
|
188
|
-
|
|
|
189
|
-
|
|
|
190
|
-
|
|
|
191
|
-
|
|
|
192
|
-
|
|
|
193
|
-
|
|
|
194
|
-
|
|
|
195
|
-
|
|
|
196
|
-
|
|
197
|
-
___
|
|
198
|
-
|
|
199
|
-
- TOC > [Validation](https://github.com/MikeDev75015/mongodb-dynamic-api/blob/develop/README.md#validation-optional)
|
|
200
|
-
/ [Versioning](https://github.com/MikeDev75015/mongodb-dynamic-api/blob/develop/README.md#versioning-optional)
|
|
201
|
-
/ [Caching](https://github.com/MikeDev75015/mongodb-dynamic-api/blob/develop/README.md#caching-enabled-by-default)
|
|
202
|
-
/ [Authentication](https://github.com/MikeDev75015/mongodb-dynamic-api/blob/develop/README.md#authentication-optional)
|
|
203
|
-
/ [Casl](https://github.com/MikeDev75015/mongodb-dynamic-api/blob/develop/README.md#casl-only-with-authentication)
|
|
204
|
-
|
|
205
|
-
___
|
|
206
|
-
|
|
207
|
-
### [Swagger UI](https://docs.nestjs.com/openapi/introduction#document-options) (optional but strongly recommended)
|
|
208
|
-
`function enableDynamicAPISwagger(app: INestApplication, options?: DynamicAPISwaggerOptions): void`
|
|
209
|
-
|
|
210
|
-
**Configuration**
|
|
211
|
-
|
|
212
|
-
```typescript
|
|
213
|
-
// src/main.ts
|
|
214
|
-
import { enableDynamicAPISwagger } from 'mongodb-dynamic-api';
|
|
215
|
-
|
|
216
|
-
async function bootstrap() {
|
|
217
|
-
const app = await NestFactory.create(AppModule);
|
|
218
|
-
// ...
|
|
219
|
-
enableDynamicAPISwagger(app); // <- add this line in your main.ts file
|
|
220
|
-
|
|
221
|
-
await app.listen(3000);
|
|
222
|
-
}
|
|
223
|
-
```
|
|
224
|
-
|
|
225
|
-
The `enableDynamicAPISwagger` function will automatically build the swagger documentation.
|
|
226
|
-
<br>This method can be called with optional parameters to specify more documentation options.
|
|
227
|
-
<br>*See <strong>nestjs</strong> <a href="https://docs.nestjs.com/openapi/introduction#document-options" target="_blank">documentation</a> for more details.*
|
|
228
|
-
|
|
229
|
-
**Usage**
|
|
230
|
-
|
|
231
|
-
Add the `@ApiProperty` | `@ApiPropertyOptional` decorators to your class properties to have a better swagger documentation.
|
|
232
|
-
<br>Let's add an optional company field to the `User` class.
|
|
233
|
-
|
|
234
|
-
```typescript
|
|
235
|
-
// src/users/user.ts
|
|
236
|
-
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
|
237
|
-
|
|
238
|
-
@Schema({ collection: 'users' })
|
|
239
|
-
export class User extends BaseEntity {
|
|
240
|
-
@ApiProperty() // <- add this line
|
|
241
|
-
@Prop({ type: String, required: true })
|
|
242
|
-
name: string;
|
|
243
|
-
|
|
244
|
-
@ApiProperty() // <- add this line
|
|
245
|
-
@Prop({ type: String, required: true })
|
|
246
|
-
email: string;
|
|
247
|
-
|
|
248
|
-
@ApiPropertyOptional() // <- add this line
|
|
249
|
-
@Prop({ type: String })
|
|
250
|
-
company?: string;
|
|
251
|
-
}
|
|
252
|
-
```
|
|
253
|
-
|
|
254
|
-
go to the swagger API path (default to `/dynamic-api`) and you will see the auto generated API
|
|
255
|
-
|
|
256
|
-

|
|
257
|
-
|
|
258
|
-
<a href="https://github.com/MikeDev75015/mongodb-dynamic-api/blob/develop/README/swagger-user-api.md" target="_blank">See more User API screenshots</a>
|
|
259
|
-
|
|
260
|
-
___
|
|
261
|
-
|
|
262
|
-
- TOC > [Introduction](https://github.com/MikeDev75015/mongodb-dynamic-api/blob/develop/README.md#how-to-enjoy-it)
|
|
263
|
-
/ [Versioning](https://github.com/MikeDev75015/mongodb-dynamic-api/blob/develop/README.md#versioning-optional)
|
|
264
|
-
/ [Caching](https://github.com/MikeDev75015/mongodb-dynamic-api/blob/develop/README.md#caching-enabled-by-default)
|
|
265
|
-
/ [Authentication](https://github.com/MikeDev75015/mongodb-dynamic-api/blob/develop/README.md#authentication-optional)
|
|
266
|
-
/ [Casl](https://github.com/MikeDev75015/mongodb-dynamic-api/blob/develop/README.md#casl-only-with-authentication)
|
|
267
|
-
|
|
268
|
-
___
|
|
269
|
-
|
|
270
|
-
### [Validation](https://docs.nestjs.com/techniques/validation#using-the-built-in-validationpipe) (optional)
|
|
271
|
-
<br>`function enableDynamicAPIValidation(app: INestApplication, options?: ValidationPipeOptions): void`
|
|
272
|
-
|
|
273
|
-
**Configuration**
|
|
274
|
-
|
|
275
|
-
```typescript
|
|
276
|
-
// src/main.ts
|
|
277
|
-
import { enableDynamicAPIValidation } from 'mongodb-dynamic-api';
|
|
278
|
-
|
|
279
|
-
async function bootstrap() {
|
|
280
|
-
const app = await NestFactory.create(AppModule);
|
|
281
|
-
// ...
|
|
282
|
-
enableDynamicAPIValidation(app); // <- add this line in your main.ts file
|
|
283
|
-
|
|
284
|
-
await app.listen(3000);
|
|
285
|
-
}
|
|
286
|
-
```
|
|
287
|
-
The `enableDynamicAPIValidation` function allow to configure the pipe validation options for the API globally.
|
|
288
|
-
|
|
289
|
-
You can also define the pipe validation options in the `DynamicApiModule.forFeature` method, either in the controller options,
|
|
290
|
-
or in each route object defined in the routes property.
|
|
291
|
-
<br>*If the options are specified in 2, the options specified in the route will have priority.*
|
|
292
|
-
|
|
293
|
-
```typescript
|
|
294
|
-
// src/users/users.module.ts
|
|
295
|
-
import { DynamicApiModule } from 'mongodb-dynamic-api';
|
|
296
|
-
import { User } from './user';
|
|
297
|
-
|
|
298
|
-
@Module({
|
|
299
|
-
imports: [
|
|
300
|
-
DynamicApiModule.forFeature({
|
|
301
|
-
entity: User,
|
|
302
|
-
controllerOptions: {
|
|
303
|
-
// ...
|
|
304
|
-
validationPipeOptions: { // <- in the controller options
|
|
305
|
-
transform: true,
|
|
306
|
-
whitelist: true,
|
|
307
|
-
forbidNonWhitelisted: true,
|
|
308
|
-
},
|
|
309
|
-
},
|
|
310
|
-
routes: [
|
|
311
|
-
{
|
|
312
|
-
type: 'DuplicateOne',
|
|
313
|
-
validationPipeOptions: { transform: true }, // <- in the route options
|
|
314
|
-
},
|
|
315
|
-
],
|
|
316
|
-
}),
|
|
317
|
-
],
|
|
318
|
-
})
|
|
319
|
-
export class UsersModule {}
|
|
320
|
-
```
|
|
321
|
-
|
|
322
|
-
**Usage**
|
|
323
|
-
|
|
324
|
-
Use the `Class validator` <a href="https://github.com/typestack/class-validator?tab=readme-ov-file#validation-decorators" target="_blank">decorators</a> to validate your class properties.
|
|
325
|
-
<br>Let's add `IsEmail` decorator to the `email` field.
|
|
326
|
-
|
|
327
|
-
```typescript
|
|
328
|
-
// src/users/user.ts
|
|
329
|
-
import { IsEmail } from 'class-validator';
|
|
330
|
-
|
|
331
|
-
@Schema({ collection: 'users' })
|
|
332
|
-
export class User extends BaseEntity {
|
|
333
|
-
@ApiProperty()
|
|
334
|
-
@Prop({ type: String, required: true })
|
|
335
|
-
name: string;
|
|
336
|
-
|
|
337
|
-
@ApiProperty()
|
|
338
|
-
@IsEmail()
|
|
339
|
-
@Prop({ type: String, required: true })
|
|
340
|
-
email: string;
|
|
341
|
-
|
|
342
|
-
@ApiPropertyOptional()
|
|
343
|
-
@Prop({ type: String })
|
|
344
|
-
company?: string;
|
|
345
|
-
}
|
|
346
|
-
```
|
|
347
|
-
|
|
348
|
-
Ok, now if you try to create a new user with an invalid email, you will get a `400 Bad Request` error.
|
|
349
|
-
|
|
350
|
-

|
|
351
|
-
|
|
352
|
-
___
|
|
353
|
-
|
|
354
|
-
- TOC > [Introduction](https://github.com/MikeDev75015/mongodb-dynamic-api/blob/develop/README.md#how-to-enjoy-it)
|
|
355
|
-
/ [Swagger UI](https://github.com/MikeDev75015/mongodb-dynamic-api/blob/develop/README.md#swagger-ui-optional-but-strongly-recommended)
|
|
356
|
-
/ [Caching](https://github.com/MikeDev75015/mongodb-dynamic-api/blob/develop/README.md#caching-enabled-by-default)
|
|
357
|
-
/ [Authentication](https://github.com/MikeDev75015/mongodb-dynamic-api/blob/develop/README.md#authentication-optional)
|
|
358
|
-
/ [Casl](https://github.com/MikeDev75015/mongodb-dynamic-api/blob/develop/README.md#casl-only-with-authentication)
|
|
359
|
-
|
|
360
|
-
___
|
|
361
|
-
|
|
362
|
-
### [Versioning](https://docs.nestjs.com/techniques/versioning) (optional)
|
|
363
|
-
`function enableDynamicAPIVersioning(app: INestApplication, options?: VersioningOptions): void`
|
|
364
|
-
|
|
365
|
-
The `enableDynamicAPIVersioning` function will automatically add versioning to the API.
|
|
366
|
-
<br>By default, it will use the <strong>URI versioning type</strong>.
|
|
367
|
-
<br>This method can be called with a second <strong>optional parameter</strong> to specify custom options.
|
|
368
|
-
<br>*See <strong>nestjs</strong> <a href="https://docs.nestjs.com/techniques/versioning" target="_blank">documentation</a> for more details.*
|
|
369
|
-
|
|
370
|
-
**Configuration**
|
|
371
|
-
|
|
372
|
-
```typescript
|
|
373
|
-
// src/main.ts
|
|
374
|
-
import { enableDynamicAPIVersioning } from 'mongodb-dynamic-api';
|
|
375
|
-
|
|
376
|
-
async function bootstrap() {
|
|
377
|
-
const app = await NestFactory.create(AppModule);
|
|
378
|
-
// ...
|
|
379
|
-
enableDynamicAPIVersioning(app); // <- add this line in your main.ts file
|
|
380
|
-
|
|
381
|
-
await app.listen(3000);
|
|
382
|
-
}
|
|
383
|
-
```
|
|
384
|
-
|
|
385
|
-
**Usage**
|
|
386
|
-
|
|
387
|
-
Pass the `version` property to the `controllerOptions` object or to the `route` object in the `DynamicApiModule.forFeature` method.
|
|
388
|
-
<br>*If the version is specified in 2, the version specified in the route will have priority.*
|
|
389
|
-
|
|
390
|
-
Let's add a new version to the `User` content.
|
|
391
|
-
|
|
392
|
-
```typescript
|
|
393
|
-
// src/users/create-one-user-v2.dto.ts
|
|
394
|
-
import { ApiProperty, ApiPropertyOptional, PickType } from '@nestjs/swagger';
|
|
395
|
-
import { IsOptional, IsString } from 'class-validator';
|
|
396
|
-
import { User } from './user';
|
|
397
|
-
|
|
398
|
-
export class CreateOneUserV2Dto extends PickType(User, ['email']) {
|
|
399
|
-
@ApiProperty()
|
|
400
|
-
@IsString()
|
|
401
|
-
fullName: string;
|
|
402
|
-
|
|
403
|
-
@ApiProperty()
|
|
404
|
-
@IsString()
|
|
405
|
-
phoneNumber: string;
|
|
406
|
-
|
|
407
|
-
@ApiPropertyOptional()
|
|
408
|
-
@IsString()
|
|
409
|
-
@IsOptional()
|
|
410
|
-
country?: string;
|
|
411
|
-
}
|
|
412
|
-
```
|
|
413
|
-
|
|
414
|
-
```typescript
|
|
415
|
-
// src/users/user-v2.presenter.ts
|
|
416
|
-
import { ApiProperty, ApiPropertyOptional, PickType } from '@nestjs/swagger';
|
|
417
|
-
import { User } from './user';
|
|
418
|
-
|
|
419
|
-
export class UserV2Presenter extends PickType(User, ['email']) {
|
|
420
|
-
@ApiProperty()
|
|
421
|
-
fullName: string;
|
|
422
|
-
|
|
423
|
-
@ApiProperty()
|
|
424
|
-
phoneNumber: string;
|
|
425
|
-
|
|
426
|
-
@ApiPropertyOptional()
|
|
427
|
-
country?: string;
|
|
428
|
-
}
|
|
429
|
-
```
|
|
430
|
-
|
|
431
|
-
```typescript
|
|
432
|
-
// src/users/users.module.ts
|
|
433
|
-
import { DynamicApiModule } from 'mongodb-dynamic-api';
|
|
434
|
-
import { User } from './user';
|
|
435
|
-
import { CreateOneUserV2Dto } from './create-one-user-v2.dto';
|
|
436
|
-
import { UserV2Presenter } from './user-v2.presenter';
|
|
437
|
-
|
|
438
|
-
@Module({
|
|
439
|
-
imports: [
|
|
440
|
-
DynamicApiModule.forFeature({
|
|
441
|
-
entity: User,
|
|
442
|
-
controllerOptions: {
|
|
443
|
-
path: 'users',
|
|
444
|
-
version: '1', // <- add this line
|
|
445
|
-
},
|
|
446
|
-
routes: [
|
|
447
|
-
{ type: 'GetMany' },
|
|
448
|
-
{ type: 'GetOne' },
|
|
449
|
-
{
|
|
450
|
-
type: 'CreateOne',
|
|
451
|
-
dTOs: {
|
|
452
|
-
body: CreateOneUserV2Dto,
|
|
453
|
-
presenter: UserV2Presenter,
|
|
454
|
-
},
|
|
455
|
-
version: '2', // <- add this line
|
|
456
|
-
},
|
|
457
|
-
],
|
|
458
|
-
}),
|
|
459
|
-
],
|
|
460
|
-
})
|
|
461
|
-
export class UsersModule {}
|
|
462
|
-
```
|
|
463
|
-
|
|
464
|
-
Great, now you have a versioned User API, and you can access it at the `/v1/users` and `/v2/users` paths.
|
|
465
|
-
|
|
466
|
-

|
|
467
|
-
|
|
468
|
-
___
|
|
469
|
-
|
|
470
|
-
- TOC > [Introduction](https://github.com/MikeDev75015/mongodb-dynamic-api/blob/develop/README.md#how-to-enjoy-it)
|
|
471
|
-
/ [Swagger UI](https://github.com/MikeDev75015/mongodb-dynamic-api/blob/develop/README.md#swagger-ui-optional-but-strongly-recommended)
|
|
472
|
-
/ [Validation](https://github.com/MikeDev75015/mongodb-dynamic-api/blob/develop/README.md#validation-optional)
|
|
473
|
-
/ [Authentication](https://github.com/MikeDev75015/mongodb-dynamic-api/blob/develop/README.md#authentication-optional)
|
|
474
|
-
/ [Casl](https://github.com/MikeDev75015/mongodb-dynamic-api/blob/develop/README.md#casl-only-with-authentication)
|
|
475
|
-
|
|
476
|
-
___
|
|
477
|
-
|
|
478
|
-
### [Caching](https://docs.nestjs.com/techniques/caching#in-memory-cache) (enabled by default)
|
|
479
|
-
|
|
480
|
-
By default, the caching is activated globally for all the routes. It uses the nestjs built-in in-memory data store with the default options.
|
|
481
|
-
<br>You can configure the cache options by passing the `cacheOptions` in the second parameter of the `DynamicApiModule.forRoot` method.
|
|
482
|
-
|
|
483
|
-
**Configuration**
|
|
484
|
-
|
|
485
|
-
```typescript
|
|
486
|
-
// src/app.module.ts
|
|
487
|
-
import { DynamicApiModule } from 'mongodb-dynamic-api';
|
|
488
|
-
|
|
489
|
-
@Module({
|
|
490
|
-
imports: [
|
|
491
|
-
DynamicApiModule.forRoot('...', {
|
|
492
|
-
cacheOptions: {
|
|
493
|
-
ttl: 60, // <- The time to live in milliseconds. This is the maximum amount of time that an item can be in the cache before it is removed.
|
|
494
|
-
max: 100, // <- The maximum number of items that can be stored in the cache.
|
|
495
|
-
},
|
|
496
|
-
}),
|
|
497
|
-
// ...
|
|
498
|
-
],
|
|
499
|
-
controllers: [AppController],
|
|
500
|
-
providers: [AppService],
|
|
501
|
-
})
|
|
502
|
-
export class AppModule {}
|
|
503
|
-
```
|
|
504
|
-
|
|
505
|
-
*See <strong>nestjs</strong> <a href="https://docs.nestjs.com/techniques/caching" target="_blank">documentation</a> for more details.*
|
|
506
|
-
|
|
507
|
-
**[Not recommended]** The cache can also be disabled globally with the `useGlobalCache` property set to `false` in the `DynamicApiModule.forRoot` method.
|
|
508
|
-
|
|
509
|
-
```typescript
|
|
510
|
-
// src/app.module.ts
|
|
511
|
-
import { DynamicApiModule } from 'mongodb-dynamic-api';
|
|
512
|
-
|
|
513
|
-
@Module({
|
|
514
|
-
imports: [
|
|
515
|
-
DynamicApiModule.forRoot('...', {
|
|
516
|
-
useGlobalCache: false, // <- add this line
|
|
517
|
-
}),
|
|
518
|
-
// ...
|
|
519
|
-
],
|
|
520
|
-
controllers: [AppController],
|
|
521
|
-
providers: [AppService],
|
|
522
|
-
})
|
|
523
|
-
export class AppModule {}
|
|
524
|
-
```
|
|
525
|
-
|
|
526
|
-
**Usage**
|
|
527
|
-
|
|
528
|
-
When you request the `/users` route with the `GET` method, the response will be cached until the cache expires or until the maximum number of items is reached or until a request like `POST`, `PUT`, `PATCH`, or `DELETE` is made on the same route.
|
|
529
|
-
<br><br>Let's inspect the behavior in the network tab of our browser
|
|
530
|
-
<br>We expected to see a code `200` for the first GET request, a code `304`* for the second GET request, and again a code `200` for the third GET request made after the POST request.
|
|
531
|
-
|
|
532
|
-
**The 304 Not Modified redirect response code indicates that there is no need to retransmit the requested resources. This is an implicit redirection to a cached resource.*
|
|
533
|
-
|
|
534
|
-
```text
|
|
535
|
-
1. GET /users
|
|
536
|
-
```
|
|
537
|
-

|
|
538
|
-
|
|
539
|
-
```text
|
|
540
|
-
2. GET /users
|
|
541
|
-
```
|
|
542
|
-

|
|
543
|
-
```text
|
|
544
|
-
3. POST /users
|
|
545
|
-
```
|
|
546
|
-

|
|
547
|
-
```text
|
|
548
|
-
4. GET /users
|
|
549
|
-
```
|
|
550
|
-

|
|
551
|
-
|
|
552
|
-
___
|
|
553
|
-
|
|
554
|
-
- TOC > [Introduction](https://github.com/MikeDev75015/mongodb-dynamic-api/blob/develop/README.md#how-to-enjoy-it)
|
|
555
|
-
/ [Swagger UI](https://github.com/MikeDev75015/mongodb-dynamic-api/blob/develop/README.md#swagger-ui-optional-but-strongly-recommended)
|
|
556
|
-
/ [Validation](https://github.com/MikeDev75015/mongodb-dynamic-api/blob/develop/README.md#validation-optional)
|
|
557
|
-
/ [Versioning](https://github.com/MikeDev75015/mongodb-dynamic-api/blob/develop/README.md#versioning-optional)
|
|
558
|
-
/ [Casl](https://github.com/MikeDev75015/mongodb-dynamic-api/blob/develop/README.md#casl-only-with-authentication)
|
|
559
|
-
|
|
560
|
-
___
|
|
561
|
-
|
|
562
|
-
### [Authentication](https://docs.nestjs.com/security/authorization#integrating-casl) (optional)
|
|
563
|
-
|
|
564
|
-
An authentication strategy like <a href="https://docs.nestjs.com/security/authentication#jwt-token" target="_blank">JWT</a> is already implemented in the Dynamic API.
|
|
565
|
-
All you have to do is to pass the User object and some options to the `useAuth` property of the `DynamicApiModule.forRoot` method.
|
|
566
|
-
|
|
567
|
-
**Configuration**
|
|
568
|
-
|
|
569
|
-
Ok, let's update our `User` class to add a `password` field.
|
|
570
|
-
|
|
571
|
-
```typescript
|
|
572
|
-
// src/users/user.ts
|
|
573
|
-
import { IsEmail } from 'class-validator';
|
|
574
|
-
|
|
575
|
-
@Schema({ collection: 'users' })
|
|
576
|
-
export class User extends BaseEntity {
|
|
577
|
-
@ApiProperty()
|
|
578
|
-
@IsNotEmpty()
|
|
579
|
-
@IsString()
|
|
580
|
-
@Prop({ type: String, required: true })
|
|
581
|
-
email: string;
|
|
582
|
-
|
|
583
|
-
@Exclude()
|
|
584
|
-
@IsNotEmpty()
|
|
585
|
-
@IsString()
|
|
586
|
-
@Prop({ type: String, required: true })
|
|
587
|
-
password: string;
|
|
588
|
-
|
|
589
|
-
@ApiPropertyOptional({ type: Boolean, default: false })
|
|
590
|
-
@IsBoolean()
|
|
591
|
-
@IsOptional()
|
|
592
|
-
@Prop({ type: Boolean, default: false })
|
|
593
|
-
isAdmin: boolean;
|
|
594
|
-
|
|
595
|
-
@ApiPropertyOptional()
|
|
596
|
-
@IsNotEmpty()
|
|
597
|
-
@IsString()
|
|
598
|
-
@IsOptional()
|
|
599
|
-
@Prop({ type: String })
|
|
600
|
-
company?: string;
|
|
601
|
-
}
|
|
602
|
-
```
|
|
603
|
-
|
|
604
|
-
Now, we are going to add the `useAuth` property to the `DynamicApiModule.forRoot` method and pass the `User` object and some options.
|
|
605
|
-
<br>By default, the login field is `email` and the password field is `password`. Your User class must have these fields.
|
|
606
|
-
<br>If you want to use other fields, you can specify them in the `user` property by passing the `loginField` and / or `passwordField` properties.
|
|
607
|
-
|
|
608
|
-
```typescript
|
|
609
|
-
// src/app.module.ts
|
|
610
|
-
import { DynamicApiModule } from 'mongodb-dynamic-api';
|
|
611
|
-
import { User } from './users/user';
|
|
612
|
-
import { UsersModule } from './users/users.module';
|
|
613
|
-
|
|
614
|
-
@Module({
|
|
615
|
-
imports: [
|
|
616
|
-
DynamicApiModule.forRoot('...', {
|
|
617
|
-
// ...,
|
|
618
|
-
useAuth: { // <- add this
|
|
619
|
-
user: {
|
|
620
|
-
entity: User, // <- put here the entity which will represent a User of your API
|
|
621
|
-
},
|
|
622
|
-
},
|
|
623
|
-
}),
|
|
624
|
-
UsersModule,
|
|
625
|
-
],
|
|
626
|
-
controllers: [AppController],
|
|
627
|
-
providers: [AppService],
|
|
628
|
-
})
|
|
629
|
-
export class AppModule {}
|
|
630
|
-
```
|
|
631
|
-
|
|
632
|
-
By setting the `useAuth` property, the Dynamic API will automatically add the authentication API.
|
|
633
|
-
<br>It will add the `/auth/register`, `/auth/login`, and `/auth/account` routes to the API.
|
|
634
|
-
|
|
635
|
-
By default, only the `/auth/register` and `/auth/login` routes are public.
|
|
636
|
-
All other routes are protected and require a valid `JWT token` to access them.
|
|
637
|
-
|
|
638
|
-
**Swagger Configuration**
|
|
639
|
-
|
|
640
|
-
For Swagger users, you must enable the bearer Auth option by setting the `bearerAuth` property to `true` in the enableDynamicAPISwagger method.
|
|
641
|
-
This will add the Authorize button in the Swagger UI. This button will allow you to pass the `JWT Token` and unlock the protected routes.
|
|
642
|
-
|
|
643
|
-
```typescript
|
|
644
|
-
// src/main.ts
|
|
645
|
-
import { enableDynamicAPISwagger } from 'mongodb-dynamic-api';
|
|
646
|
-
|
|
647
|
-
async function bootstrap() {
|
|
648
|
-
const app = await NestFactory.create(AppModule);
|
|
649
|
-
// ...
|
|
650
|
-
enableDynamicAPISwagger(app, {
|
|
651
|
-
// ...,
|
|
652
|
-
swaggerExtraConfig: { // <- add this line in your main.ts file
|
|
653
|
-
bearerAuth: true,
|
|
654
|
-
},
|
|
655
|
-
});
|
|
656
|
-
|
|
657
|
-
await app.listen(3000);
|
|
658
|
-
}
|
|
659
|
-
```
|
|
660
|
-
|
|
661
|
-

|
|
662
|
-
|
|
663
|
-
<a href="https://github.com/MikeDev75015/mongodb-dynamic-api/blob/develop/README/swagger-authentication-api.md" target="_blank">See more Authentication API screenshots</a>
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
**Usage**
|
|
667
|
-
|
|
668
|
-
Ok let's add a new user with the `POST` method on the `/auth/register` route.
|
|
669
|
-
<br>You will receive a valid `JWT token` in the response.
|
|
670
|
-
|
|
671
|
-
```text
|
|
672
|
-
POST /auth/register
|
|
673
|
-
|
|
674
|
-
curl -X 'POST' \
|
|
675
|
-
'<your-host>/auth/register' \
|
|
676
|
-
-H 'accept: application/json' \
|
|
677
|
-
-H 'Content-Type: application/json' \
|
|
678
|
-
-d '{
|
|
679
|
-
"email": "<your-email>",
|
|
680
|
-
"password": "<your-password>" // <- the password will be hashed automatically before saving in the database
|
|
681
|
-
}'
|
|
682
|
-
```
|
|
683
|
-
```json
|
|
684
|
-
# Server response
|
|
685
|
-
{"accessToken":"<your-jwt-token>"}
|
|
686
|
-
```
|
|
687
|
-
|
|
688
|
-
If you go to `/auth/login` and request the route with the `POST` method passing the `email` and `password` fields in the body.
|
|
689
|
-
<br>You will also receive a valid `JWT token` in the response.
|
|
690
|
-
|
|
691
|
-
```text
|
|
692
|
-
POST /auth/login
|
|
693
|
-
|
|
694
|
-
curl -X 'POST' \
|
|
695
|
-
'<your-host>/auth/login' \
|
|
696
|
-
-H 'accept: application/json' \
|
|
697
|
-
-H 'Content-Type: application/json' \
|
|
698
|
-
-d '{
|
|
699
|
-
"email": "<your-email>",
|
|
700
|
-
"password": "<your-password>"
|
|
701
|
-
}'
|
|
702
|
-
```
|
|
703
|
-
```json
|
|
704
|
-
# Server response
|
|
705
|
-
{"accessToken":"<your-jwt-token>"}
|
|
706
|
-
```
|
|
707
|
-
|
|
708
|
-
Now let's request the `/auth/account` protected route with the `GET` method and pass our valid JWT token in the `Authorization` header.
|
|
709
|
-
|
|
710
|
-
```text
|
|
711
|
-
GET /auth/account
|
|
712
|
-
|
|
713
|
-
curl -X 'GET' \
|
|
714
|
-
'<your-host>/auth/account' \
|
|
715
|
-
-H 'accept: application/json' \
|
|
716
|
-
-H 'Authorization: Bearer <your-jwt-token>'
|
|
717
|
-
```
|
|
718
|
-
```json
|
|
719
|
-
# Server response
|
|
720
|
-
{"id":"65edc717c1ec...","email":"<your-email>"}
|
|
721
|
-
```
|
|
722
|
-
|
|
723
|
-
Great, now you have a fully functional authentication API.
|
|
724
|
-
|
|
725
|
-
All other routes are protected and require a valid JWT token to be accessed. You can easily make it public by adding the `isPublic` property to the `controllerOptions` object or to the `route` object in the `DynamicApiModule.forFeature` method.
|
|
726
|
-
|
|
727
|
-
```typescript
|
|
728
|
-
// src/users/users.module.ts
|
|
729
|
-
import { DynamicApiModule } from 'mongodb-dynamic-api';
|
|
730
|
-
import { User } from './user';
|
|
731
|
-
|
|
732
|
-
@Module({
|
|
733
|
-
imports: [
|
|
734
|
-
DynamicApiModule.forFeature({
|
|
735
|
-
entity: User,
|
|
736
|
-
controllerOptions: {
|
|
737
|
-
path: 'users',
|
|
738
|
-
isPublic: true, // <- add this to make all user API routes public
|
|
739
|
-
},
|
|
740
|
-
// ...
|
|
741
|
-
}),
|
|
742
|
-
],
|
|
743
|
-
})
|
|
744
|
-
export class UsersModule {}
|
|
745
|
-
```
|
|
746
|
-
```typescript
|
|
747
|
-
// src/users/users.module.ts
|
|
748
|
-
import { DynamicApiModule } from 'mongodb-dynamic-api';
|
|
749
|
-
import { User } from './user';
|
|
750
|
-
|
|
751
|
-
@Module({
|
|
752
|
-
imports: [
|
|
753
|
-
DynamicApiModule.forFeature({
|
|
754
|
-
entity: User,
|
|
755
|
-
controllerOptions: {
|
|
756
|
-
path: 'users',
|
|
757
|
-
},
|
|
758
|
-
routes: [
|
|
759
|
-
{ type: 'GetMany' }, // <- protected route
|
|
760
|
-
{ type: 'GetOne', isPublic: true }, // <- public route
|
|
761
|
-
{ type: 'UpdateOne' }, // <- protected route
|
|
762
|
-
{ type: 'DeleteOne' }, // <- protected route
|
|
763
|
-
],
|
|
764
|
-
}),
|
|
765
|
-
],
|
|
766
|
-
})
|
|
767
|
-
export class UsersModule {}
|
|
768
|
-
```
|
|
180
|
+
| GET **/users** <br>*Get many* | x | x | x |
|
|
181
|
+
| GET **/users/:id** <br>*Get one* | x | `id: string` | x |
|
|
182
|
+
| POST **/users/many** <br>*Create many* | `{ list: [{ name: string; email: string; }] }` | x | x |
|
|
183
|
+
| POST **/users** <br>*Create one* | `{ name: string; email: string; }` | x | x |
|
|
184
|
+
| PUT **/users/:id** <br>*Replace one* | `{ name: string; email: string; }` | `id: string` | x |
|
|
185
|
+
| PATCH **/users** <br>*Update many* | `{ name?: string; email?: string; }` | x | `ids: string[]` |
|
|
186
|
+
| PATCH **/users/:id** <br>*Update one* | `{ name?: string; email?: string; }` | `id: string` | x |
|
|
187
|
+
| DELETE **/users** <br>*Delete many* | x | x | `ids: string[]` |
|
|
188
|
+
| DELETE **/users/:id** <br>*Delete one* | x | `id: string` | x |
|
|
189
|
+
| POST **/users/duplicate** <br>*Duplicate many* | `{ name?: string; email?: string; }` | x | `ids: string[]` |
|
|
190
|
+
| POST **/users/duplicate/:id**<br>*Duplicate one* | `{ name?: string; email?: string; }` | `id: string` | x |
|
|
769
191
|
|
|
770
192
|
___
|
|
771
193
|
|
|
772
|
-
|
|
773
|
-
/ [Swagger UI](https://github.com/MikeDev75015/mongodb-dynamic-api/blob/develop/README.md#swagger-ui-optional-but-strongly-recommended)
|
|
774
|
-
/ [Validation](https://github.com/MikeDev75015/mongodb-dynamic-api/blob/develop/README.md#validation-optional)
|
|
775
|
-
/ [Versioning](https://github.com/MikeDev75015/mongodb-dynamic-api/blob/develop/README.md#versioning-optional)
|
|
776
|
-
/ [Caching](https://github.com/MikeDev75015/mongodb-dynamic-api/blob/develop/README.md#caching-enabled-by-default)
|
|
194
|
+
Go further with optional features like **Swagger UI**, **Validation**, **Caching**, **Authentication** and **Authorization**.
|
|
777
195
|
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
<br>Authentication is required, you need to enable it or implement your own strategy that adds the User object in the request.
|
|
784
|
-
|
|
785
|
-
**MongoDB dynamic API** uses the `User` object in the requests to apply the ability predicates defined in the `DynamicApiModule.forFeature`.
|
|
786
|
-
<br>You can define them either **in the controller options**,
|
|
787
|
-
or **in each route object** declared in the routes property.
|
|
788
|
-
<br>*If the ability predicates are specified in 2, those defined in the route will have priority.*
|
|
789
|
-
|
|
790
|
-
**An ability predicate is an arrow function that takes a subject and the User object (optional) as arguments and returns a boolean.**
|
|
791
|
-
|
|
792
|
-
Let's create a new Article content and set the ability predicates to the `UpdateOne`, `DeleteOne` and `DeleteMany` routes.
|
|
793
|
-
|
|
794
|
-
**Configuration**
|
|
795
|
-
|
|
796
|
-
```typescript
|
|
797
|
-
// src/articles/article.ts
|
|
798
|
-
import { Prop } from '@nestjs/mongoose';
|
|
799
|
-
import { ApiProperty } from '@nestjs/swagger';
|
|
800
|
-
import { BaseEntity } from 'mongodb-dynamic-api';
|
|
801
|
-
|
|
802
|
-
export class Article extends BaseEntity {
|
|
803
|
-
@ApiProperty({ type: Boolean, default: false })
|
|
804
|
-
@Prop({ type: Boolean, default: false })
|
|
805
|
-
isPublished: boolean;
|
|
806
|
-
|
|
807
|
-
@ApiProperty()
|
|
808
|
-
@Prop({ type: String })
|
|
809
|
-
authorId: string;
|
|
810
|
-
}
|
|
811
|
-
```
|
|
812
|
-
|
|
813
|
-
```typescript
|
|
814
|
-
// src/articles/articles.module.ts
|
|
815
|
-
import { Module } from '@nestjs/common';
|
|
816
|
-
import { DynamicApiModule } from 'mongodb-dynamic-api';
|
|
817
|
-
import { User } from '../users/user';
|
|
818
|
-
import { Article } from './article';
|
|
819
|
-
|
|
820
|
-
@Module({
|
|
821
|
-
imports: [
|
|
822
|
-
DynamicApiModule.forFeature({
|
|
823
|
-
entity: Article,
|
|
824
|
-
controllerOptions: {
|
|
825
|
-
path: 'articles',
|
|
826
|
-
abilityPredicates: [ // <- declare the ability predicates in the controller options
|
|
827
|
-
{
|
|
828
|
-
targets: ['DeleteMany', 'DeleteOne'], // <- declare the targets
|
|
829
|
-
predicate: (_: Article, user: User) => user.isAdmin, // <- add the condition
|
|
830
|
-
},
|
|
831
|
-
],
|
|
832
|
-
},
|
|
833
|
-
routes: [
|
|
834
|
-
{ type: 'GetMany', isPublic: true },
|
|
835
|
-
{ type: 'GetOne', isPublic: true },
|
|
836
|
-
{ type: 'CreateOne' },
|
|
837
|
-
{
|
|
838
|
-
type: 'UpdateOne',
|
|
839
|
-
abilityPredicate: (article: Article, user: User) => // <- declare the ability predicate in the route object
|
|
840
|
-
article.authorId === user.id && !article.isPublished,
|
|
841
|
-
},
|
|
842
|
-
],
|
|
843
|
-
}),
|
|
844
|
-
],
|
|
845
|
-
})
|
|
846
|
-
export class ArticlesModule {}
|
|
847
|
-
```
|
|
848
|
-
|
|
849
|
-
```typescript
|
|
850
|
-
// src/app.module.ts
|
|
851
|
-
import { Module } from '@nestjs/common';
|
|
852
|
-
import { DynamicApiModule } from 'mongodb-dynamic-api';
|
|
853
|
-
import { AppController } from './app.controller';
|
|
854
|
-
import { AppService } from './app.service';
|
|
855
|
-
import { User } from './users/user';
|
|
856
|
-
import { ArticlesModule } from './articles/articles.module';
|
|
857
|
-
|
|
858
|
-
@Module({
|
|
859
|
-
imports: [
|
|
860
|
-
DynamicApiModule.forRoot(
|
|
861
|
-
'your-mongodb-uri',
|
|
862
|
-
{
|
|
863
|
-
useAuth: {
|
|
864
|
-
user: {
|
|
865
|
-
entity: User,
|
|
866
|
-
additionalFields: {
|
|
867
|
-
toRegister: ['isAdmin'], // <- here you can set additional fields to display in the register body
|
|
868
|
-
toRequest: ['isAdmin', 'company'], // <- here you can set additional fields to the User object in the request
|
|
869
|
-
},
|
|
870
|
-
},
|
|
871
|
-
},
|
|
872
|
-
},
|
|
873
|
-
),
|
|
874
|
-
ArticlesModule,
|
|
875
|
-
],
|
|
876
|
-
controllers: [AppController],
|
|
877
|
-
providers: [AppService],
|
|
878
|
-
})
|
|
879
|
-
export class AppModule {}
|
|
880
|
-
```
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
**Usage**
|
|
884
|
-
|
|
885
|
-
First, let's create an admin user with the `POST` method on the `/auth/register` public route.
|
|
886
|
-
```text
|
|
887
|
-
POST /auth/register
|
|
888
|
-
|
|
889
|
-
curl -X 'POST' \
|
|
890
|
-
'<your-host>/auth/register' \
|
|
891
|
-
-H 'accept: application/json' \
|
|
892
|
-
-H 'Content-Type: application/json' \
|
|
893
|
-
-d '{
|
|
894
|
-
"email": "admin@test.co",
|
|
895
|
-
"isAdmin": true,
|
|
896
|
-
"password": "admin"
|
|
897
|
-
}'
|
|
898
|
-
```
|
|
899
|
-
|
|
900
|
-
Then, we are going to protect the `/auth/register` route by setting the `protectRegister` property to `true` and add a **register ability predicate** in the useAuth Object of the `DynamicApiModule.forRoot` method.
|
|
901
|
-
```typescript
|
|
902
|
-
// src/app.module.ts
|
|
903
|
-
@Module({
|
|
904
|
-
imports: [
|
|
905
|
-
DynamicApiModule.forRoot(
|
|
906
|
-
'your-mongodb-uri',
|
|
907
|
-
{
|
|
908
|
-
useAuth: {
|
|
909
|
-
// ...,
|
|
910
|
-
protectRegister: true, // <- add this line
|
|
911
|
-
registerAbilityPredicate: (user: User) => user.isAdmin,
|
|
912
|
-
},
|
|
913
|
-
},
|
|
914
|
-
),
|
|
915
|
-
```
|
|
916
|
-
|
|
917
|
-
Ok, now let's create a non admin user with the `POST` method on the `/auth/register` route.
|
|
918
|
-
```text
|
|
919
|
-
POST /auth/register
|
|
920
|
-
|
|
921
|
-
curl -X 'POST' \
|
|
922
|
-
'<your-host>/auth/register' \
|
|
923
|
-
-H 'accept: application/json' \
|
|
924
|
-
-H 'Content-Type: application/json' \
|
|
925
|
-
-d '{
|
|
926
|
-
"email": "toto@test.co",
|
|
927
|
-
"password": "toto"
|
|
928
|
-
}'
|
|
929
|
-
```
|
|
930
|
-
```json
|
|
931
|
-
# Server response
|
|
932
|
-
{"accessToken":"<toto-jwt-token>"}
|
|
933
|
-
```
|
|
934
|
-
|
|
935
|
-
Next, under toto's account (not admin), we will try to register a new user with the `POST` method on the `/auth/register` route.
|
|
936
|
-
<br>The register ability predicate will return `false` and we will receive a `403 Forbidden` error.
|
|
937
|
-
|
|
938
|
-
```text
|
|
939
|
-
POST /auth/register
|
|
940
|
-
|
|
941
|
-
curl -X 'POST' \
|
|
942
|
-
'http://localhost:5000/auth/register' \
|
|
943
|
-
-H 'accept: application/json' \
|
|
944
|
-
-H 'Authorization: Bearer <toto-jwt-token>' \
|
|
945
|
-
-H 'Content-Type: application/json' \
|
|
946
|
-
-d '{
|
|
947
|
-
"email": "bill@test.co",
|
|
948
|
-
"password": "bill"
|
|
949
|
-
}'
|
|
950
|
-
```
|
|
951
|
-
```json
|
|
952
|
-
# Server response
|
|
953
|
-
{
|
|
954
|
-
"message": "Forbidden resource",
|
|
955
|
-
"error": "Forbidden",
|
|
956
|
-
"statusCode": 403
|
|
957
|
-
}
|
|
958
|
-
```
|
|
959
|
-
|
|
960
|
-
The register route is now well protected and only an admin user can create new users.
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
___
|
|
196
|
+
- **[Swagger UI](https://github.com/MikeDev75015/mongodb-dynamic-api/blob/develop/README/swagger-ui.md)**
|
|
197
|
+
- **[Validation](https://github.com/MikeDev75015/mongodb-dynamic-api/blob/develop/README/validation.md)** with **Class Validator**
|
|
198
|
+
- **[Caching](https://github.com/MikeDev75015/mongodb-dynamic-api/blob/develop/README/caching.md)** with **cache-manager**
|
|
199
|
+
- **[Authentication](https://github.com/MikeDev75015/mongodb-dynamic-api/blob/develop/README/authentication.md)** with **JWT**
|
|
200
|
+
- **[Authorization](https://github.com/MikeDev75015/mongodb-dynamic-api/blob/develop/README/authorization.md)** with **Casl**
|
|
964
201
|
|
|
965
|
-
More coming soon...
|
|
966
202
|
|
|
967
203
|
|
|
968
204
|
|
|
205
|
+
<br>
|
|
206
|
+
<br>
|
|
207
|
+
<br>
|
|
969
208
|
|