nest-mongoose-crud 1.0.1 → 1.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md
CHANGED
|
@@ -32,22 +32,130 @@ yarn add nest-mongoose-crud
|
|
|
32
32
|
```ts
|
|
33
33
|
// src/post/schemas/post.schema.ts
|
|
34
34
|
import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
|
|
35
|
-
import { Document } from 'mongoose';
|
|
35
|
+
import mongoose, { Document, Schema as MongooseSchema } from 'mongoose';
|
|
36
|
+
import { User } from 'src/user/schemas/user.schema';
|
|
37
|
+
|
|
38
|
+
export type PostDocument = Post & Document;
|
|
39
|
+
|
|
40
|
+
export enum PostStatus {
|
|
41
|
+
DRAFT = 'draft',
|
|
42
|
+
PUBLISHED = 'published',
|
|
43
|
+
ARCHIVED = 'archived',
|
|
44
|
+
DELETED = 'deleted',
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export enum PostVisibility {
|
|
48
|
+
PUBLIC = 'public',
|
|
49
|
+
PRIVATE = 'private',
|
|
50
|
+
UNLISTED = 'unlisted',
|
|
51
|
+
}
|
|
36
52
|
|
|
37
53
|
@Schema({ timestamps: true })
|
|
38
54
|
export class Post {
|
|
39
|
-
@Prop({ required: true })
|
|
55
|
+
@Prop({ required: true, index: true, text: true })
|
|
40
56
|
title: string;
|
|
41
57
|
|
|
42
|
-
@Prop()
|
|
58
|
+
@Prop({ required: true, index: true, text: true })
|
|
43
59
|
content: string;
|
|
44
60
|
|
|
45
|
-
@Prop({
|
|
46
|
-
|
|
61
|
+
@Prop({
|
|
62
|
+
type: String,
|
|
63
|
+
enum: PostStatus,
|
|
64
|
+
default: PostStatus.DRAFT,
|
|
65
|
+
index: true,
|
|
66
|
+
})
|
|
67
|
+
status: PostStatus;
|
|
68
|
+
|
|
69
|
+
@Prop({
|
|
70
|
+
type: String,
|
|
71
|
+
enum: PostVisibility,
|
|
72
|
+
default: PostVisibility.PUBLIC,
|
|
73
|
+
index: true,
|
|
74
|
+
})
|
|
75
|
+
visibility: PostVisibility;
|
|
76
|
+
|
|
77
|
+
// --- MANY-TO-ONE RELATIONSHIP WITH USER ---
|
|
78
|
+
@Prop({
|
|
79
|
+
type: mongoose.Schema.Types.ObjectId,
|
|
80
|
+
ref: 'User',
|
|
81
|
+
required: true,
|
|
82
|
+
index: true,
|
|
83
|
+
})
|
|
84
|
+
author: User;
|
|
85
|
+
|
|
86
|
+
// --- FIELDS FOR SEARCHING ---
|
|
87
|
+
@Prop([String])
|
|
88
|
+
keywords: string[];
|
|
89
|
+
|
|
90
|
+
@Prop([String])
|
|
91
|
+
categories: string[];
|
|
92
|
+
|
|
93
|
+
// --- FIELDS FOR RANGE QUERIES ---
|
|
94
|
+
@Prop({ default: 0 })
|
|
95
|
+
viewCount: number;
|
|
96
|
+
|
|
97
|
+
@Prop({ default: 0 })
|
|
98
|
+
likeCount: number;
|
|
99
|
+
|
|
100
|
+
@Prop({ default: 0 })
|
|
101
|
+
commentCount: number;
|
|
102
|
+
|
|
103
|
+
@Prop({ type: Date })
|
|
104
|
+
publishedAt: Date;
|
|
105
|
+
|
|
106
|
+
@Prop()
|
|
107
|
+
readingTime: number; // in minutes
|
|
108
|
+
|
|
109
|
+
// --- FIELDS FOR FILTERING ---
|
|
110
|
+
@Prop({ default: false, index: true })
|
|
111
|
+
isFeatured: boolean;
|
|
112
|
+
|
|
113
|
+
@Prop({ default: false, index: true })
|
|
114
|
+
isPinned: boolean;
|
|
115
|
+
|
|
116
|
+
@Prop({ default: false, index: true })
|
|
117
|
+
allowComments: boolean;
|
|
118
|
+
|
|
119
|
+
@Prop({ default: true })
|
|
120
|
+
allowSharing: boolean;
|
|
121
|
+
|
|
122
|
+
@Prop([String])
|
|
123
|
+
tags: string[];
|
|
124
|
+
|
|
125
|
+
// --- METADATA & SEO ---
|
|
126
|
+
@Prop()
|
|
127
|
+
excerpt: string;
|
|
128
|
+
|
|
129
|
+
@Prop()
|
|
130
|
+
coverImage: string;
|
|
131
|
+
|
|
132
|
+
@Prop()
|
|
133
|
+
slug: string;
|
|
134
|
+
|
|
135
|
+
@Prop()
|
|
136
|
+
metaTitle: string;
|
|
137
|
+
|
|
138
|
+
@Prop()
|
|
139
|
+
metaDescription: string;
|
|
140
|
+
|
|
141
|
+
@Prop({ type: MongooseSchema.Types.Mixed })
|
|
142
|
+
metadata: Record<string, any>;
|
|
47
143
|
}
|
|
48
144
|
|
|
49
|
-
export type PostDocument = Post & Document;
|
|
50
145
|
export const PostSchema = SchemaFactory.createForClass(Post);
|
|
146
|
+
|
|
147
|
+
// --- COMPOUND INDEXES FOR OPTIMIZED QUERIES ---
|
|
148
|
+
|
|
149
|
+
// Text search index on title and content
|
|
150
|
+
PostSchema.index({ title: 'text', content: 'text' });
|
|
151
|
+
|
|
152
|
+
// For user's post feed
|
|
153
|
+
PostSchema.index({
|
|
154
|
+
status: 1,
|
|
155
|
+
visibility: 1,
|
|
156
|
+
publishedAt: -1,
|
|
157
|
+
isFeatured: 1,
|
|
158
|
+
});
|
|
51
159
|
```
|
|
52
160
|
|
|
53
161
|
### 2. Create a service by extending `BaseCrudService`
|
|
@@ -65,11 +173,6 @@ export class PostService extends BaseCrudService<PostDocument> {
|
|
|
65
173
|
constructor(@InjectModel(Post.name) model: Model<PostDocument>) {
|
|
66
174
|
super(model);
|
|
67
175
|
}
|
|
68
|
-
|
|
69
|
-
// optional: add custom methods or override existing ones
|
|
70
|
-
findAllUpdate() {
|
|
71
|
-
return this.model.find({});
|
|
72
|
-
}
|
|
73
176
|
}
|
|
74
177
|
```
|
|
75
178
|
|
|
@@ -97,28 +200,6 @@ export class PostController extends BaseController {
|
|
|
97
200
|
constructor(protected readonly service: PostService) {
|
|
98
201
|
super(service);
|
|
99
202
|
}
|
|
100
|
-
|
|
101
|
-
// you may override any endpoint, add new ones, etc.
|
|
102
|
-
@Get()
|
|
103
|
-
findAll() {
|
|
104
|
-
return this.service.findAllUpdate();
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
```
|
|
108
|
-
|
|
109
|
-
> 👇 Example user controller from the test project:
|
|
110
|
-
|
|
111
|
-
```ts
|
|
112
|
-
const BaseCrudController = createCrudController({
|
|
113
|
-
create: { dto: CreateUserDto },
|
|
114
|
-
update: { dto: UpdateUserDto },
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
@Controller('users')
|
|
118
|
-
export class UserController extends BaseCrudController {
|
|
119
|
-
constructor(protected readonly service: UserService) {
|
|
120
|
-
super(service);
|
|
121
|
-
}
|
|
122
203
|
}
|
|
123
204
|
```
|
|
124
205
|
|
|
@@ -144,39 +225,202 @@ export class PostModule {}
|
|
|
144
225
|
|
|
145
226
|
---
|
|
146
227
|
|
|
147
|
-
## 🔍
|
|
228
|
+
## 🔍 Querying
|
|
229
|
+
|
|
230
|
+
The `APIFeatures` class provides powerful querying capabilities for your CRUD APIs, allowing you to filter, sort, search, paginate, and populate your data efficiently. Below, each feature is explained in detail with practical examples using our Post model.
|
|
231
|
+
|
|
232
|
+
### Filtering
|
|
233
|
+
|
|
234
|
+
Filtering allows you to retrieve documents that match specific criteria. There are two types: basic filtering for exact matches and advanced filtering for range queries.
|
|
235
|
+
|
|
236
|
+
#### Basic Filtering
|
|
237
|
+
|
|
238
|
+
For fields that accept multiple values (like enums, arrays, or booleans), use comma-separated values. This uses MongoDB's `$in` operator to match any of the provided values.
|
|
148
239
|
|
|
149
|
-
|
|
150
|
-
standard REST query parameters. All of the following work out of the
|
|
151
|
-
box when you call `service.findAll(query)` or hit a controller
|
|
152
|
-
event:
|
|
240
|
+
**Explanation:** This is useful for filtering by categories, statuses, tags, or any field where you want to include multiple options. The query parameter value is split by commas and converted to an `$in` array.
|
|
153
241
|
|
|
154
|
-
|
|
155
|
-
| ------------------------------ | ----------------------------------------------- | ----------------------- | --- |
|
|
156
|
-
| `?field=value` | Exact match (supports comma‑separated values) | `?status=active,paused` |
|
|
157
|
-
| `?price[gte]=10&price[lte]=50` | Comparison operators (`gte`, `gt`, `lte`, `lt`) | |
|
|
158
|
-
| `?sort=createdAt,-title` | Sort by one or more fields | |
|
|
159
|
-
| `?fields=name,email` | Project specific fields | |
|
|
160
|
-
| `?page=2&limit=20` | Pagination | |
|
|
161
|
-
| `?search=term:field1,field2` | Case‑insensitive regex search | |
|
|
162
|
-
| `?populate=author:email | comments:body` | Populate mongoose refs | |
|
|
242
|
+
**Example:** Get posts that are either published or archived.
|
|
163
243
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
244
|
+
```http
|
|
245
|
+
GET /posts?status=published,archived
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
This translates to MongoDB query: `{ status: { $in: ['published', 'archived'] } }`
|
|
249
|
+
|
|
250
|
+
**Example:** Get posts with specific tags.
|
|
251
|
+
|
|
252
|
+
```http
|
|
253
|
+
GET /posts?tags=javascript,react,nestjs
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
**Example:** Get featured and pinned posts.
|
|
257
|
+
|
|
258
|
+
```http
|
|
259
|
+
GET /posts?isFeatured=true&isPinned=true
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
In our Post model, you can filter by:
|
|
263
|
+
|
|
264
|
+
- **Enums:** `status` (draft, published, archived, deleted), `visibility` (public, private, unlisted)
|
|
265
|
+
- **Arrays:** `categories`, `tags`, `keywords`
|
|
266
|
+
- **Booleans:** `isFeatured`, `isPinned`, `allowComments`, `allowSharing`
|
|
267
|
+
- **Relationships:** `author` (user ID)
|
|
268
|
+
|
|
269
|
+
#### Advanced Filtering
|
|
270
|
+
|
|
271
|
+
For range queries and comparisons, use bracket notation with MongoDB comparison operators:
|
|
272
|
+
|
|
273
|
+
- `[gte]` - greater than or equal (`$gte`)
|
|
274
|
+
- `[gt]` - greater than (`$gt`)
|
|
275
|
+
- `[lte]` - less than or equal (`$lte`)
|
|
276
|
+
- `[lt]` - less than (`$lt`)
|
|
277
|
+
|
|
278
|
+
**Explanation:** These operators allow precise range filtering on numeric or date fields. The bracket notation `[operator]` is parsed to extract the field name and apply the corresponding MongoDB operator.
|
|
279
|
+
|
|
280
|
+
**Example:** Get posts with high engagement (more than 100 views and 10 likes).
|
|
281
|
+
|
|
282
|
+
```http
|
|
283
|
+
GET /posts?viewCount[gte]=100&likeCount[gte]=10
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
**Example:** Get posts published between January 1, 2024 and December 31, 2024.
|
|
287
|
+
|
|
288
|
+
```http
|
|
289
|
+
GET /posts?publishedAt[gte]=2024-01-01&publishedAt[lte]=2024-12-31
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
**Example:** Get posts with reading time between 5-15 minutes.
|
|
293
|
+
|
|
294
|
+
```http
|
|
295
|
+
GET /posts?readingTime[gte]=5&readingTime[lte]=15
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
Note: Date fields should be in ISO format (YYYY-MM-DDTHH:mm:ss.sssZ). Our Post model has `publishedAt` for publication dates and automatic `createdAt`/`updatedAt` timestamps.
|
|
299
|
+
|
|
300
|
+
You can combine multiple operators on the same field or different fields:
|
|
301
|
+
|
|
302
|
+
```http
|
|
303
|
+
GET /posts?viewCount[gte]=50&commentCount[lte]=5&readingTime[gte]=3&readingTime[lte]=10
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
### Sorting
|
|
307
|
+
|
|
308
|
+
Sort your results by one or more fields to control the order of returned documents.
|
|
167
309
|
|
|
168
|
-
|
|
310
|
+
**Explanation:** Separate multiple fields with commas. Prefix a field with `-` for descending order. If no sort is specified, results default to `-createdAt` (newest first). This uses MongoDB's `sort()` method.
|
|
311
|
+
|
|
312
|
+
**Example:** Sort posts by view count descending.
|
|
313
|
+
|
|
314
|
+
```http
|
|
315
|
+
GET /posts?sort=-viewCount
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
**Example:** Sort by engagement metrics in ascending order.
|
|
169
319
|
|
|
170
320
|
```http
|
|
171
|
-
GET /posts?
|
|
321
|
+
GET /posts?sort=commentCount
|
|
172
322
|
```
|
|
173
323
|
|
|
324
|
+
For our Post model, you can sort by any field including: `title`, `status`, `visibility`, `publishedAt`, `viewCount`, `likeCount`, `commentCount`, `readingTime`, `isFeatured`, `isPinned`, `createdAt`, `updatedAt`, and more.
|
|
325
|
+
|
|
326
|
+
### Limiting Fields
|
|
327
|
+
|
|
328
|
+
Control which fields are included or excluded in the response to optimize payload size and hide sensitive data.
|
|
329
|
+
|
|
330
|
+
**Explanation:** Use a comma-separated list of field names. By default, the `__v` field (Mongoose version key) is excluded. Prefix fields with `-` to exclude them instead of including only them. This uses MongoDB's `select()` method.
|
|
331
|
+
|
|
332
|
+
**Example:** Only return essential fields for a list view.
|
|
333
|
+
|
|
174
334
|
```http
|
|
175
|
-
GET /posts?
|
|
335
|
+
GET /posts?fields=title,excerpt,viewCount,likeCount,publishedAt,author
|
|
176
336
|
```
|
|
177
337
|
|
|
338
|
+
**Example:** Return all fields except content and metadata for performance.
|
|
339
|
+
|
|
178
340
|
```http
|
|
179
|
-
GET /posts?
|
|
341
|
+
GET /posts?fields=-content,-metadata,-__v
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
**Example:** Get only SEO-related fields.
|
|
345
|
+
|
|
346
|
+
```http
|
|
347
|
+
GET /posts?fields=slug,metaTitle,metaDescription,coverImage
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
### Pagination
|
|
351
|
+
|
|
352
|
+
Split large result sets into manageable pages for better performance and user experience.
|
|
353
|
+
|
|
354
|
+
**Explanation:** Use `page` (default: 1) and `limit` (default: 10) parameters. The API calculates the skip value as `(page - 1) * limit`. The response includes pagination metadata like total count, current page, total pages, and next/previous page numbers.
|
|
355
|
+
|
|
356
|
+
**Example:** Get the second page with 20 posts per page.
|
|
357
|
+
|
|
358
|
+
```http
|
|
359
|
+
GET /posts?page=2&limit=20
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
This skips the first 20 documents and returns documents 21-40.
|
|
363
|
+
|
|
364
|
+
**Example:** Get featured posts with pagination.
|
|
365
|
+
|
|
366
|
+
```http
|
|
367
|
+
GET /posts?isFeatured=true&page=1&limit=5
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
### Searching
|
|
371
|
+
|
|
372
|
+
Perform case-insensitive text searches across multiple fields using regex.
|
|
373
|
+
|
|
374
|
+
**Explanation:** The format is `?search=searchTerm:field1,field2,field3`. The search term is applied as a regex pattern to each specified field, combined with MongoDB's `$or` operator. This is efficient when fields have text indexes.
|
|
375
|
+
|
|
376
|
+
**Example:** Search for "javascript" in the title or content fields.
|
|
377
|
+
|
|
378
|
+
```http
|
|
379
|
+
GET /posts?search=javascript:title,content
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
**Example:** Search across title, content, and keywords.
|
|
383
|
+
|
|
384
|
+
```http
|
|
385
|
+
GET /posts?search=react:title,content,keywords
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
**Example:** Search in excerpt and meta description for SEO content.
|
|
389
|
+
|
|
390
|
+
```http
|
|
391
|
+
GET /posts?search=seo:excerpt,metaDescription
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
This creates: `{ $or: [{ title: { $regex: 'javascript', $options: 'i' } }, { content: { $regex: 'javascript', $options: 'i' } }] }`
|
|
395
|
+
|
|
396
|
+
In our Post model, `title` and `content` have text indexes, making searches fast. You can also search in `excerpt`, `metaTitle`, `metaDescription`, or array fields like `keywords`, `categories`, `tags`.
|
|
397
|
+
|
|
398
|
+
### Population
|
|
399
|
+
|
|
400
|
+
Populate referenced documents to include related data in your response, avoiding multiple queries.
|
|
401
|
+
|
|
402
|
+
**Explanation:** Specify the reference path and optionally which fields to select from the populated document. This reduces payload size by only including necessary fields. Use `|` to separate multiple population paths. Population occurs after filtering, sorting, etc., for optimal performance.
|
|
403
|
+
|
|
404
|
+
**Format:** `?populate=path:field1,field2,field3`
|
|
405
|
+
|
|
406
|
+
**Example:** Populate the author field, but only include firstName, lastName, and email from the User model.
|
|
407
|
+
|
|
408
|
+
```http
|
|
409
|
+
GET /posts?populate=author:firstName,lastName,email
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
**Example:** Get author details and role for permission checks.
|
|
413
|
+
|
|
414
|
+
```http
|
|
415
|
+
GET /posts?populate=author:firstName,lastName,email,role,status
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
In our Post model, `author` references the User model. Without field selection, all User fields would be included; with selection, only the specified fields are returned, improving performance and security. You can also populate deeply to include nested relationships.
|
|
419
|
+
|
|
420
|
+
**Example:** Combine with other queries - get published posts by active users.
|
|
421
|
+
|
|
422
|
+
```http
|
|
423
|
+
GET /posts?status=published&populate=author:status&author.status=active
|
|
180
424
|
```
|
|
181
425
|
|
|
182
426
|
---
|
|
@@ -192,10 +436,6 @@ interface EndpointConfig {
|
|
|
192
436
|
dto?: DtoClass; // class used by ValidationPipe
|
|
193
437
|
guards?: any[];
|
|
194
438
|
interceptors?: any[];
|
|
195
|
-
pipes?: any[];
|
|
196
|
-
status?: number; // HTTP status code
|
|
197
|
-
enabled?: boolean; // turn off an endpoint
|
|
198
|
-
validationOptions?: ValidationPipeOptions;
|
|
199
439
|
}
|
|
200
440
|
|
|
201
441
|
interface CrudControllerConfig {
|
|
@@ -204,11 +444,6 @@ interface CrudControllerConfig {
|
|
|
204
444
|
delete?: EndpointConfig;
|
|
205
445
|
getAll?: EndpointConfig;
|
|
206
446
|
getOne?: EndpointConfig;
|
|
207
|
-
global?: {
|
|
208
|
-
guards?: any[];
|
|
209
|
-
interceptors?: any[];
|
|
210
|
-
pipes?: any[];
|
|
211
|
-
};
|
|
212
447
|
}
|
|
213
448
|
```
|
|
214
449
|
|
|
@@ -216,38 +451,120 @@ interface CrudControllerConfig {
|
|
|
216
451
|
|
|
217
452
|
```ts
|
|
218
453
|
const BaseController = createCrudController({
|
|
219
|
-
global: {
|
|
220
|
-
guards: [AuthGuard],
|
|
221
|
-
interceptors: [LoggingInterceptor],
|
|
222
|
-
},
|
|
223
454
|
getAll: {
|
|
224
|
-
guards: [AdminGuard],
|
|
225
|
-
interceptors: [CacheInterceptor],
|
|
455
|
+
guards: [AdminGuard, SomeOtherGuard, ...],
|
|
456
|
+
interceptors: [CacheInterceptor, SomeOtherInterceptor, ...],
|
|
226
457
|
},
|
|
227
|
-
create: { dto: CreatePostDto
|
|
458
|
+
create: { dto: CreatePostDto },
|
|
228
459
|
});
|
|
229
460
|
```
|
|
230
461
|
|
|
231
|
-
####
|
|
462
|
+
#### Override validation options
|
|
232
463
|
|
|
233
464
|
```ts
|
|
234
465
|
const BaseController = createCrudController({
|
|
235
|
-
|
|
466
|
+
create: {
|
|
467
|
+
dto: CreateDto,
|
|
468
|
+
},
|
|
236
469
|
});
|
|
237
470
|
```
|
|
238
471
|
|
|
239
|
-
|
|
472
|
+
---
|
|
473
|
+
|
|
474
|
+
## Override example
|
|
475
|
+
|
|
476
|
+
The following shows a simple way to override a generated endpoint by
|
|
477
|
+
providing a custom service method and a controller handler. You can
|
|
478
|
+
either override a handler via the `createCrudController` options
|
|
479
|
+
(`handler`), or replace the route entirely by extending the base
|
|
480
|
+
controller — this example demonstrates a common pattern: custom service
|
|
481
|
+
logic plus an explicit controller method that calls it.
|
|
240
482
|
|
|
241
483
|
```ts
|
|
242
|
-
|
|
484
|
+
// src/post/post.service.ts
|
|
485
|
+
import { Injectable } from '@nestjs/common';
|
|
486
|
+
import { InjectModel } from '@nestjs/mongoose';
|
|
487
|
+
import { Model } from 'mongoose';
|
|
488
|
+
import { Post, PostDocument } from './schemas/post.schema';
|
|
489
|
+
import { CreatePostUpdated } from './dto/create-post-update.dto';
|
|
490
|
+
|
|
491
|
+
import { BaseCrudService } from 'nest-mongoose-crud'; // Adjust path as needed
|
|
492
|
+
|
|
493
|
+
@Injectable()
|
|
494
|
+
export class PostService extends BaseCrudService<PostDocument> {
|
|
495
|
+
constructor(@InjectModel(Post.name) postModel: Model<PostDocument>) {
|
|
496
|
+
super(postModel);
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
create(updatePostDto: CreatePostUpdated) {
|
|
500
|
+
return 'create one updated!!';
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
```
|
|
504
|
+
|
|
505
|
+
```ts
|
|
506
|
+
// src/post/post.controller.ts
|
|
507
|
+
import {
|
|
508
|
+
Body,
|
|
509
|
+
Controller,
|
|
510
|
+
Post,
|
|
511
|
+
Type,
|
|
512
|
+
UsePipes,
|
|
513
|
+
ValidationPipe,
|
|
514
|
+
} from '@nestjs/common';
|
|
515
|
+
|
|
516
|
+
import { createCrudController } from 'nest-mongoose-crud'; // Adjust path as needed
|
|
517
|
+
import { PostService } from './post.service';
|
|
518
|
+
import { CreatePostDto } from './dto/create-post.dto';
|
|
519
|
+
import { UpdatePostDto } from './dto/update-post.dto';
|
|
520
|
+
import { LogRequest1Interceptor } from './interceptors/log-request-1/log-request-1.interceptor';
|
|
521
|
+
import { LogRequest2Interceptor } from './interceptors/log-request-2/log-request-2.interceptor';
|
|
522
|
+
import { LogRequest1Guard } from './guards/log-request-1/log-request-1.guard';
|
|
523
|
+
import { CreatePostUpdated } from './dto/create-post-update.dto';
|
|
524
|
+
import { LogRequest2Guard } from './guards/log-request-1/log-request-2.guard';
|
|
525
|
+
|
|
526
|
+
const BaseController: Type<any> = createCrudController({
|
|
243
527
|
create: {
|
|
244
|
-
dto:
|
|
245
|
-
|
|
528
|
+
dto: CreatePostDto,
|
|
529
|
+
interceptors: [LogRequest1Interceptor, LogRequest2Interceptor],
|
|
530
|
+
},
|
|
531
|
+
|
|
532
|
+
update: {
|
|
533
|
+
dto: UpdatePostDto,
|
|
534
|
+
},
|
|
535
|
+
|
|
536
|
+
getAll: {
|
|
537
|
+
guards: [LogRequest1Guard, LogRequest2Guard],
|
|
538
|
+
interceptors: [LogRequest1Interceptor, LogRequest2Interceptor],
|
|
246
539
|
},
|
|
247
540
|
});
|
|
541
|
+
|
|
542
|
+
@Controller('posts')
|
|
543
|
+
export class PostController extends BaseController {
|
|
544
|
+
constructor(private service: PostService) {
|
|
545
|
+
super(service);
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
@Post()
|
|
549
|
+
@UsePipes(
|
|
550
|
+
new ValidationPipe({
|
|
551
|
+
transform: true,
|
|
552
|
+
whitelist: true,
|
|
553
|
+
forbidNonWhitelisted: true,
|
|
554
|
+
}),
|
|
555
|
+
)
|
|
556
|
+
create(@Body() dto: CreatePostUpdated) {
|
|
557
|
+
return this.service.create(dto);
|
|
558
|
+
}
|
|
559
|
+
}
|
|
248
560
|
```
|
|
249
561
|
|
|
250
|
-
|
|
562
|
+
This snippet demonstrates:
|
|
563
|
+
|
|
564
|
+
- overriding service behaviour by extending `BaseCrudService`;
|
|
565
|
+
- exposing a custom controller route that delegates to the service;
|
|
566
|
+
- mixing generated options (via `createCrudController`) with explicit
|
|
567
|
+
controller methods when you need full control.
|
|
251
568
|
|
|
252
569
|
## 📦 Additional Utilities
|
|
253
570
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"create-crud.controller.utils.d.ts","sourceRoot":"","sources":["../../src/utils/create-crud.controller.utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAKL,IAAI,EAQJ,UAAU,EAIX,MAAM,gBAAgB,CAAC;AAExB,OAAO,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AAIpC,MAAM,WAAW,cAAc;IAC7B,GAAG,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC;IAChB,MAAM,CAAC,EAAE,GAAG,EAAE,CAAC;IACf,YAAY,CAAC,EAAE,GAAG,EAAE,CAAC;IACrB,KAAK,CAAC,EAAE,GAAG,EAAE,CAAC;IACd,MAAM,CAAC,EAAE,UAAU,CAAC;IACpB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,iBAAiB,CAAC,EAAE;QAClB,SAAS,CAAC,EAAE,OAAO,CAAC;QACpB,SAAS,CAAC,EAAE,OAAO,CAAC;QACpB,oBAAoB,CAAC,EAAE,OAAO,CAAC;KAChC,CAAC;CACH;AAED,MAAM,WAAW,oBAAoB;IACnC,MAAM,CAAC,EAAE,cAAc,CAAC;IACxB,MAAM,CAAC,EAAE,cAAc,CAAC;IACxB,MAAM,CAAC,EAAE,cAAc,CAAC;IACxB,MAAM,CAAC,EAAE,cAAc,CAAC;IACxB,MAAM,CAAC,EAAE,cAAc,CAAC;IACxB,MAAM,CAAC,EAAE;QACP,MAAM,CAAC,EAAE,GAAG,EAAE,CAAC;QACf,YAAY,CAAC,EAAE,GAAG,EAAE,CAAC;QACrB,KAAK,CAAC,EAAE,GAAG,EAAE,CAAC;KACf,CAAC;CACH;AA2BD,wBAAgB,oBAAoB,CAAC,CAAC,SAAS,QAAQ,EACrD,MAAM,GAAE,oBAAyB,GAChC,IAAI,CAAC,GAAG,CAAC,
|
|
1
|
+
{"version":3,"file":"create-crud.controller.utils.d.ts","sourceRoot":"","sources":["../../src/utils/create-crud.controller.utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAKL,IAAI,EAQJ,UAAU,EAIX,MAAM,gBAAgB,CAAC;AAExB,OAAO,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AAIpC,MAAM,WAAW,cAAc;IAC7B,GAAG,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC;IAChB,MAAM,CAAC,EAAE,GAAG,EAAE,CAAC;IACf,YAAY,CAAC,EAAE,GAAG,EAAE,CAAC;IACrB,KAAK,CAAC,EAAE,GAAG,EAAE,CAAC;IACd,MAAM,CAAC,EAAE,UAAU,CAAC;IACpB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,iBAAiB,CAAC,EAAE;QAClB,SAAS,CAAC,EAAE,OAAO,CAAC;QACpB,SAAS,CAAC,EAAE,OAAO,CAAC;QACpB,oBAAoB,CAAC,EAAE,OAAO,CAAC;KAChC,CAAC;CACH;AAED,MAAM,WAAW,oBAAoB;IACnC,MAAM,CAAC,EAAE,cAAc,CAAC;IACxB,MAAM,CAAC,EAAE,cAAc,CAAC;IACxB,MAAM,CAAC,EAAE,cAAc,CAAC;IACxB,MAAM,CAAC,EAAE,cAAc,CAAC;IACxB,MAAM,CAAC,EAAE,cAAc,CAAC;IACxB,MAAM,CAAC,EAAE;QACP,MAAM,CAAC,EAAE,GAAG,EAAE,CAAC;QACf,YAAY,CAAC,EAAE,GAAG,EAAE,CAAC;QACrB,KAAK,CAAC,EAAE,GAAG,EAAE,CAAC;KACf,CAAC;CACH;AA2BD,wBAAgB,oBAAoB,CAAC,CAAC,SAAS,QAAQ,EACrD,MAAM,GAAE,oBAAyB,GAChC,IAAI,CAAC,GAAG,CAAC,CAgFX"}
|
|
@@ -73,6 +73,7 @@ function createCrudController(config = {}) {
|
|
|
73
73
|
], BaseController.prototype, "findAll", null);
|
|
74
74
|
__decorate([
|
|
75
75
|
(0, common_1.Get)(':id'),
|
|
76
|
+
applyEndpointDecorators(config.getOne),
|
|
76
77
|
__param(0, (0, common_1.Param)('id')),
|
|
77
78
|
__param(1, (0, common_1.Query)()),
|
|
78
79
|
__metadata("design:type", Function),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"create-crud.controller.utils.js","sourceRoot":"","sources":["../../src/utils/create-crud.controller.utils.ts"],"names":[],"mappings":";;;;;;;;;;;;;;AA2EA,
|
|
1
|
+
{"version":3,"file":"create-crud.controller.utils.js","sourceRoot":"","sources":["../../src/utils/create-crud.controller.utils.ts"],"names":[],"mappings":"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"}
|