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.
- package/CHANGELOG.md +4 -0
- package/README.md +745 -69
- package/package.json +15 -15
- package/src/decorators/api-endpoint-visibility.decorator.js +2 -1
- package/src/decorators/api-endpoint-visibility.decorator.js.map +1 -1
- package/src/guards/base-policies.guard.d.ts +1 -0
- package/src/helpers/repository.helper.d.ts +1 -0
- package/src/helpers/schema.helper.d.ts +1 -0
- package/src/interfaces/dynamic-api-cache-options.interface.d.ts +2 -2
- package/src/interfaces/dynamic-api-global-state.interface.d.ts +1 -0
- package/src/interfaces/dynamic-api-policy-handler.interface.d.ts +1 -0
- package/src/interfaces/dynamic-api-schema-options.interface.d.ts +1 -0
- package/src/interfaces/dynamic-api-service-callback.interface.d.ts +1 -0
- package/src/models/base-entity.model.d.ts +1 -0
- package/src/modules/auth/auth.module.d.ts +1 -1
- package/src/modules/auth/services/base-auth.service.d.ts +1 -0
- package/src/modules/auth/strategies/jwt.strategy.d.ts +3 -1
- package/src/routes/aggregate/base-aggregate.service.d.ts +1 -0
- package/src/routes/create-many/base-create-many.service.d.ts +1 -0
- package/src/routes/create-one/base-create-one.service.d.ts +1 -0
- package/src/routes/delete-many/base-delete-many.service.d.ts +1 -0
- package/src/routes/delete-one/base-delete-one.service.d.ts +1 -0
- package/src/routes/duplicate-many/base-duplicate-many.service.d.ts +1 -0
- package/src/routes/duplicate-one/base-duplicate-one.service.d.ts +1 -0
- package/src/routes/get-many/base-get-many.service.d.ts +1 -0
- package/src/routes/get-one/base-get-one.service.d.ts +1 -0
- package/src/routes/replace-one/base-replace-one.service.d.ts +1 -0
- package/src/routes/update-many/base-update-many.service.d.ts +1 -0
- package/src/routes/update-one/base-update-one.service.d.ts +1 -0
- package/src/services/base/base.service.d.ts +1 -0
- package/src/services/dynamic-api-global-state/dynamic-api-global-state.service.d.ts +2 -1
- package/src/version.json +1 -1
- package/test/e2e.setup.d.ts +1 -0
- package/test/utils.d.ts +2 -1
- 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
|
-
|
|
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
|
-
|
|
108
|
+
## Installation
|
|
65
109
|
|
|
66
|
-
|
|
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
|
-
|
|
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
|
-
|
|
118
|
+
Start a new NestJS project with TypeScript in strict mode:
|
|
82
119
|
|
|
83
|
-
|
|
84
|
-
```text
|
|
120
|
+
```bash
|
|
85
121
|
nest new --strict your-project-name
|
|
122
|
+
cd your-project-name
|
|
86
123
|
```
|
|
87
124
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
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
|
-
|
|
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-
|
|
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
|
-
|
|
118
|
-
|
|
172
|
+
> **π‘ Tip:** Use environment variables for connection strings in production:
|
|
173
|
+
> ```typescript
|
|
174
|
+
> DynamicApiModule.forRoot(process.env.MONGODB_URI)
|
|
175
|
+
> ```
|
|
119
176
|
|
|
120
|
-
|
|
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
|
-
|
|
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 {
|
|
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
|
-
|
|
141
|
-
-
|
|
142
|
-
-
|
|
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
|
-
|
|
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-
|
|
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
|
-
|
|
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
|
|
186
|
-
|
|
187
|
-
| GET
|
|
188
|
-
|
|
|
189
|
-
|
|
|
190
|
-
|
|
|
191
|
-
|
|
|
192
|
-
|
|
|
193
|
-
|
|
|
194
|
-
| DELETE
|
|
195
|
-
|
|
|
196
|
-
|
|
|
197
|
-
|
|
|
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
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
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
|
|