mongodb-dynamic-api 2.14.2 β†’ 3.0.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 (35) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/README.md +745 -69
  3. package/package.json +15 -15
  4. package/src/decorators/api-endpoint-visibility.decorator.js +2 -1
  5. package/src/decorators/api-endpoint-visibility.decorator.js.map +1 -1
  6. package/src/guards/base-policies.guard.d.ts +1 -0
  7. package/src/helpers/repository.helper.d.ts +1 -0
  8. package/src/helpers/schema.helper.d.ts +1 -0
  9. package/src/interfaces/dynamic-api-cache-options.interface.d.ts +2 -2
  10. package/src/interfaces/dynamic-api-global-state.interface.d.ts +1 -0
  11. package/src/interfaces/dynamic-api-policy-handler.interface.d.ts +1 -0
  12. package/src/interfaces/dynamic-api-schema-options.interface.d.ts +1 -0
  13. package/src/interfaces/dynamic-api-service-callback.interface.d.ts +1 -0
  14. package/src/models/base-entity.model.d.ts +1 -0
  15. package/src/modules/auth/auth.module.d.ts +1 -1
  16. package/src/modules/auth/services/base-auth.service.d.ts +1 -0
  17. package/src/modules/auth/strategies/jwt.strategy.d.ts +3 -1
  18. package/src/routes/aggregate/base-aggregate.service.d.ts +1 -0
  19. package/src/routes/create-many/base-create-many.service.d.ts +1 -0
  20. package/src/routes/create-one/base-create-one.service.d.ts +1 -0
  21. package/src/routes/delete-many/base-delete-many.service.d.ts +1 -0
  22. package/src/routes/delete-one/base-delete-one.service.d.ts +1 -0
  23. package/src/routes/duplicate-many/base-duplicate-many.service.d.ts +1 -0
  24. package/src/routes/duplicate-one/base-duplicate-one.service.d.ts +1 -0
  25. package/src/routes/get-many/base-get-many.service.d.ts +1 -0
  26. package/src/routes/get-one/base-get-one.service.d.ts +1 -0
  27. package/src/routes/replace-one/base-replace-one.service.d.ts +1 -0
  28. package/src/routes/update-many/base-update-many.service.d.ts +1 -0
  29. package/src/routes/update-one/base-update-one.service.d.ts +1 -0
  30. package/src/services/base/base.service.d.ts +1 -0
  31. package/src/services/dynamic-api-global-state/dynamic-api-global-state.service.d.ts +2 -1
  32. package/src/version.json +1 -1
  33. package/test/e2e.setup.d.ts +1 -0
  34. package/test/utils.d.ts +2 -1
  35. package/tsconfig.tsbuildinfo +1 -1
package/README.md CHANGED
@@ -48,7 +48,10 @@
48
48
  ---
49
49
 
50
50
  ## mongodb-dynamic-api <img src="https://pbs.twimg.com/media/EDoWJbUXYAArclg.png" width="24" height="24" />
51
- ```text
51
+
52
+ A powerful, production-ready NestJS module that automatically generates fully functional REST APIs with WebSocket support for MongoDB collections.
53
+
54
+ ```bash
52
55
  npm install --save mongodb-dynamic-api
53
56
  ```
54
57
 
@@ -60,91 +63,157 @@ npm install --save mongodb-dynamic-api
60
63
 
61
64
  </div>
62
65
 
66
+ ## πŸ“‹ Table of Contents
67
+
68
+ - [Overview](#overview)
69
+ - [Key Features](#key-features)
70
+ - [Installation](#installation)
71
+ - [Quick Start](#quick-start)
72
+ - [Advanced Features](#advanced-features)
73
+ - [API Reference](#api-reference)
74
+ - [Best Practices](#best-practices)
75
+
76
+ ---
77
+
78
+ ## Overview
79
+
80
+ **mongodb-dynamic-api** is a flexible and highly configurable NestJS 11 module that dramatically accelerates API development by automatically generating complete CRUD operations for your MongoDB collections. Instead of writing repetitive boilerplate code, define your data models and let the module handle the rest.
81
+
82
+ The module provides:
83
+ - **Automatic CRUD generation** - Full REST API endpoints generated from your schemas
84
+ - **Real-time capabilities** - Built-in WebSocket support for live updates
85
+ - **Enterprise-ready features** - Authentication, authorization, caching, and validation out of the box
86
+ - **Developer-friendly** - Swagger documentation auto-generated for all endpoints
87
+ - **Highly customizable** - Fine-grained control at global, controller, and route levels
88
+
89
+ ---
90
+
91
+ ## Key Features
92
+
93
+ | Feature | Description |
94
+ |---------|-------------|
95
+ | πŸš€ **Zero Boilerplate** | Generate complete CRUD APIs from schema definitions |
96
+ | πŸ” **JWT Authentication** | Built-in authentication with 6 endpoints (login, register, get/update account, reset/change password) |
97
+ | πŸ›‘οΈ **Authorization** | Role-based access control with ability predicates |
98
+ | ⚑ **Smart Caching** | Global caching with automatic invalidation |
99
+ | βœ… **Validation** | Global and per-route validation with class-validator |
100
+ | πŸ“‘ **WebSockets** | Socket.IO support for calling routes via WebSocket events |
101
+ | πŸ“š **Swagger UI** | Auto-generated OpenAPI documentation |
102
+ | πŸ”„ **Versioning** | URI-based API versioning |
103
+ | πŸ—‘οΈ **Soft Delete** | Built-in soft delete with `SoftDeletableEntity` |
104
+ | πŸ” **Advanced Queries** | MongoDB queries + Aggregation pipelines for complex analytics |
105
+
106
+ ---
63
107
 
64
- <p style="text-align: justify; width: 100%;font-size: 15px;">
108
+ ## Installation
65
109
 
66
- In summary, DynamicApiModule is a flexible and configurable module using NestJS 10 that provides dynamic API functionality for your contents.
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](https://github.com/MikeDev75015/mongodb-dynamic-api/blob/develop/README/swagger-ui.md),
70
- [Versioning](https://github.com/MikeDev75015/mongodb-dynamic-api/blob/develop/README/versioning.md),
71
- [Validation](https://github.com/MikeDev75015/mongodb-dynamic-api/blob/develop/README/validation.md),
72
- [Caching](https://github.com/MikeDev75015/mongodb-dynamic-api/blob/develop/README/caching.md),
73
- [Authentication](https://github.com/MikeDev75015/mongodb-dynamic-api/blob/develop/README/authentication.md),
74
- [Authorization](https://github.com/MikeDev75015/mongodb-dynamic-api/blob/develop/README/authorization.md) and
75
- [WebSockets](https://github.com/MikeDev75015/mongodb-dynamic-api/blob/develop/README/websockets.md).
110
+ ### Prerequisites
76
111
 
77
- </p>
112
+ - Node.js >= 16.0.0
113
+ - NestJS >= 11.0.0
114
+ - MongoDB >= 4.0
78
115
 
79
- ___
116
+ ### Create a New Project
80
117
 
81
- ### HOW TO ENJOY IT
118
+ Start a new NestJS project with TypeScript in strict mode:
82
119
 
83
- - Start a new [nest](https://docs.nestjs.com/) project with **typescript** (use the `--strict` option)
84
- ```text
120
+ ```bash
85
121
  nest new --strict your-project-name
122
+ cd your-project-name
86
123
  ```
