# Validation

Ts.ED provide by default a AJV package @tsed/ajv to perform a validation on a Model.

This package must be installed to run automatic validation on input data. Any model used on parameter and annotated with one of JsonSchema decorator will be validated with AJV.

npm install --save @tsed/ajv
1

But, you can choose another library as model validator.

# Data input validation

Ts.ED support the data input validation with the decorators provided by @tsed/schema.

Example:

import {PathParams} from "@tsed/platform-params";
import {Get} from "@tsed/schema";
import {Controller} from "@tsed/di";
import {MinLength} from "@tsed/schema";

@Controller("/calendars")
export class CalendarsController {
  @Get(":id")
  findOne(@PathParams("id") @MinLength(10) id: string) {
    return `This action returns a #${id} calendar`;
  }
}
1
2
3
4
5
6
7
8
9
10
11
12

# Custom Validation

Ts.ED allows you to change the default ValidationPipe by your own library. The principle is simple. Create a CustomValidationPipe and use OverrideProvider to change the default ValidationPipe .

import {ValidationError, ValidationPipe} from "@tsed/platform-params";
import {JsonParameterStore, PipeMethods} from "@tsed/schema";
import {OverrideProvider} from "@tsed/di";
import {getJsonSchema} from "@tsed/schema";
import {validate} from "./validate";

@OverrideProvider(ValidationPipe)
export class CustomValidationPipe extends ValidationPipe implements PipeMethods {
  public transform(obj: any, metadata: JsonParameterStore): void {
    // JSON service contain tool to build the Schema definition of a model.
    const schema = getJsonSchema(metadata.type);

    if (schema) {
      const valid = validate(schema, obj);

      if (!valid) {
        throw new ValidationError("My message", [
          /// list of errors
        ]);
      }
    }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

WARNING

Don't forgot to import the new CustomValidatorPipe in your server.ts !

# Use Joi

There are several approaches available for object validation. One common approach is to use schema-based validation. The Joi (opens new window) library allows you to create schemas in a pretty straightforward way, with a readable API.

Let's look at a pipe that makes use of Joi-based schemas.

Start by installing the required package:

npm install --save joi
1

In the code sample below, we create a simple class that takes a schema as a constructor argument. We then apply the schema.validate() method, which validates our incoming argument against the provided schema.

In the next section, you'll see how we supply the appropriate schema for a given controller method using the UsePipe decorator.

import {ObjectSchema} from "joi";
import {Injectable} from "@tsed/di";
import {JsonParameterStore, PipeMethods} from "@tsed/schema";
import {ValidationError, ValidationPipe} from "@tsed/platform-params";

@OverrideProvider(ValidationPipe)
export class JoiValidationPipe implements PipeMethods {
  transform(value: any, metadata: JsonParameterStore) {
    const schema = metadata.store.get<ObjectSchema>(JoiValidationPipe);

    if (schema) {
      const {error} = schema.validate(value);

      if (error) {
        throw new ValidationError("Oops something is wrong", [error]);
      }
    }

    return value;
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

Now, we have to create a custom decorator to store the Joi schema along with a parameter:

import {ObjectSchema} from "joi";
import {StoreSet} from "@tsed/core";
import {JoiValidationPipe} from "../pipes/JoiValidationPipe";

export function UseJoiSchema(schema: ObjectSchema) {
  return StoreSet(JoiValidationPipe, schema);
}
1
2
3
4
5
6
7

And finally, we are able to add Joi schema with our new decorator:

import {BodyParams} from "@tsed/platform-params";
import {Get} from "@tsed/schema";
import {Controller} from "@tsed/di";
import {UseJoiSchema} from "../decorators/UseJoiSchema";
import {joiPersonModel, PersonModel} from "../models/PersonModel";

@Controller("/persons")
export class PersonsController {
  @Get(":id")
  async findOne(
    @BodyParams("id")
    @UseJoiSchema(joiPersonModel)
    person: PersonModel
  ) {
    return person;
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# Use Class validator

Let's look at an alternate implementation of our validation technique.

Ts.ED works also with the class-validator (opens new window) library. This library allows you to use decorator-based validation (like Ts.ED with his JsonSchema decorators). Decorator-based validation combined with Ts.ED Pipe capabilities since we have access to the medata.type of the processed parameter.

Before we start, we need to install the required packages:

npm i --save class-validator class-transformer
1

Once these are installed, we can add a few decorators to the PersonModel:

import {IsString, IsInt} from "class-validator";

export class CreateCatDto {
  @IsString()
  firstName: string;

  @IsInt()
  age: number;
}
1
2
3
4
5
6
7
8
9

TIP

Read more about the class-validator decorators here (opens new window).

Now we can create a [ClassValidationPipe] class:

import {ValidationError, ValidationPipe} from "@tsed/platform-params";
import {JsonParameterStore, PipeMethods} from "@tsed/schema";
import {OverrideProvider} from "@tsed/di";
import {plainToClass} from "class-transformer";
import {validate} from "class-validator";

@OverrideProvider(ValidationPipe)
export class ClassValidationPipe extends ValidationPipe implements PipeMethods<any> {
  async transform(value: any, metadata: JsonParameterStore) {
    if (!this.shouldValidate(metadata)) {
      // there is no type and collectionType
      return value;
    }

    const object = plainToClass(metadata.type, value);
    const errors = await validate(object);

    if (errors.length > 0) {
      throw new ValidationError("Oops something is wrong", errors);
    }

    return value;
  }

  protected shouldValidate(metadata: JsonParameterStore): boolean {
    const types: Function[] = [String, Boolean, Number, Array, Object];

    return !(metadata.type || metadata.collectionType) || !types.includes(metadata.type);
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

Notice

Above, we have used the class-transformer (opens new window) library. It's made by the same author as the class-validator library, and as a result, they play very well together.

Note that we get the type from ParamMetadata and give it to plainToObject function. The method shouldValidate bypass the validation process for the basic types and when the metadata.type or metadata.collectionType are not available.

Next, we use the class-transformer function plainToClass() to transform our plain JavaScript argument object into a typed object so that we can apply validation. The incoming body, when deserialized from the network request, does not have any type information. Class-validator needs to use the validation decorators we defined for our PersonModel earlier, so we need to perform this transformation.

Finally, we return the value when we haven't errors or throws a ValidationError.

TIP

If you use class-validator, it also be logical to use class-transformer (opens new window) as Deserializer. So we recommend to override also the DeserializerPipe .

import {DeserializerPipe} from "@tsed/platform-params";
import {JsonParameterStore, PipeMethods} from "@tsed/schema";
import {OverrideProvider} from "@tsed/di";
import {plainToClass} from "class-transformer";

@OverrideProvider(DeserializerPipe)
export class ClassTransformerPipe implements PipeMethods {
  transform(value: any, metadata: JsonParameterStore) {
    return plainToClass(metadata.type, value);
  }
}
1
2
3
4
5
6
7
8
9
10
11

We just have to import the pipe on our server.ts and use model as type on a parameter.

Last Updated: 3/15/2024, 12:53:49 PM

Other topics