Robust Data Integrity: Node.js JSON Schema Validation with TypeBox and TypeScript
Tags: javascript, typescript, node, tutorial
Published: 2022-08-19 15:11:02

### Introduction

In today's article I will explain how we can define a JSON Schema and perform its data validation using the [TypeBox](https://github.com/sinclairzx81/typebox) library in a Node environment with TypeScript.

Anyone who has gone through the frustration of creating a set of interfaces and enums and then translating it to a JSON Schema knows how hard it is to do this conversion, although there are libraries that already generate data types for JSON Schema.

And sometimes we either have to create our own generics or we are literally limited with what is provided to us by the libraries, with which we often end up spending more time solving problems related to data types than actually solving the real problem.

For these same reasons I like to use TypeBox, because I feel that the TypeScript support is first-class citizen.

## Getting Started

In a Node environment with TypeScript that you already have, install the following dependency:

```sh
npm install @sinclair/typebox --save
```

For the example of today's article, let's create a schema with only three properties that will be required, as follows:

```js
import { Type, Static } from "@sinclair/typebox";

export const profileSchema = Type.Object({
  firstName: Type.String(),
  lastName: Type.String(),
  age: Type.Integer(),
});
```

The schema created above is equivalent to the following JSON Schema:

```js
{
   "type":"object",
   "properties":{
      "firstName":{
         "type":"string"
      },
      "lastName":{
         "type":"string"
      },
      "age":{
         "type":"integer"
      }
   },
   "required":[
      "firstName",
      "lastName",
      "age"
   ]
}
```

Now, from the schema that was created, let's create a static data type:

```js
import { Type, Static } from "@sinclair/typebox";

export const profileSchema = Type.Object({
  firstName: Type.String(),
  lastName: Type.String(),
  age: Type.Integer(),
});

// 👇 added this line
export type ProfileSchemaType = Static<typeof profileSchema>; 
```

Then we can create a small factory, which will receive a schema as the only argument and as a return it will have a "copy" of the schema that was passed in the arguments and a validation function.

In this validation function we will receive as the only argument the data whose properties we want to validate, if they are valid we return the same data, otherwise we throw an error. This way:

```js
import { TObject } from "@sinclair/typebox";
import { TypeCompiler } from "@sinclair/typebox/compiler";

interface ValidatorFactoryReturn<T> {
  schema: TObject;
  verify: (data: T) => T;
}

export const validatorFactory = <T extends unknown>(
  schema: TObject
): ValidatorFactoryReturn<T> => {
  const C = TypeCompiler.Compile(schema);

  const verify = (data: T): T => {
    const isValid = C.Check(data);
    if (isValid) {
      return data;
    }
    throw new Error(
      JSON.stringify(
        [...C.Errors(data)].map(({ path, message }) => ({ path, message }))
      )
    );
  };

  return { schema, verify };
};
```

Finally, we can instantiate our factory passing the schema we created in the arguments and then we can validate the data we want using the `.verify()` function.

To have a clearer example, if you want to validate the data from the body of the http request, you can use it as follows:

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

import { profileSchema, ProfileSchemaType } from "./schema";
import { validatorFactory } from "./validator";

const profileValidation = validatorFactory<ProfileSchemaType>(profileSchema);

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

app.use(koaBody());

router.post("/", (ctx) => {
  const body = ctx.request.body as ProfileSchemaType;
  const data = profileValidation.verify(body);
  ctx.body = { data };
});

app.use(router.routes());

app.listen(3000);
```

And in the http request body we can send the following object:

```js
{
  "firstName": "Francisco",
  "lastName": "Mendes",
  "job": "Full Stack Dev"
}
```

As you can expect, you will receive an error, most likely a `500` because the `job` property is not defined in the schema and the `age` property is missing. But if a correct object is sent, it is expected that the response will be the same as the object that was sent.

## 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](https://github.com/FranciscoMendes10866/node-typebox-validation) the link to the github repository.