87
124
 
88
- - Go to your new project root and install the [mongodb-dynamic-api](https://www.npmjs.com/package/mongodb-dynamic-api) package
89
- ```text
90
- npm i -S mongodb-dynamic-api
125
+ ### Install the Package
126
+
127
+ Install mongodb-dynamic-api (all dependencies are included):
128
+
129
+ ```bash
130
+ npm install --save mongodb-dynamic-api
91
131
  ```
92
- **Basic Configuration**
93
132
 
94
- - Add `DynamicApiModule.forRoot` to your `app.module.ts` and pass your **MongoDB connection string** to the method.
133
+ > **✨ Note:** All required dependencies are included in the package:
134
+ > - `@nestjs/mongoose` & `mongoose` - MongoDB integration
135
+ > - `@nestjs/jwt` & `@nestjs/passport` - Authentication
136
+ > - `@nestjs/swagger` - API documentation
137
+ > - `class-validator` & `class-transformer` - Validation & transformation
138
+ > - `@nestjs/cache-manager` & `cache-manager` - Caching
139
+ > - `@nestjs/websockets` & `socket.io` - Real-time support
140
+ >
141
+ > **No additional packages required** for any of the optional features (authentication, caching, websockets, validation, etc.).
142
+
143
+ ---
144
+
145
+ ## Quick Start
146
+
147
+ This guide will help you set up your first dynamic API in less than 5 minutes.
148
+
149
+ ### Step 1: Configure the Root Module
150
+
151
+ Import `DynamicApiModule.forRoot()` in your `app.module.ts` and provide your MongoDB connection string:
95
152
 
96
153
  ```typescript
97
154
  // src/app.module.ts
155
+ import { Module } from '@nestjs/common';
98
156
  import { DynamicApiModule } from 'mongodb-dynamic-api';
157
+ import { AppController } from './app.controller';
158
+ import { AppService } from './app.service';
99
159
 
100
160
  @Module({
101
161
  imports: [
102
162
  DynamicApiModule.forRoot(
103
- 'mongodb-uri', // <- replace by your own MongoDB connection string
163
+ 'mongodb://localhost:27017/my-database', // MongoDB connection URI
104
164
  ),
105
- // ...
106
165
  ],
107
166
  controllers: [AppController],
108
167
  providers: [AppService],
109
168
  })
110
169
  export class AppModule {}
111
170
  ```
112
- **Basic Usage**
113
-
114
- - 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.
115
- - 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.
116
171
 
117
- - You must extend the `BaseEntity` | `SoftDeletableEntity` class from the `mongodb-dynamic-api` package for all your **collection models**.
118
- See more details **[here](https://github.com/MikeDev75015/mongodb-dynamic-api/blob/develop/README/entities.md)**.
172
+ > **πŸ’‘ Tip:** Use environment variables for connection strings in production:
173
+ > ```typescript
174
+ > DynamicApiModule.forRoot(process.env.MONGODB_URI)
175
+ > ```
119
176
 
120
- - You can also add the `@DynamicAPISchemaOptions` decorator to pass schema options.
121
- See more details **[here](https://github.com/MikeDev75015/mongodb-dynamic-api/blob/develop/README/schema-options.md)**.
177
+ ### Step 2: Define Your Entity
122
178
 
123
- Just create a new file `user.ts` and add the following code.
179
+ Create your first entity using Mongoose decorators. All entities must extend either `BaseEntity` or `SoftDeletableEntity`:
124
180
 
125
181
  ```typescript
126
- // src/users/user.ts
182
+ // src/users/user.entity.ts
127
183
  import { Prop, Schema } from '@nestjs/mongoose';
128
184
  import { BaseEntity } from 'mongodb-dynamic-api';
129
185
 
130
186
  @Schema({ collection: 'users' })
131
- export class User extends BaseEntity { // <- extends BaseEntity
187
+ export class User extends BaseEntity {
132
188
  @Prop({ type: String, required: true })
133
189
  name: string;
134
190
 
135
- @Prop({ type: String, required: true })
191
+ @Prop({ type: String, required: true, unique: true })
136
192
  email: string;
193
+
194
+ @Prop({ type: String })
195
+ phone?: string;
196
+
197
+ @Prop({ type: Boolean, default: true })
198
+ isActive: boolean;
137
199
  }
138
200
  ```
139
201
 
140
- - Then we will use the `DynamicApiModule.forFeature` method to add the `User` API to our application.
141
- - We pass the `User` class to the `entity` property and specify the path `users` to the `controllerOptions` property.
142
- - Create a new file `users.module.ts` and add the following code.
202
+ > **πŸ“ Note:**
203
+ > - `BaseEntity` provides `id`, `createdAt`, and `updatedAt` fields automatically
204
+ > - Timestamps are **automatically enabled** - no need to add `timestamps: true` in `@Schema()`
205
+ > - `_id` and `__v` are automatically excluded from JSON responses
206
+ > - See [Entities documentation](https://github.com/MikeDev75015/mongodb-dynamic-api/blob/develop/README/entities.md) for more details
207
+
208
+ ### Step 3: Create a Feature Module
209
+
210
+ Use `DynamicApiModule.forFeature()` to generate the API endpoints:
143
211
 
144
212
  ```typescript
145
213
  // src/users/users.module.ts
214
+ import { Module } from '@nestjs/common';
146
215
  import { DynamicApiModule } from 'mongodb-dynamic-api';
147
- import { User } from './user';
216
+ import { User } from './user.entity';
148
217
 
149
218
  @Module({
150
219
  imports: [
@@ -159,19 +228,22 @@ import { User } from './user';
159
228
  export class UsersModule {}
160
229
  ```
161
230
 
162
- - Last step, add the `UsersModule` to the **imports** in the `app.module.ts` after the `DynamicApiModule.forRoot` method.
231
+ ### Step 4: Register the Feature Module
232
+
233
+ Add `UsersModule` to the imports array in `app.module.ts`:
163
234
 
164
235
  ```typescript
165
236
  // src/app.module.ts
237
+ import { Module } from '@nestjs/common';
166
238
  import { DynamicApiModule } from 'mongodb-dynamic-api';
167
239
  import { UsersModule } from './users/users.module';
168
240
 
169
241
  @Module({
170
242
  imports: [
171
243
  DynamicApiModule.forRoot(
172
- 'mongodb-uri', // <- replace by your own MongoDB connection string
244
+ 'mongodb://localhost:27017/my-database',
173
245
  ),
174
- UsersModule,
246
+ UsersModule, // Add your feature module here
175
247
  ],
176
248
  controllers: [AppController],
177
249
  providers: [AppService],
@@ -179,38 +251,642 @@ import { UsersModule } from './users/users.module';
179
251
  export class AppModule {}
180
252
  ```
181
253
 
182
- **And that's all !** *You now have a fully functional CRUD API for the `User` content at the `/users` path.*
254
+ ### Step 5: Start Your Application
255
+
256
+ ```bash
257
+ npm run start:dev
258
+ ```
259
+
260
+ **πŸŽ‰ Congratulations!** Your API is now running with complete CRUD operations at `http://localhost:3000/users`
261
+
262
+ ---
263
+
264
+ ## API Reference
265
+
266
+ Your generated API includes the following endpoints:
183
267
 
268
+ ### Available Endpoints
184
269
 
185
- | Endpoint | Body | Param | Query |
186
- |:--------------------------------------------------|:----------------------------------------------:|:------------:|:---------------:|
187
- | GET **/users** <br>*Get many* | x | x | x |
188
- | GET **/users/:id** <br>*Get one* | x | `id: string` | x |
189
- | POST **/users/many** <br>*Create many* | `{ list: [{ name: string; email: string; }] }` | x | x |
190
- | POST **/users** <br>*Create one* | `{ name: string; email: string; }` | x | x |
191
- | PUT **/users/:id** <br>*Replace one* | `{ name: string; email: string; }` | `id: string` | x |
192
- | PATCH **/users** <br>*Update many* | `{ name?: string; email?: string; }` | x | `ids: string[]` |
193
- | PATCH **/users/:id** <br>*Update one* | `{ name?: string; email?: string; }` | `id: string` | x |
194
- | DELETE **/users** <br>*Delete many* | x | x | `ids: string[]` |
195
- | DELETE **/users/:id** <br>*Delete one* | x | `id: string` | x |
196
- | POST **/users/duplicate** <br>*Duplicate many* | `{ name?: string; email?: string; }` | x | `ids: string[]` |
197
- | POST **/users/duplicate/:id**<br>*Duplicate one* | `{ name?: string; email?: string; }` | `id: string` | x |
270
+ | Endpoint | Method | Description | Request Body | Params | Query |
271
+ |:---------|:------:|:------------|:-------------|:-------|:------|
272
+ | `/users` | `GET` | Retrieve all users | - | - | MongoDB query object |
273
+ | `/users/:id` | `GET` | Retrieve a single user by ID | - | `id` | - |
274
+ | `/users/many` | `POST` | Create multiple users at once | `{ list: User[] }` | - | - |
275
+ | `/users` | `POST` | Create a single user | `User` | - | - |
276
+ | `/users/:id` | `PUT` | Replace a user completely | `User` | `id` | - |
277
+ | `/users` | `PATCH` | Update multiple users | `Partial<User>` | - | `ids[]` |
278
+ | `/users/:id` | `PATCH` | Update a single user partially | `Partial<User>` | `id` | - |
279
+ | `/users` | `DELETE` | Delete multiple users | - | - | `ids[]` |
280
+ | `/users/:id` | `DELETE` | Delete a single user | - | `id` | - |
281
+ | `/users/duplicate` | `POST` | Duplicate multiple users with updates | `Partial<User>` | - | `ids[]` |
282
+ | `/users/duplicate/:id` | `POST` | Duplicate a single user with updates | `Partial<User>` | `id` | - |
283
+ | `/users/aggregate` | `GET` | Execute MongoDB aggregation pipeline | - | - | Query DTO params |
198
284
 
199
- ___
285
+ > **πŸ’‘ Note:** The `Aggregate` route requires a custom Query DTO with a `toPipeline` static method. See [Advanced Queries documentation](#advanced-queries-with-aggregate) below.
200
286
 
201
- Go further with optional features like:
202
- - **[Swagger UI](https://github.com/MikeDev75015/mongodb-dynamic-api/blob/develop/README/swagger-ui.md)**
203
- - **[Versioning](https://github.com/MikeDev75015/mongodb-dynamic-api/blob/develop/README/versioning.md)**
204
- - **[Validation](https://github.com/MikeDev75015/mongodb-dynamic-api/blob/develop/README/validation.md)**
205
- - **[Caching](https://github.com/MikeDev75015/mongodb-dynamic-api/blob/develop/README/caching.md)**
206
- - **[Authentication](https://github.com/MikeDev75015/mongodb-dynamic-api/blob/develop/README/authentication.md)**
207
- - **[Authorization](https://github.com/MikeDev75015/mongodb-dynamic-api/blob/develop/README/authorization.md)**
208
- - **[WebSockets](https://github.com/MikeDev75015/mongodb-dynamic-api/blob/develop/README/websockets.md)**
287
+ ### Request Examples
288
+
289
+ #### Get Many Users
290
+
291
+ ```bash
292
+ # Get all users
293
+ GET /users
294
+
295
+ # Query parameters are passed directly to MongoDB find()
296
+ # You can use any valid MongoDB query
297
+ GET /users?isActive=true&name=John
298
+ ```
299
+
300
+ #### Create One User
301
+
302
+ ```bash
303
+ POST /users
304
+ Content-Type: application/json
305
+
306
+ {
307
+ "name": "John Doe",
308
+ "email": "john.doe@example.com",
309
+ "phone": "+1234567890",
310
+ "isActive": true
311
+ }
312
+ ```
313
+
314
+ **Response (201 Created):**
315
+ ```json
316
+ {
317
+ "id": "507f1f77bcf86cd799439011",
318
+ "name": "John Doe",
319
+ "email": "john.doe@example.com",
320
+ "phone": "+1234567890",
321
+ "isActive": true,
322
+ "createdAt": "2026-02-21T10:30:00.000Z",
323
+ "updatedAt": "2026-02-21T10:30:00.000Z"
324
+ }
325
+ ```
326
+
327
+ #### Create Many Users
328
+
329
+ ```bash
330
+ POST /users/many
331
+ Content-Type: application/json
332
+
333
+ {
334
+ "list": [
335
+ {
336
+ "name": "Alice Smith",
337
+ "email": "alice@example.com"
338
+ },
339
+ {
340
+ "name": "Bob Johnson",
341
+ "email": "bob@example.com"
342
+ }
343
+ ]
344
+ }
345
+ ```
346
+
347
+ **Response (201 Created):**
348
+ ```json
349
+ [
350
+ {
351
+ "id": "507f1f77bcf86cd799439011",
352
+ "name": "Alice Smith",
353
+ "email": "alice@example.com",
354
+ "isActive": true,
355
+ "createdAt": "2026-02-21T10:30:00.000Z",
356
+ "updatedAt": "2026-02-21T10:30:00.000Z"
357
+ },
358
+ {
359
+ "id": "507f1f77bcf86cd799439012",
360
+ "name": "Bob Johnson",
361
+ "email": "bob@example.com",
362
+ "isActive": true,
363
+ "createdAt": "2026-02-21T10:30:00.000Z",
364
+ "updatedAt": "2026-02-21T10:30:00.000Z"
365
+ }
366
+ ]
367
+ ```
368
+
369
+ #### Update One User
370
+
371
+ ```bash
372
+ PATCH /users/507f1f77bcf86cd799439011
373
+ Content-Type: application/json
374
+
375
+ {
376
+ "phone": "+0987654321",
377
+ "isActive": false
378
+ }
379
+ ```
380
+
381
+ **Response (200 OK):**
382
+ ```json
383
+ {
384
+ "id": "507f1f77bcf86cd799439011",
385
+ "name": "John Doe",
386
+ "email": "john.doe@example.com",
387
+ "phone": "+0987654321",
388
+ "isActive": false,
389
+ "createdAt": "2026-02-21T10:30:00.000Z",
390
+ "updatedAt": "2026-02-21T10:35:00.000Z"
391
+ }
392
+ ```
393
+
394
+ #### Duplicate One User
395
+
396
+ ```bash
397
+ POST /users/duplicate/507f1f77bcf86cd799439011
398
+ Content-Type: application/json
399
+
400
+ {
401
+ "email": "john.doe.copy@example.com"
402
+ }
403
+ ```
404
+
405
+ **Response (201 Created):**
406
+ ```json
407
+ {
408
+ "id": "507f1f77bcf86cd799439013",
409
+ "name": "John Doe",
410
+ "email": "john.doe.copy@example.com",
411
+ "phone": "+0987654321",
412
+ "isActive": false,
413
+ "createdAt": "2026-02-21T10:40:00.000Z",
414
+ "updatedAt": "2026-02-21T10:40:00.000Z"
415
+ }
416
+ ```
417
+
418
+ ---
419
+
420
+ ## Advanced Features
421
+
422
+ The module supports powerful customization options. Here are some quick examples:
423
+
424
+ ### Advanced Queries with Aggregate
425
+
426
+ The `Aggregate` route type enables **MongoDB aggregation pipelines** for complex queries, analytics, and data transformations. This is perfect for:
427
+ - Grouping and counting data
428
+ - Calculating statistics (sum, average, min, max)
429
+ - Joining related collections with `$lookup`
430
+ - Complex filtering and transformations
431
+ - Pagination with counting
432
+
433
+ **Key Requirements:**
434
+ 1. You **must** provide a custom Query DTO
435
+ 2. The Query DTO **must** have a static `toPipeline` method
436
+ 3. The method returns an array of MongoDB pipeline stages
437
+ 4. Optionally provide a custom Presenter with `fromAggregate` method
438
+
439
+ **Example - User Statistics:**
440
+
441
+ ```typescript
442
+ // src/users/dtos/user-stats.query.ts
443
+ import { IsOptional, IsString } from 'class-validator';
444
+ import { PipelineStage } from 'mongodb-pipeline-builder';
445
+
446
+ export class UserStatsQuery {
447
+ @IsOptional()
448
+ @IsString()
449
+ status?: string;
450
+
451
+ static toPipeline(query: UserStatsQuery): PipelineStage[] {
452
+ const pipeline: PipelineStage[] = [];
453
+
454
+ // Filter by status if provided
455
+ if (query.status) {
456
+ pipeline.push({ $match: { isActive: query.status === 'active' } });
457
+ }
458
+
459
+ // Group by creation date (year-month)
460
+ pipeline.push({
461
+ $group: {
462
+ _id: {
463
+ year: { $year: '$createdAt' },
464
+ month: { $month: '$createdAt' },
465
+ },
466
+ count: { $sum: 1 },
467
+ users: { $push: { id: '$_id', name: '$name', email: '$email' } },
468
+ },
469
+ });
470
+
471
+ // Sort by date
472
+ pipeline.push({
473
+ $sort: { '_id.year': -1, '_id.month': -1 },
474
+ });
475
+
476
+ return pipeline;
477
+ }
478
+ }
479
+
480
+ // src/users/dtos/user-stats.presenter.ts
481
+ export class UserStatsPresenter {
482
+ period: string;
483
+ count: number;
484
+ users: { id: string; name: string; email: string }[];
485
+
486
+ static fromAggregate(results: any[]): UserStatsPresenter[] {
487
+ return results.map(result => ({
488
+ period: `${result._id.year}-${String(result._id.month).padStart(2, '0')}`,
489
+ count: result.count,
490
+ users: result.users,
491
+ }));
492
+ }
493
+ }
494
+
495
+ // src/users/users.module.ts
496
+ @Module({
497
+ imports: [
498
+ DynamicApiModule.forFeature({
499
+ entity: User,
500
+ controllerOptions: { path: 'users' },
501
+ routes: [
502
+ {
503
+ type: 'Aggregate',
504
+ subPath: 'stats',
505
+ dTOs: {
506
+ query: UserStatsQuery,
507
+ presenter: UserStatsPresenter,
508
+ },
509
+ },
510
+ ],
511
+ }),
512
+ ],
513
+ })
514
+ export class UsersModule {}
515
+ ```
516
+
517
+ **Usage:**
518
+ ```bash
519
+ # Get all user statistics
520
+ GET /users/aggregate/stats
521
+
522
+ # Filter by active users
523
+ GET /users/aggregate/stats?status=active
524
+ ```
525
+
526
+ **With Pagination Support:**
527
+
528
+ The Aggregate route automatically detects pagination when your pipeline starts with a `$facet` stage:
529
+
530
+ ```typescript
531
+ import { Type } from 'class-transformer';
532
+ import { IsOptional, IsNumber } from 'class-validator';
533
+ import { PipelineBuilder, PipelineStage } from 'mongodb-pipeline-builder';
534
+
535
+ export class PaginatedUsersQuery {
536
+ @IsOptional()
537
+ @IsNumber()
538
+ @Type(() => Number)
539
+ page?: number = 1;
540
+
541
+ @IsOptional()
542
+ @IsNumber()
543
+ @Type(() => Number)
544
+ limit?: number = 10;
545
+
546
+ static toPipeline(query: PaginatedUsersQuery): PipelineStage[] {
547
+ const builder = new PipelineBuilder('paginated-users');
548
+
549
+ // Using method chaining - all methods return the builder
550
+ return builder
551
+ .Match({ isActive: true }) // Add your filters
552
+ .Paging(query.limit, query.page) // Paging(elementsPerPage, page)
553
+ .build(); // Build and return the pipeline array
554
+ }
555
+ }
556
+ ```
557
+
558
+ **Response with pagination:**
559
+ ```json
560
+ {
561
+ "list": [...],
562
+ "count": 150,
563
+ "totalPage": 15
564
+ }
565
+ ```
566
+
567
+ > πŸ“š **Learn More:** The module uses [`mongodb-pipeline-builder`](https://www.npmjs.com/package/mongodb-pipeline-builder) (by the same author) to greatly simplify building aggregation pipelines with:
568
+ > - **Fluent API** - Chain methods for readable pipeline construction
569
+ > - **Type Safety** - Full TypeScript support with IntelliSense
570
+ > - **Automatic Pagination** - Built-in support for `$facet` with counting via `Paging()` method
571
+ > - **Stage Methods** - Pre-built methods: `Match()`, `Group()`, `Lookup()`, `Sort()`, `Project()`, `AddFields()`, etc.
572
+ > - **Operators** - Import operators like `$Sum()`, `$Average()`, `$First()`, `$Max()`, `$Push()`, etc.
573
+ > - **Helpers** - Utility functions: `LookupEqualityHelper()`, `ProjectOnlyHelper()`, `Field()`, etc.
574
+ > - **No manual stage objects** - Write `builder.Match({ field: value })` instead of `{ $match: { field: value } }`
575
+ >
576
+ > **Example with operators and helpers:**
577
+ > ```typescript
578
+ > import { PipelineBuilder } from 'mongodb-pipeline-builder';
579
+ > import { $Sum, $Average, $First, $Push } from 'mongodb-pipeline-builder/operators';
580
+ > import { Field, ProjectOnlyHelper, LookupEqualityHelper } from 'mongodb-pipeline-builder/helpers';
581
+ >
582
+ > const pipeline = new PipelineBuilder('my-pipeline')
583
+ > .Match({ status: 'active' })
584
+ > .Project(ProjectOnlyHelper('name', 'email', 'orders'))
585
+ > .Lookup(LookupEqualityHelper('orders', 'userOrders', '_id', 'userId'))
586
+ > .Group({
587
+ > _id: '$department',
588
+ > totalUsers: $Sum(1),
589
+ > avgOrderValue: $Average('$orders.total'),
590
+ > firstUser: $First('$name'),
591
+ > allEmails: $Push('$email'),
592
+ > })
593
+ > .AddFields(
594
+ > Field('calculatedField', { $multiply: ['$totalUsers', 10] })
595
+ > )
596
+ > .Sort({ totalUsers: -1 })
597
+ > .Paging(10, 1) // 10 per page, page 1
598
+ > .build();
599
+ > ```
600
+ >
601
+ > This makes complex pipelines much easier to write and maintain!
602
+
603
+ **Advanced Example - Join Collections with $lookup:**
604
+
605
+ ```typescript
606
+ // src/orders/dtos/order-analytics.query.ts
607
+ import { IsOptional, IsDateString } from 'class-validator';
608
+ import { PipelineBuilder, PipelineStage } from 'mongodb-pipeline-builder';
609
+ import { $First, $Sum, $Average } from 'mongodb-pipeline-builder/operators';
610
+ import { LookupEqualityHelper } from 'mongodb-pipeline-builder/helpers';
611
+
612
+ export class OrderAnalyticsQuery {
613
+ @IsOptional()
614
+ @IsDateString()
615
+ startDate?: string;
616
+
617
+ @IsOptional()
618
+ @IsDateString()
619
+ endDate?: string;
620
+
621
+ static toPipeline(query: OrderAnalyticsQuery): PipelineStage[] {
622
+ // Using PipelineBuilder (simplified approach - recommended)
623
+ const builder = new PipelineBuilder('order-analytics');
624
+
625
+ // Filter by date range
626
+ if (query.startDate || query.endDate) {
627
+ const dateFilter: any = {};
628
+ if (query.startDate) dateFilter.$gte = new Date(query.startDate);
629
+ if (query.endDate) dateFilter.$lte = new Date(query.endDate);
630
+ builder.Match({ createdAt: dateFilter });
631
+ }
632
+
633
+ // Using method chaining with helpers for readable pipeline construction
634
+ return builder
635
+ .Lookup(LookupEqualityHelper('users', 'user', 'userId', '_id')) // Join with users
636
+ .Unwind('$user') // Unwind user array
637
+ .Lookup(LookupEqualityHelper('products', 'products', 'items.productId', '_id')) // Join with products
638
+ .Group({ // Group by user and calculate totals using operators
639
+ _id: '$userId',
640
+ userName: $First('$user.name'),
641
+ userEmail: $First('$user.email'),
642
+ orderCount: $Sum(1),
643
+ totalAmount: $Sum('$total'),
644
+ avgOrderValue: $Average('$total'),
645
+ })
646
+ .Sort({ totalAmount: -1 }) // Sort by total amount descending
647
+ .build(); // Build and return the pipeline
648
+
649
+ /* Manual approach (without PipelineBuilder and helpers):
650
+ const pipeline: PipelineStage[] = [];
651
+
652
+ if (query.startDate || query.endDate) {
653
+ const dateFilter: any = {};
654
+ if (query.startDate) dateFilter.$gte = new Date(query.startDate);
655
+ if (query.endDate) dateFilter.$lte = new Date(query.endDate);
656
+ pipeline.push({ $match: { createdAt: dateFilter } });
657
+ }
658
+
659
+ pipeline.push({
660
+ $lookup: {
661
+ from: 'users',
662
+ localField: 'userId',
663
+ foreignField: '_id',
664
+ as: 'user',
665
+ },
666
+ });
667
+
668
+ pipeline.push({ $unwind: '$user' });
669
+
670
+ pipeline.push({
671
+ $lookup: {
672
+ from: 'products',
673
+ localField: 'items.productId',
674
+ foreignField: '_id',
675
+ as: 'products',
676
+ },
677
+ });
678
+
679
+ pipeline.push({
680
+ $group: {
681
+ _id: '$userId',
682
+ userName: { $first: '$user.name' },
683
+ userEmail: { $first: '$user.email' },
684
+ orderCount: { $sum: 1 },
685
+ totalAmount: { $sum: '$total' },
686
+ avgOrderValue: { $avg: '$total' },
687
+ },
688
+ });
689
+
690
+ pipeline.push({ $sort: { totalAmount: -1 } });
691
+
692
+ return pipeline;
693
+
694
+ Compare with PipelineBuilder approach:
695
+ - LookupEqualityHelper() vs verbose $lookup object
696
+ - $First(), $Sum(), $Average() vs raw MongoDB operators
697
+ - Method chaining vs array.push()
698
+ - Much more readable and maintainable!
699
+ */
700
+ }
701
+ }
702
+
703
+ // src/orders/dtos/order-analytics.presenter.ts
704
+ export class OrderAnalyticsPresenter {
705
+ userId: string;
706
+ userName: string;
707
+ userEmail: string;
708
+ orderCount: number;
709
+ totalAmount: number;
710
+ avgOrderValue: number;
711
+
712
+ static fromAggregate(results: any[]): OrderAnalyticsPresenter[] {
713
+ return results.map(result => ({
714
+ userId: result._id,
715
+ userName: result.userName,
716
+ userEmail: result.userEmail,
717
+ orderCount: result.orderCount,
718
+ totalAmount: Math.round(result.totalAmount * 100) / 100,
719
+ avgOrderValue: Math.round(result.avgOrderValue * 100) / 100,
720
+ }));
721
+ }
722
+ }
723
+
724
+ // Usage
725
+ GET /orders/aggregate/analytics?startDate=2026-01-01&endDate=2026-12-31
726
+ ```
727
+
728
+ **Response:**
729
+ ```json
730
+ [
731
+ {
732
+ "userId": "507f1f77bcf86cd799439011",
733
+ "userName": "John Doe",
734
+ "userEmail": "john@example.com",
735
+ "orderCount": 15,
736
+ "totalAmount": 2450.50,
737
+ "avgOrderValue": 163.37
738
+ },
739
+ {
740
+ "userId": "507f1f77bcf86cd799439012",
741
+ "userName": "Jane Smith",
742
+ "userEmail": "jane@example.com",
743
+ "orderCount": 12,
744
+ "totalAmount": 1850.75,
745
+ "avgOrderValue": 154.23
746
+ }
747
+ ]
748
+ ```
749
+
750
+ ### Route Configuration
751
+
752
+ Control which routes are generated and customize individual routes:
753
+
754
+ ```typescript
755
+ DynamicApiModule.forFeature({
756
+ entity: User,
757
+ controllerOptions: {
758
+ path: 'users',
759
+ apiTag: 'Users Management',
760
+ },
761
+ routes: [
762
+ { type: 'GetMany' },
763
+ { type: 'GetOne' },
764
+ { type: 'CreateOne', validationPipeOptions: { whitelist: true } },
765
+ { type: 'UpdateOne' },
766
+ { type: 'DeleteOne' },
767
+ // Exclude specific routes
768
+ { type: 'DeleteMany', excluded: true },
769
+ ],
770
+ })
771
+ ```
772
+
773
+ ### Enable Soft Delete
774
+
775
+ Use `SoftDeletableEntity` for soft delete functionality:
776
+
777
+ ```typescript
778
+ import { SoftDeletableEntity } from 'mongodb-dynamic-api';
779
+
780
+ @Schema({ collection: 'users' })
781
+ export class User extends SoftDeletableEntity {
782
+ @Prop({ type: String, required: true })
783
+ name: string;
784
+ // ... other fields
785
+ }
786
+ ```
787
+
788
+ Deleted entities are marked with `isDeleted: true` instead of being removed.
789
+
790
+ > πŸ“š **Learn More:** See detailed guides in the [Optional Features](#optional-features-all-dependencies-included) section below.
791
+
792
+ ---
793
+
794
+ ## Best Practices
795
+
796
+ ### 1. Use Environment Variables
797
+
798
+ Always use environment variables for sensitive configuration:
799
+
800
+ ```typescript
801
+ DynamicApiModule.forRoot(process.env.MONGODB_URI)
802
+ ```
803
+
804
+ ### 2. Add Validation
805
+
806
+ Use class-validator decorators on your entities:
807
+
808
+ ```typescript
809
+ import { IsEmail, IsNotEmpty, Length } from 'class-validator';
810
+
811
+ @Schema({ collection: 'users' })
812
+ export class User extends BaseEntity {
813
+ @IsNotEmpty()
814
+ @Length(2, 100)
815
+ @Prop({ type: String, required: true })
816
+ name: string;
817
+
818
+ @IsEmail()
819
+ @Prop({ type: String, required: true, unique: true })
820
+ email: string;
821
+ }
822
+ ```
823
+
824
+ ### 3. Optimize with Indexes
825
+
826
+ Define indexes for frequently queried fields:
827
+
828
+ ```typescript
829
+ import { DynamicAPISchemaOptions } from 'mongodb-dynamic-api';
830
+
831
+ @DynamicAPISchemaOptions({
832
+ indexes: [
833
+ { fields: { email: 1 }, options: { unique: true } },
834
+ { fields: { createdAt: -1 } },
835
+ ],
836
+ })
837
+ @Schema({ collection: 'users' })
838
+ export class User extends BaseEntity {
839
+ // ...
840
+ }
841
+ ```
842
+
843
+ > πŸ“š **Learn More:** See [Schema Options](https://github.com/MikeDev75015/mongodb-dynamic-api/blob/develop/README/schema-options.md) and [Validation](https://github.com/MikeDev75015/mongodb-dynamic-api/blob/develop/README/validation.md) guides.
844
+
845
+ ---
846
+
847
+ ## Learn More
848
+
849
+ Explore advanced features and configurations:
850
+
851
+ ### Core Concepts
852
+
853
+ - **[Entities](https://github.com/MikeDev75015/mongodb-dynamic-api/blob/develop/README/entities.md)** - Learn about BaseEntity and SoftDeletableEntity (timestamps auto-enabled)
854
+ - **[Schema Options](https://github.com/MikeDev75015/mongodb-dynamic-api/blob/develop/README/schema-options.md)** - Configure indexes, hooks with options (document/query), and custom initialization
855
+
856
+ ### Optional Features (All Dependencies Included)
857
+
858
+ | Feature | Description | Documentation |
859
+ |---------|-------------|---------------|
860
+ | πŸ“š **Swagger UI** | Auto-generated OpenAPI documentation | [View Guide](https://github.com/MikeDev75015/mongodb-dynamic-api/blob/develop/README/swagger-ui.md) |
861
+ | πŸ”„ **Versioning** | URI-based API versioning | [View Guide](https://github.com/MikeDev75015/mongodb-dynamic-api/blob/develop/README/versioning.md) |
862
+ | βœ… **Validation** | Request validation with class-validator | [View Guide](https://github.com/MikeDev75015/mongodb-dynamic-api/blob/develop/README/validation.md) |
863
+ | ⚑ **Caching** | Global caching with auto-invalidation | [View Guide](https://github.com/MikeDev75015/mongodb-dynamic-api/blob/develop/README/caching.md) |
864
+ | πŸ” **Authentication** | JWT authentication (6 endpoints) | [View Guide](https://github.com/MikeDev75015/mongodb-dynamic-api/blob/develop/README/authentication.md) |
865
+ | πŸ›‘οΈ **Authorization** | Role-based access control | [View Guide](https://github.com/MikeDev75015/mongodb-dynamic-api/blob/develop/README/authorization.md) |
866
+ | πŸ“‘ **WebSockets** | Socket.IO integration for routes | [View Guide](https://github.com/MikeDev75015/mongodb-dynamic-api/blob/develop/README/websockets.md) |
867
+
868
+ ### Important Notes
869
+
870
+ - **Timestamps**: Automatically enabled when entity extends `BaseEntity` (provides `createdAt` and `updatedAt`)
871
+ - **Soft Delete**: Use `SoftDeletableEntity` to add `isDeleted` and `deletedAt` fields
872
+ - **Version Format**: Must be numeric strings (`'1'`, `'2'`), not semantic versioning
873
+ - **WebSocket Events**:
874
+ - Auth events have fixed names: `auth-login`, `auth-register`, `auth-get-account`, `auth-update-account`, `auth-reset-password`, `auth-change-password`
875
+ - CRUD events are generated from entity name or `apiTag`: `kebabCase(routeType + '/' + displayedName)`
876
+ - **Ability Predicates**: Signature varies by context:
877
+ - Auth routes: `(user, body?) => boolean`
878
+ - CRUD routes: `(entity, user) => boolean`
879
+
880
+ ---
881
+
882
+ ## License
883
+
884
+ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
885
+
886
+ ---
209
887
 
210
888
 
889
+ **Made with ❀️ by [Mickaël NODANCHE](https://cv-mikeonline.web.app)**
211
890
 
212
891
 
213
- <br>
214
- <br>
215
- <br>
216
892