# AJV
This tutorial shows you how you can validate your data with decorators.
Validation feature uses Ajv (opens new window) and json-schema (opens new window) to perform the model validation.
# Installation
Before using the validation decorators, we need to install the ajv (opens new window) module.
npm install --save ajv
npm install --save @tsed/ajv
2
Then import @tsed/ajv
in your Server:
import {Configuration} from "@tsed/common";
import "@tsed/ajv"; // import ajv ts.ed module
@Configuration({
ajv: {
returnsCoercedValues: true // returns coerced value to the next pipe instead of returns original value (See #2355)
}
})
export class Server {}
2
3
4
5
6
7
8
9
The AJV module allows a few settings to be added through the ServerSettings (all are optional):
- options are AJV specific options passed directly to the AJV constructor,
- errorFormatter can be used to alter the output produced by the
@tsed/ajv
package.
The error message could be changed like this:
import {Configuration} from "@tsed/common";
import "@tsed/ajv"; // import ajv ts.ed module
@Configuration({
ajv: {
errorFormatter: (error) => `At ${error.modelName}${error.dataPath}, value '${error.data}' ${error.message}`,
verbose: true
}
})
export class Server {}
2
3
4
5
6
7
8
9
10
# Decorators
Ts.ED gives some decorators to write your validation model:
# Examples
# Model validation
A model can be used on a method controller along with BodyParams => BodyParams or other decorators, and will be validated by Ajv.
import {Required, MaxLength, MinLength, Minimum, Maximum, Format, Enum, Pattern, Email} from "@tsed/common";
export class CalendarModel {
@MaxLength(20)
@MinLength(3)
@Required()
title: string;
@Minimum(0)
@Maximum(10)
rating: number;
@Email()
email: string;
@Format("date") // or date-time, etc...
createDate: Date;
@Pattern(/hello/)
customInput: string;
@Enum("value1", "value2")
customInput: "value1" | "value2";
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# Validation error
When a validation error occurs, AJV generates a list of errors with a full description like this:
[
{
"keyword": "minLength",
"dataPath": ".password",
"schemaPath": "#/properties/password/minLength",
"params": {"limit": 6},
"message": "should NOT be shorter than 6 characters",
"modelName": "User"
}
]
2
3
4
5
6
7
8
9
10
# User defined keywords
Ajv allows you to define custom keywords to validate a property.
You can find more details on the different ways to declare a custom validator on this page: https://ajv.js.org/docs/keywords.html
Ts.ED introduces the Keyword decorator to declare a new custom validator for Ajv. Combined with the CustomKey decorator to add keywords to a property of your class, you can use more complex scenarios than what basic JsonSchema allows.
For example, we can create a custom validator to support the range
validation over a number. To do that, we have to define
the custom validator by using
Keyword
decorator:
import {Keyword, KeywordMethods} from "@tsed/ajv";
import {array, number} from "@tsed/schema";
@Keyword({
keyword: "range",
type: "number",
schemaType: "array",
implements: ["exclusiveRange"],
metaSchema: array().items([number(), number()]).minItems(2).additionalItems(false)
})
class RangeKeyword implements KeywordMethods {
compile([min, max]: number[], parentSchema: any) {
return parentSchema.exclusiveRange === true ? (data: any) => data > min && data < max : (data: any) => data >= min && data <= max;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Then we can declare a model using the standard decorators from @tsed/schema
:
Finally, we can create a unit test to verify if our example works properly:
import "@tsed/ajv";
import {PlatformTest} from "@tsed/common";
import {getJsonSchema} from "@tsed/schema";
import {Product} from "./Product";
import "../keywords/RangeKeyword";
describe("Product", () => {
beforeEach(PlatformTest.create);
afterEach(PlatformTest.reset);
it("should call custom keyword validation (compile)", () => {
const ajv = PlatformTest.get<Ajv>(Ajv);
const schema = getJsonSchema(Product, {customKeys: true});
const validate = ajv.compile(schema);
expect(schema).to.deep.equal({
properties: {
price: {
exclusiveRange: true,
range: [10, 100],
type: "number"
}
},
type: "object"
});
expect(validate({price: 10.01})).toEqual(true);
expect(validate({price: 99.99})).toEqual(true);
expect(validate({price: 10})).toEqual(false);
expect(validate({price: 100})).toEqual(false);
});
});
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
31
32
WARNING
If you planed to create keyword that transform the data, you have to set returnsCoercedValues
to true
in your configuration.
# With "code" function
Starting from v7 Ajv uses CodeGen module (opens new window) for all pre-defined keywords - see codegen.md (opens new window) for details.
Example even
keyword:
# Formats v6.36.0+
You can add and replace any format using
Formats
decorator. For example, the current format validator for uri
doesn't allow
empty string. So, with this decorator you can create or override an existing ajv-formats (opens new window) validator.
import {Formats, FormatsMethods} from "@tsed/ajv";
const NOT_URI_FRAGMENT = /\/|:/;
const URI =
/^(?:[a-z][a-z0-9+\-.]*:)(?:\/?\/(?:(?:[a-z0-9\-._~!$&'()*+,;=:]|%[0-9a-f]{2})*@)?(?:\[(?:(?:(?:(?:[0-9a-f]{1,4}:){6}|::(?:[0-9a-f]{1,4}:){5}|(?:[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){4}|(?:(?:[0-9a-f]{1,4}:){0,1}[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){3}|(?:(?:[0-9a-f]{1,4}:){0,2}[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){2}|(?:(?:[0-9a-f]{1,4}:){0,3}[0-9a-f]{1,4})?::[0-9a-f]{1,4}:|(?:(?:[0-9a-f]{1,4}:){0,4}[0-9a-f]{1,4})?::)(?:[0-9a-f]{1,4}:[0-9a-f]{1,4}|(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?))|(?:(?:[0-9a-f]{1,4}:){0,5}[0-9a-f]{1,4})?::[0-9a-f]{1,4}|(?:(?:[0-9a-f]{1,4}:){0,6}[0-9a-f]{1,4})?::)|[Vv][0-9a-f]+\.[a-z0-9\-._~!$&'()*+,;=:]+)\]|(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?)|(?:[a-z0-9\-._~!$&'()*+,;=]|%[0-9a-f]{2})*)(?::\d*)?(?:\/(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})*)*|\/(?:(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})+(?:\/(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})*)*)?|(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})+(?:\/(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})*)*)(?:\?(?:[a-z0-9\-._~!$&'()*+,;=:@/?]|%[0-9a-f]{2})*)?(?:#(?:[a-z0-9\-._~!$&'()*+,;=:@/?]|%[0-9a-f]{2})*)?$/i;
@Formats("uri", {type: "string"})
export class UriFormat implements FormatsMethods<string> {
validate(str: string): boolean {
// http://jmrware.com/articles/2009/uri_regexp/URI_regex.html + optional protocol + required "."
return str === "" ? true : NOT_URI_FRAGMENT.test(str) && URI.test(str);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
Then, we can import this class to our server as follows:
import {Configuration} from "@tsed/common";
import "@tsed/ajv"; // import ajv ts.ed module
import "./formats/UriFormat"; // just import the class, then Ts.ED will mount automatically the new format
@Configuration({
ajv: {
// ajv options
}
})
export class Server {}
2
3
4
5
6
7
8
9
10
Now, this example will be valid:
import {Uri, getJsonSchema} from "@tsed/schema";
import {PlatformTest} from "@tsed/common";
import {AjvService} from "@tsed/ajv";
import "./UriFormat";
describe("UriFormat", () => {
beforeEach(() => PlatformTest.create());
afterEach(() => PlatformTest.reset());
it("should validate empty string when we load the our custom Formats for AJV", async () => {
class MyModel {
@Uri() // or @Format("uri")
uri: string;
}
const service = PlatformTest.get<AjvService>(AjvService);
const jsonSchema = getJsonSchema(MyModel);
expect(jsonSchema).to.deep.equal({
properties: {
uri: {
format: "uri",
type: "string"
}
},
type: "object"
});
const result = await service.validate({uri: ""}, {type: MyModel});
expect(result).to.deep.eq({uri: ""});
});
});
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
31
32
# Author
# Maintainers
Last Updated: 10/24/2024, 6:33:40 AM
Other topics
- Session & cookies
- Passport.js
- Keycloak
- Prisma
- TypeORM
- MikroORM
- Mongoose
- GraphQL
- GraphQL WS
- Apollo
- TypeGraphQL
- GraphQL Nexus
- Socket.io
- Swagger
- AJV
- Multer
- Serve static files
- Templating
- Serverless HTTP
- Seq
- OIDC
- Stripe
- Agenda
- Terminus
- Serverless
- Server-sent events
- IORedis
- Vike
- Jest
- Vitest
- Controllers
- Providers
- Model
- JsonMapper
- Middlewares
- Pipes
- Interceptors
- Authentication
- Hooks
- Exceptions
- Throw HTTP Exceptions
- Cache
- Command
- Response Filter
- Injection scopes
- Custom providers
- Lazy-loading provider
- Custom endpoint decorator
- Testing
- Customize 404