Using Yup to validate user input in a NestJS project
Tags: javascript, nestjs, yup, validate
Published: 2023-09-20 18:17:24

This article assumes that you are familiar with [NestJS](https://nestjs.com/) framework.

**The Importance of Validation**
Validation is a crucial step in ensuring the integrity and security of your application. While `NestJS` documentation offers the [class-validator](https://github.com/typestack/class-validator) library for validation, it can sometimes be challenging to work with. Lets explore an alternative validation approach using [Yup](https://github.com/jquense/yup) in a `NestJS` project.

**Using class-validator**
In a typical `NestJS` project, you might define your DTOs (Data Transfer Objects) with `class-validator` decorators to perform validation. In my project users can submit posts with title and variable amount of teams. A team has one field - title. In accordance with `Nestjs` docs I created a DTO for post data and added validation to it:
```js
class Team {
  @IsDefined({message: 'Bad request'})
  @IsString({message: 'Bad request'})
  @MinLength(TITLE_MIN_LENGTH, {
    message: 'Too short',
  })
  @MaxLength(TITLE_MAX_LENGTH, {
    message: 'Too long',
  })
  name: string;
}

class PostBody {
  @IsDefined({message: 'Bad request'})
  @IsString({message: 'Bad request'})
  @MinLength(TITLE_MIN_LENGTH, {
    message: 'Too short',
  })
  @MaxLength(TITLE_MAX_LENGTH, {
    message: 'Too long',
  })
  title: string;

  @IsDefined({message: 'Bad request'})
  @IsArray({message: 'Bad request'})
  @ArrayMinSize(MIN_TEAMS)
  @ArrayMaxSize(MAX_TEAMS)
  @ValidateNested()
  @Type(() => Team)
  teams: Team[];
}
```
Pretty hard to find where the real DTO is, huh?

My primary concern was that the `ArrayMinSize` and `ArrayMaxSize` validators were not functioning correctly. Users could submit any number of teams, and the DTO would still pass validation. This is a known issue with `class-validator`, and although workarounds exist, none of them seemed optimal.

**Introducing Yup**
After conducting some research I decided to switch to a library that works for all of my use cases out of box. I have looked at most popular validation libraries and found out that `Yup` covers all my needs:
- Validates my DTOs.
- Easy schema definition syntax.
- Ability to incorporate error messages directly into validation rules.

`Yup` functions similarly to `class-validator`. It requires you to define validation rule schemas, allowing to separate the DTO from validation. The DTO is now a plain JavaScript object:
```js
class PostBody {
  title: string;
  teams: Team[];
}
```
Validation rules can be defined as constants and can be reused across various validation scenarios. Lets define rule for validating title:
```js
const titleSchema = string()
  .strict()
  .typeError('Bad request')
  .trim()
  .min(TITLE_MIN_LENGTH, `Too short`)
  .max(TITLE_MAX_LENGTH, `Too long`)
```
And now to the main point - validation for an array of objects:
```js
const teamsSchema = array()
  .strict()
  .typeError('Bad request')
  .min(MIN_TEAMS, 'Bad request')
  .max(MAX_TEAMS, 'Bad request')
  .of(
    object({
      name: titleSchema,
    }),
  )
```

Schema for validating the entire post:
```js
const postSchema = object({
  title: titleSchema,
  teams: teamsSchema,
})
```
Note the reuse of `titleSchema` in `postSchema` and in `teamsSchema`. DRY in action.

Declaring validation rules as constants enables us to create similar schemas without the need to redundantly redeclare the rules. For instance, we can define a "post create" schema that accepts all fields and a "post update" schema that accepts only the title field.

To integrate `Yup` validation into a `NestJS` project, we'll need a custom validation pipe:
```js
class YupValidationPipe implements PipeTransform {
  constructor(private schema: ObjectSchema<any>) {}

  transform(value: any) {
    return this.schema.validateSync(value);
  }
}
```
Now we can use the `YupValidationPipe` in a controller action to validate incoming data easily:
```js
async createPost(
  @GetUser() user: User, 
  @Body(new YupValidationPipe(postSchema)) post: PostBody
) { ... }
```
**Conclusion**
While initially seeking a library solely for required validation, my transition to `Yup` yielded unexpected advantages:
- Cleaner and more concise DTO code.
- Rule reusability.
- Convenient error message handling within validation rules, facilitating user-friendly error displays (an uncommon feature in many libraries).

Of course, integrating `Yup` into my `NestJS` project required some additional effort, but it ultimately proved to be a more efficient solution compared to troubleshooting issues with `class-validator`.

Visit [clapduel.com](https://www.clapduel.com) to see the code in action.