DEV Community

Cover image for Ensuring Data Integrity: JSON Schema Validation in Node.js with Ajv
Francisco Mendes
Francisco Mendes

Posted on • Edited on

Ensuring Data Integrity: JSON Schema Validation in Node.js with Ajv

Introduction

If you work with JavaScript and Node.js, most likely you've already felt the need to do data validation either on the frontend or on the backend.

There are several libraries and several approaches to perform data validation and most of the time we like to use abstractions, however sometimes we really need to define a JSON Schema and then validate the data through that same JSON Schema.

And for that same reason in today's article we are going to use Ajv, in addition to being a validator, it also has several plugins that help us to "extend" the validation criteria.

Prerequisites

Before going further, you are expected to have basic knowledge of these technologies:

  • Node
  • NPM
  • JSON Schema

Getting Started

First let's create our project folder:

mkdir node-ajv
cd node-ajv
npm init -y
Enter fullscreen mode Exit fullscreen mode

In the project's package.json add the following property:

{
  "type": "module",
}
Enter fullscreen mode Exit fullscreen mode

Now, let's install Ajv in our project:

npm install ajv --save
Enter fullscreen mode Exit fullscreen mode

The next step will be to create our JSON Schema, which will be inside the src/ folder in a file called schema.js:

export const authSchema = {
  type: "object",
  properties: {
    username: {
      type: "string",
      description: "Username of the user",
    },
    email: {
      type: "string",
      description: "Email of the user",
    },
    password: {
      type: "string",
      description: "Password of the user",
      minLength: 8,
      maxLength: 24,
    },
  },
  required: ["username", "email", "password"],
  additionalProperties: false,
};
Enter fullscreen mode Exit fullscreen mode

Then we can create the validator.js, this file will contain the Ajv instance as well as the factory that will be reused whenever we want to create a new validator:

import Ajv from "ajv";
import { inspect } from "util";

const ajv = new Ajv({ allErrors: true });

export const validatorFactory = (schema) => {
  const validate = ajv.compile(schema);

  const verify = (data) => {
    const isValid = validate(data);
    if (isValid) {
      return data;
    }
    throw new Error(
      ajv.errorsText(
        validate.errors?.filter((err) => err.keyword !== "if"),
        { dataVar: "schemaValidation" } + "\n\n" + inspect(data)
      )
    );
  };

  return { schema, verify };
};
Enter fullscreen mode Exit fullscreen mode

As you may have noticed, the validatorFactory() function takes in the arguments the schema (which corresponds to a JSON Schema) and returns two properties:

  • schema - A "copy" of the schema that was passed in the function arguments (if you want you can do a deep copy)
  • verify - receives in the arguments the data that we intend to validate and if this same data is valid the validation is successful and is returned, otherwise the error is thrown

For this article to be easily testable, we can create a simple api, first we install the dependencies:

npm install koa @koa/router koa-body --save
Enter fullscreen mode Exit fullscreen mode

Then we create a base api:

import Koa from "koa";
import Router from "@koa/router";
import koaBody from "koa-body";

const app = new Koa();
const router = new Router();

app.use(koaBody());

router.post("/", async (ctx) => {
  ctx.body = "Hello, World";
});

app.use(router.routes());

app.listen(3000);
Enter fullscreen mode Exit fullscreen mode

Finally, we can import the schema that was created, as well as the validation factory, then we create a validator called authValidation and validate the data from the body of the http request. This way:

import Koa from "koa";
import Router from "@koa/router";
import koaBody from "koa-body";

import { validatorFactory } from "./validator.js"; //  👈 added this
import { authSchema } from "./schema.js"; //  👈 added this

const authValidation = validatorFactory(authSchema); //  👈 added this

const app = new Koa();
const router = new Router();

app.use(koaBody());

// 👇 changes have been made here
router.post("/", async (ctx) => {
  const body = ctx.request.body;
  const data = authValidation.verify(body);
  ctx.body = { data };
});

app.use(router.routes());

app.listen(3000);
Enter fullscreen mode Exit fullscreen mode

Now we can make an http request with the POST at http://localhost:3000 like this:

{
  "email": "random@mail.com",
  "username": "random",
  "password": "randomPaswword"
}
Enter fullscreen mode Exit fullscreen mode

Conclusion

As usual, I hope you enjoyed the article and that it helped you with an existing project or simply wanted to try it out.

If you found a mistake in the article, please let me know in the comments so I can correct it. Before finishing, if you want to access the source code of this article, I leave here the link to the github repository.

Top comments (4)

Collapse
 
urielsouza29 profile image
Uriel dos Santos Souza

How to translate errors?

Collapse
 
franciscomendes10866 profile image
Francisco Mendes

For that you can use this package: github.com/ajv-validator/ajv-i18n

Collapse
 
urielsouza29 profile image
Uriel dos Santos Souza

Error customization

Thread Thread
 
franciscomendes10866 profile image
Francisco Mendes

If you want to set a custom error message, you can use this: github.com/ajv-validator/ajv-errors