0
# Validation Pipes
1
2
Pre-built pipes for validating and parsing MongoDB ObjectIds in request parameters and body data. These pipes integrate with NestJS's validation system to ensure data integrity when working with MongoDB documents.
3
4
## Capabilities
5
6
### IsObjectIdPipe
7
8
Validates if a string is a valid MongoDB ObjectId format without converting it to an ObjectId instance.
9
10
```typescript { .api }
11
class IsObjectIdPipe implements PipeTransform {
12
/**
13
* Validates that a string is a valid MongoDB ObjectId format
14
* @param value - String value to validate
15
* @returns The original string if valid
16
* @throws BadRequestException if invalid ObjectId format
17
*/
18
transform(value: string): string;
19
}
20
```
21
22
**Usage Examples:**
23
24
```typescript
25
import { Controller, Get, Param } from '@nestjs/common';
26
import { IsObjectIdPipe } from '@nestjs/mongoose';
27
28
@Controller('users')
29
export class UsersController {
30
// Validate ObjectId in route parameter
31
@Get(':id')
32
async findOne(@Param('id', IsObjectIdPipe) id: string) {
33
// id is guaranteed to be a valid ObjectId string
34
return this.usersService.findById(id);
35
}
36
37
// Multiple ObjectId parameters
38
@Get(':userId/posts/:postId')
39
async findUserPost(
40
@Param('userId', IsObjectIdPipe) userId: string,
41
@Param('postId', IsObjectIdPipe) postId: string,
42
) {
43
return this.postsService.findByUserAndId(userId, postId);
44
}
45
46
// Optional ObjectId validation
47
@Get()
48
async findAll(@Query('authorId', new IsObjectIdPipe({ optional: true })) authorId?: string) {
49
if (authorId) {
50
return this.usersService.findByAuthor(authorId);
51
}
52
return this.usersService.findAll();
53
}
54
}
55
56
// In request body validation
57
import { Body, Post } from '@nestjs/common';
58
59
@Controller('comments')
60
export class CommentsController {
61
@Post()
62
async create(@Body() createCommentDto: CreateCommentDto) {
63
return this.commentsService.create(createCommentDto);
64
}
65
}
66
67
// DTO with ObjectId validation
68
export class CreateCommentDto {
69
@IsString()
70
content: string;
71
72
@Transform(({ value }) => value, { toClassOnly: true })
73
@Validate(IsObjectIdPipe)
74
postId: string;
75
76
@Transform(({ value }) => value, { toClassOnly: true })
77
@Validate(IsObjectIdPipe)
78
authorId: string;
79
}
80
```
81
82
### ParseObjectIdPipe
83
84
Validates and converts a string to a MongoDB ObjectId instance for direct use with Mongoose operations.
85
86
```typescript { .api }
87
class ParseObjectIdPipe implements PipeTransform {
88
/**
89
* Validates and converts a string to MongoDB ObjectId
90
* @param value - String value to parse
91
* @returns ObjectId instance
92
* @throws BadRequestException if invalid ObjectId format
93
*/
94
transform(value: string): Types.ObjectId;
95
}
96
```
97
98
**Usage Examples:**
99
100
```typescript
101
import { Controller, Get, Param, Delete } from '@nestjs/common';
102
import { ParseObjectIdPipe } from '@nestjs/mongoose';
103
import { Types } from 'mongoose';
104
105
@Controller('users')
106
export class UsersController {
107
// Parse ObjectId in route parameter
108
@Get(':id')
109
async findOne(@Param('id', ParseObjectIdPipe) id: Types.ObjectId) {
110
// id is now a proper ObjectId instance
111
return this.usersService.findById(id);
112
}
113
114
@Delete(':id')
115
async remove(@Param('id', ParseObjectIdPipe) id: Types.ObjectId) {
116
return this.usersService.remove(id);
117
}
118
119
// Complex route with multiple ObjectIds
120
@Get(':userId/orders/:orderId/items/:itemId')
121
async findOrderItem(
122
@Param('userId', ParseObjectIdPipe) userId: Types.ObjectId,
123
@Param('orderId', ParseObjectIdPipe) orderId: Types.ObjectId,
124
@Param('itemId', ParseObjectIdPipe) itemId: Types.ObjectId,
125
) {
126
return this.ordersService.findItemByIds(userId, orderId, itemId);
127
}
128
}
129
130
// Service usage with parsed ObjectIds
131
@Injectable()
132
export class UsersService {
133
constructor(@InjectModel(User.name) private userModel: Model<User>) {}
134
135
async findById(id: Types.ObjectId): Promise<User> {
136
// Can use ObjectId directly in Mongoose operations
137
return this.userModel.findById(id).exec();
138
}
139
140
async findByIds(ids: Types.ObjectId[]): Promise<User[]> {
141
return this.userModel.find({ _id: { $in: ids } }).exec();
142
}
143
144
async updateRelationship(userId: Types.ObjectId, friendId: Types.ObjectId): Promise<User> {
145
return this.userModel.findByIdAndUpdate(
146
userId,
147
{ $addToSet: { friends: friendId } },
148
{ new: true }
149
).exec();
150
}
151
}
152
```
153
154
## Advanced Pipe Usage
155
156
### Custom Validation Options
157
158
```typescript { .api }
159
import { ArgumentMetadata, BadRequestException, Injectable, PipeTransform } from '@nestjs/common';
160
import { isValidObjectId, Types } from 'mongoose';
161
162
@Injectable()
163
export class CustomObjectIdPipe implements PipeTransform {
164
constructor(private readonly options: { optional?: boolean; allowNull?: boolean } = {}) {}
165
166
transform(value: any, metadata: ArgumentMetadata): string | Types.ObjectId | null {
167
// Handle optional values
168
if (this.options.optional && (value === undefined || value === '')) {
169
return undefined;
170
}
171
172
// Handle null values
173
if (this.options.allowNull && value === null) {
174
return null;
175
}
176
177
// Validate ObjectId format
178
if (!isValidObjectId(value)) {
179
throw new BadRequestException(`Invalid ObjectId format: ${value}`);
180
}
181
182
// Return as string or ObjectId based on needs
183
return metadata.metatype === Types.ObjectId ? new Types.ObjectId(value) : value;
184
}
185
}
186
187
// Usage with custom options
188
@Controller('posts')
189
export class PostsController {
190
@Get()
191
async findAll(
192
@Query('categoryId', new CustomObjectIdPipe({ optional: true })) categoryId?: string,
193
@Query('authorId', new CustomObjectIdPipe({ optional: true })) authorId?: string,
194
) {
195
const filters: any = {};
196
if (categoryId) filters.category = categoryId;
197
if (authorId) filters.author = authorId;
198
199
return this.postsService.findWithFilters(filters);
200
}
201
}
202
```
203
204
### Array ObjectId Validation
205
206
```typescript { .api }
207
@Injectable()
208
export class ParseObjectIdArrayPipe implements PipeTransform {
209
transform(value: string | string[]): Types.ObjectId[] {
210
const ids = Array.isArray(value) ? value : [value];
211
212
return ids.map(id => {
213
if (!isValidObjectId(id)) {
214
throw new BadRequestException(`Invalid ObjectId format: ${id}`);
215
}
216
return new Types.ObjectId(id);
217
});
218
}
219
}
220
221
@Controller('users')
222
export class UsersController {
223
@Post('bulk-update')
224
async bulkUpdate(
225
@Body('userIds', ParseObjectIdArrayPipe) userIds: Types.ObjectId[],
226
@Body() updateData: any,
227
) {
228
return this.usersService.bulkUpdate(userIds, updateData);
229
}
230
231
@Delete('bulk-delete')
232
async bulkDelete(@Body('ids', ParseObjectIdArrayPipe) ids: Types.ObjectId[]) {
233
return this.usersService.bulkDelete(ids);
234
}
235
}
236
```
237
238
### Global Pipe Configuration
239
240
```typescript
241
import { ValidationPipe } from '@nestjs/common';
242
import { NestFactory } from '@nestjs/core';
243
import { AppModule } from './app.module';
244
245
async function bootstrap() {
246
const app = await NestFactory.create(AppModule);
247
248
// Global validation pipe with ObjectId support
249
app.useGlobalPipes(
250
new ValidationPipe({
251
transform: true,
252
whitelist: true,
253
forbidNonWhitelisted: true,
254
}),
255
);
256
257
await app.listen(3000);
258
}
259
bootstrap();
260
261
// Custom global ObjectId validation
262
@Injectable()
263
export class GlobalObjectIdValidationPipe implements PipeTransform {
264
transform(value: any, metadata: ArgumentMetadata) {
265
// Auto-detect ObjectId strings in parameters
266
if (metadata.type === 'param' && typeof value === 'string' && isValidObjectId(value)) {
267
return new Types.ObjectId(value);
268
}
269
270
return value;
271
}
272
}
273
274
// Register globally
275
app.useGlobalPipes(new GlobalObjectIdValidationPipe());
276
```
277
278
### Integration with Class Validators
279
280
```typescript { .api }
281
import { IsString, IsOptional, ValidateNested, Type, registerDecorator, ValidationOptions } from 'class-validator';
282
import { Transform } from 'class-transformer';
283
import { isValidObjectId, Types } from 'mongoose';
284
285
// Custom ObjectId validator
286
export function IsObjectId(validationOptions?: ValidationOptions) {
287
return function (object: Object, propertyName: string) {
288
registerDecorator({
289
name: 'isObjectId',
290
target: object.constructor,
291
propertyName: propertyName,
292
options: validationOptions,
293
validator: {
294
validate(value: any) {
295
return typeof value === 'string' && isValidObjectId(value);
296
},
297
defaultMessage() {
298
return '$property must be a valid ObjectId';
299
},
300
},
301
});
302
};
303
}
304
305
// DTO with ObjectId validation
306
export class CreatePostDto {
307
@IsString()
308
title: string;
309
310
@IsString()
311
content: string;
312
313
@IsObjectId()
314
@Transform(({ value }) => value.toString())
315
authorId: string;
316
317
@IsOptional()
318
@IsObjectId()
319
@Transform(({ value }) => value ? value.toString() : undefined)
320
categoryId?: string;
321
322
@IsOptional()
323
@IsObjectId({ each: true })
324
@Transform(({ value }) => Array.isArray(value) ? value.map(id => id.toString()) : [])
325
tagIds?: string[];
326
}
327
328
@Controller('posts')
329
export class PostsController {
330
@Post()
331
async create(@Body() createPostDto: CreatePostDto) {
332
// All ObjectIds are validated by class-validator
333
return this.postsService.create(createPostDto);
334
}
335
}
336
```
337
338
## Error Handling
339
340
The validation pipes throw standard NestJS `BadRequestException` when validation fails:
341
342
```typescript { .api }
343
// Example error responses
344
{
345
"statusCode": 400,
346
"message": "Validation failed (ObjectId is expected)",
347
"error": "Bad Request"
348
}
349
350
// Custom error handling
351
@Injectable()
352
export class CustomObjectIdPipe implements PipeTransform {
353
transform(value: string): Types.ObjectId {
354
if (!isValidObjectId(value)) {
355
throw new BadRequestException({
356
statusCode: 400,
357
message: `Invalid ObjectId format: "${value}". ObjectId must be a 24 character hex string.`,
358
error: 'Invalid ObjectId',
359
provided: value,
360
});
361
}
362
363
return new Types.ObjectId(value);
364
}
365
}
366
```