-
Notifications
You must be signed in to change notification settings - Fork 0
pipes
With this chapter, if you want to follow along, check out the pipes-start
branch and go to the following along section.
If you just want a final version of the project as of the end of the chapter, check out the pipes-end
branch, and proceed to the upon completion section.
Refer to the comments below, corresponding to chapter sections, to guide you through the code changes for nest cats as you proceed through the docs chapter.
Read through the Pipes chapter until you get to the Object schema validation section, then return here.
When you get to the Object schema validation section, go ahead and start with the npm package installs.
For the JoiValidationPipe
class, we'll follow the convention we established in the last few chapters. Create a folder called pipes
in the src/common
folder. The folder structure should now look like:
nest-cats
└───src
└───cats
│ └───dto
│ └───interfaces
└───common
└───filters
└───middleware
└───pipes
Go ahead and create joi-validation.pipe.ts
in the src/common/pipes
folder, using the code shown in this section of the docs.
Before moving on to the next section, let's take a few minutes to flesh the schema validation out further.
The docs are a little light on showing how to create a Joi schema that would be used with this validation pipe. Let's go ahead and create one now. Joi schemas are pretty straightforward; you can read all about them here.
Let's create a folder for the schema in the cats
folder. You should end up with a folder called src/cats/schema
. In it, create a file called create-cat.schema.ts
. Add the following code to that file:
import * as Joi from '@hapi/joi';
export const createCatSchema = Joi.object().keys({
name: Joi.string().required(),
age: Joi.number()
.integer()
.min(0)
.max(100)
.required(),
breed: Joi.string().required(),
});
Now you're ready to continue with the the Binding pipes documentation, here.
Go ahead and bind the pipe to the create()
method in src/cats/cats.controller.ts
as shown in this section. As usual, you'll also need to import the other pieces: the schema, the UsePipes
decorator, and the JoiValidationPipe
class.
Test this with the following routes:
add a valid cat
http POST :3000/cats name=Fred age:=3 breed='Alley Cat'
retrieve the valid cat
http GET :3000/cats
add a non-valid cat (we're missing required field breed
)
http POST :3000/cats name=Fred age:=3
Note the error received:
{
"error": "Bad Request",
"message": "Validation failed",
"statusCode": 400
}
Let's try to provide a better error. Write the validation error message to the console log by adding a console.log
line to the transform
method in the src/common/pipes/joi-validation.pipe.ts
as shown below:
...
transform(value: any, metadata: ArgumentMetadata) {
const { error } = Joi.validate(value, this.schema);
if (error) {
console.log('validation error: ', error);
throw new BadRequestException('Validation failed');
}
return value;
}
...
Run the invalid POST /cats
request again, and note the error logged to the console. It should look something like this
validation error: { ValidationError: child "breed" fails because ["breed" is required]
... (stack trace omitted)
isJoi: true,
name: 'ValidationError',
details:
[ { message: '"breed" is required',
path: [ 'breed' ],
type: 'any.required',
context: { key: 'breed', label: 'breed' } } ],
_object: { name: 'Fred', age: 3 },
annotate: [Function] }
With this in hand, you can see how you could use the error.details
property of the error returned by Joi.validate()
to construct a more useful API response that would enable the front-end to show the user what was wrong with the input.
When you get to the class validator section of the docs, go ahead and install the required dependencies.
Then go ahead and update the src/cats/dto/create-cat.dto.ts
file as shown in the docs.
Then go ahead and create the ValidationPipe
class in src/common/pipes/validation.pipe.ts
.
To bind this validation pipe, follow the instructions in the docs, here near the end of the section, updating src/cats/cats.controller.ts
. Since we already applied the schema validation pipe to the create()
method, let's make a copy to work with. Copy/paste that method and comment out the first one. Then bind the new ValidationPipe
as shown. Your code should look something like this:
// Use one or the other of the following implementations for
// the create() method.
//
// This version uses the JoiValidationPipe
// @Post()
// @UsePipes(new JoiValidationPipe(createCatSchema))
// async create(@Body() createCatDto: CreateCatDto) {
// this.catsService.create(createCatDto);
// }
// This version uses the hand-crafted (class validator) ValidationPipe
@Post()
@UsePipes(new ValidationPipe())
async create(@Body() createCatDto: CreateCatDto) {
this.catsService.create(createCatDto);
}
As usual, you'll also need to import the newly created ValidationPipe
into the src/cats/cats.controller.ts
file as well.
As before:
add a valid cat
http POST :3000/cats name=Fred age:=3 breed='Alley Cat'
retrieve the valid cat
http GET :3000/cats
add a non-valid cat (we're missing required field breed
)
http POST :3000/cats name=Fred age:=3
Note the error received:
{
"error": "Bad Request",
"message": "Validation failed",
"statusCode": 400
}
Again, you can see how to provide a better error message by adding a console.log
line to the transform
method in the src/common/pipes/joi-validation.pipe.ts
as shown below:
...
async transform(value: any, { metatype }: ArgumentMetadata) {
if (!metatype || !this.toValidate(metatype)) {
return value;
}
const object = plainToClass(metatype, value);
const errors = await validate(object);
if (errors.length > 0) {
console.log('validation error: ', errors);
throw new BadRequestException('Validation failed');
}
return value;
}
...
Run the invalid POST /cats
request again, and note the error logged to the console. It should look something like this
validation error: [ ValidationError {
target: CreateCatDto { name: 'Fred', age: 3 },
value: undefined,
property: 'breed',
children: [],
constraints: { isString: 'breed must be a string' } } ]
With this in hand, you can see how you could use the ValidationError
property to construct a more useful API response that would enable the front-end to show the user what was wrong with the input.
When you get to the Transformation use case section of the docs, go ahead and create the parse-int.pipe.ts
file in src/common/pipes
, as shown.
Bind the pipe to the id
parameter in the findOne()
method of src/cats/cats.controller.ts
, as shown in the docs. Don't forget to import your new ParseIntPipe
class!
Note that the method in the docs assumes a findOne()
method exists on CatsService
. For now, we'll revert this to returning a static text message as we are only interested in testing our new ParseIntPipe
. Change the implementation of that method to look like:
@Get(':id')
async findOne(@Param('id', new ParseIntPipe()) id) {
return `This action returns a #${id} cat`;
}
make a valid request
http GET :3000/cats/1
make an invalid request - pass a string where an integer is expected
http GET :3000/cats/a
Again, note the generic error. Using the techniques shown earlier, you can construct a more helpful error message.
When you get to the The built-in ValidationPipe section of the docs, go ahead and update the src/cats/cats.controller.ts
file as shown to use this built-in pipe. Include the {transform: true}
config object.
Since you now want to use the built-in ValidationPipe
(which has the same name as the custom one we just built), make sure you remove (or comment out) the statement
import { ValidationPipe } from '../common/pipes/validation.pipe';
and then add ValidationPipe
to the list of symbols imported from @nestjs/common
add a valid cat
http POST :3000/cats name=Fred age:=3 breed='Alley Cat'
retrieve the valid cat
http GET :3000/cats
add a non-valid cat (we're missing required field breed
)
http POST :3000/cats name=Fred age:=3
Note the error received has specific information about how the validation failed:
{
"error": "Bad Request",
"message": [
{
"children": [],
"constraints": {
"isString": "breed must be a string"
},
"property": "breed",
"target": {
"age": 3,
"name": "Fred"
}
}
],
"statusCode": 400
}
We're done. We've tested our pipes as we went along.
Next up is the Guards chapter.