Generating JSON Schema from TypeScript types
Tags: json, typescript, webdev
Published: 2023-07-11 14:11:37

**Written by [Ikeh Akinyemi](https://blog.logrocket.com/author/ikehakinyemi/)✏️**

In modern web development, it’s common to work with complex data that must be transmitted between different applications. JSON has proven to be an excellent standardized serialization process for exchanging wire data between applications. 

However, with this increased complexity in wire data comes a corresponding lack of security. While JSON is flexible, it doesn’t provide any protection for your data. We then face the challenge of ensuring that the data we transmit between applications adhere to a known schema. 

This uncertainty can prove problematic, especially for applications that take data security very seriously. So how do we address this? 

JSON Schema provides a solution to the aforementioned security concern. It allows us to define a strict structure for the wire data transmitted between communicating applications. 

Throughout this article, we’ll explore how to generate and work with JSON Schema using TypeScript types. Jump ahead:

*   [Why use JSON Schema and TypeScript?](#why-use-json-schema-typescript)
*   [Generating JSON Schema from TypeScript types](#generating-json-schema-typescript-types)
    *   [Project setup](#project-setup)
    *   [Writing a TypeScript interface](#writing-typescript-interface)
    *   [Using the `ts-json-schema-generator` package](#using-ts-json-schema-generator-package)
*   [Using the generated JSON Schema](#using-generated-json-schema)
    *   [Adding a middleware function to validate our data](#adding-middleware-function-validate-data)

You can check out the full project code in [this GitHub repository](https://github.com/Ikeh-Akinyemi/json-schema).

## Why use JSON Schema and TypeScript? <a name="why-use-json-schema-typescript">

Before we delve into JSON Schema as a solution, let’s first answer the question, what exactly is JSON Schema? 

JSON Schema is an established specification that helps us to [define the structure of JSON data](https://blog.logrocket.com/5-useful-json-tools-improve-productivity/#validate-data-json-schema). We define what properties the data should have, what data type we expect those properties should be, and how they should be validated. 

By using the Schema, we ensure that the wire data our application is accepting is formatted correctly and that any possible errors are caught early in our development process. So, where does TypeScript fit into the picture? 

TypeScript provides a [static type-checking feature](https://blog.logrocket.com/typescript-vs-jsdoc-javascript/) that validates the type consistency of variables, function parameters, and return values at compile-time. By using tools that can convert TypeScript types into JSON Schemas, we can ensure our wire data adheres to a strict schema.

## Generating JSON Schema from TypeScript Types <a name="generating-json-schema-typescript-types">

In this section, we will explore how we can generate JSON Schema from our defined TypeScript types we’ll define. 

We will use the `ts-json-schema-generator` library, which supports a wide range of TypeScript features, including interfaces, classes, enums, unions, and more. However, it’s worth noting that there are various other developer tools you can use to achieve the same result.

### Project setup

Let’s start by creating a new project folder named `json-schema-project` and running an `init` command within the new directory to initialize a new project:

```bash
mkdir json-schema-project; cd json-schema-project;
npm init -y
```

Now we have set up our project, let’s install the `ts-json-schema-generator` library:

```bash
npm install -g ts-json-schema-generator
```

Once we've installed the library, we can use it to generate JSON Schema from our TypeScript types.

### Writing a TypeScript interface <a name="writing-typescript-interface">

Let's start by creating a simple TypeScript file named `types.ts` within the `src` folder. Within this file, we'll write a simple TypeScript interface:

```typescript
export interface Person {
  firstName: string;
  lastName: string;
  age: number;
  socials: string[];
}
```

In the above snippet, we defined a `Person` object using TypeScript’s interface. Note the types of each property within the interface.

### Using the `ts-json-schema-generator` package <a name="using-ts-json-schema-generator-package">

Next, let’s use the `ts-json-schema-generator` package to generate a JSON Schema for the `Person` interface. We will use this command:

```typescript
ts-json-schema-generator --path 'src/types.ts' --type 'Person'
```

This command will instruct the `ts-json-schema-generator` package to generate a JSON Schema for the `Person` type in the `src/types.ts` file. 

If the command runs successfully, the result will be the below JSON object that represents the generated JSON Schema:

```json
{
  "$ref": "#/definitions/Person",
  "$schema": "http://json-schema.org/draft-07/schema#",
  "definitions": {
    "Person": {
      "additionalProperties": false,
      "properties": {
        "age": {
          "type": "number"
        },
        "firstName": {
          "type": "string"
        },
        "lastName": {
          "type": "string"
        },
        "socials": {
          "items": {
            "type": "string"
          },
          "type": "array"
        }
      },
      "required": [ "firstName", "lastName", "age", "socials"],
      "type": "object"
    }
  }
}
```

Now, with this schema, we can specify that any object that claims to be of the type `Person` should have certain properties. These include `firstName` and `lastName` properties of type `string`, an `age` property of type `number`, and a `socials` property comprising an array of strings. 

To further understand the JSON Schema, let’s take a look at different fields of the JSON Schema object and what they stand for:

*   `$schema`: Specifies the version of the JSON Schema specification being used — in this case, v7
*   `type`: Specifies the type of object being described by the schema — in this case, an `object`
*   `properties`: An object that describes the properties of the object being described by the schema — in this case, `name`, `age`, and `hobbies`
*   `required`: An array that specifies which properties of the object are required — in this case, all three properties are required

## Using the generated JSON Schema <a name="using-generated-json-schema">

Now that we have seen how to generate JSON Schema from TypeScript, we can go further to see how to use it and validate data ensuring it conforms to the expected schema that we have. 

For the following example, we will programmatically use the `ts-json-schema-generator` package alongside the `ajv` schema validation package:

```typescript
import Ajv from 'ajv';
import { createGenerator } from 'ts-json-schema-generator';
import path from 'path';

const repoRoot = process.cwd();
const config = {
  path: path.join(repoRoot, "src", "types.ts"),
  tsconfig: path.join(repoRoot, "tsconfig.json"),
  type: "Person",
};

const schema = createGenerator(config).createSchema("Person")
const validate = (data: any) => {
  const ajv = new Ajv();
  const validateFn = ajv.compile(schema);
  return validateFn(data);
};

const person = {
  firstName: 'Alice',
  lastName: 'Chapman',
  age: 30,
  socials: ['github', 'twitter']
};

console.log(validate(person)); // true
```

In the above snippet, we defined a few configurations:

*   One to see the file path containing our TypeScript type definitions
*   Another for the file path to the `tsconfig.json` file
*   The last one to specify what type we want to generate JSON Schema for

Note that if we want to generate JSON Schema for all types and we have more than one type definition, we can use `*` as the value instead of `Person`. 

Using the `createSchema` method of the `SchemaGenerator` type, we create a JSON Schema object for the `Person` type and store it in the constant variable, `schema`. 

Then, within the `validate` function, we use the `compile` method on the `Ajv` object to compile the JSON Schema. We then use the resulting validator function — `validateFn` — to validate any data sample.

### Adding a middleware function to validate our data <a name="adding-middleware-function-validate-data">

To see a more practical use case, let’s modify the previous snippet by adding a middleware function to a web server. This middleware will validate the wire data against the existing JSON Schema we have:

```typescript
// --snip--
import express, { Request, Response, NextFunction } from 'express';
import { Person } from "./types";

const repoRoot = process.cwd();
const configType = "Person";
const config = {
  path: path.join(repoRoot, "src", "types.ts"),
  tsconfig: path.join(repoRoot, "tsconfig.json"),
  type: configType,
};

const schema = createGenerator(config).createSchema(configType);
const app = express();

app.use(express.json());

// Middleware to validate incoming payload against JSON Schema
const validatePayload = (req: Request, res: Response, next: NextFunction) => {
  const ajv = new Ajv();
  const validateFn = ajv.compile(schema);
  if (!validateFn(req.body)) {
    return res.status(400).json({ error: 'Invalid payload' });
  }
  next();
};

// Endpoint that accepts User payloads
app.post('/users', validatePayload, (req: Request, res: Response) => {
  const newUser = req.body as Person;
  // Do something with newUser
  res.json(newUser);
});

app.listen(3000, () => {
  console.log('Server running on port 3000');
});
```

The above snippet defined a middleware — `validatePayload` — that checks if the incoming payload conforms to the generated JSON Schema. If not, it returns a 400 error response. 

Then, within the `POST /users` endpoint, we use the middleware to verify that the wire data sent conforms to the schema definition. This way, we’re able to prevent errors that can happen as a result of unexpected payloads, thereby improving the overall reliability of the API. 

Start the web server using the below command:

```bash
npx ts-node .
```

Make sure to set the value of the `main` field in the `package.json` file to `src/index.ts`. We’ll use [Postman to test our implementation](https://blog.logrocket.com/how-automate-api-tests-postman/): ![Postman Interface Showing Test Calling Endpoint With Unexpected Request Resulting In 400 Error Response](http://blog.logrocket.com/wp-content/uploads/2023/07/Calling-endpoint-unexpected-request-resulting-400-error-response.png) In the above screenshot, we can see the result of calling our endpoint with an unexpected request body results in a `400` error response, as it should. Next, let’s send the expected payload to the endpoint: ![Postman Interface Showing Test Calling Endpoint With Expected Payload Resulting In 200 Status Response](http://blog.logrocket.com/wp-content/uploads/2023/07/Calling-endpoint-expected-payload-resulting-200-status-response.png) As we can see, we get the expected `200` status response, indicating a successful request.

## Conclusion

We looked at how to create JSON Schema from TypeScript types in this article. 

We can guarantee that our data follows a rigorous schema and take advantage of TypeScript's static type-checking by utilizing TypeScript types to build JSON Schema. This ensures that our data is both well-structured and type-safe. 

Generating JSON Schema from TypeScript types can help prevent errors both client-side and server-side, as any data that does not conform to the schema will be rejected. It also helps with documentation, as clients can easily see the expected structure of the data they need to send. 

Here’s the [GitHub repository](https://github.com/Ikeh-Akinyemi/json-schema) containing the full source files for the code examples in this article.

---

##[LogRocket](https://lp.logrocket.com/blg/typescript-signup): Full visibility into your web and mobile apps

[![LogRocket Signup](https://blog.logrocket.com/wp-content/uploads/2017/03/1d0cd-1s_rmyo6nbrasp-xtvbaxfg.png)](https://lp.logrocket.com/blg/typescript-signup)

[LogRocket](https://lp.logrocket.com/blg/typescript-signup) is a frontend application monitoring solution that lets you replay problems as if they happened in your own browser. Instead of guessing why errors happen, or asking users for screenshots and log dumps, LogRocket lets you replay the session to quickly understand what went wrong. It works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store.

In addition to logging Redux actions and state, LogRocket records console logs, JavaScript errors, stacktraces, network requests/responses with headers + bodies, browser metadata, and custom logs. It also instruments the DOM to record the HTML and CSS on the page, recreating pixel-perfect videos of even the most complex single-page and mobile apps.

[Try it for free.](https://lp.logrocket.com/blg/typescript-signup)