Zod - TypeScript-first schema declaration and validation library #tips
Tags: programming, javascript, typescript, zod
Published: 2023-12-28 15:00:00

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/o9tpc9de5axl7avdf34j.jpg)

⭐ **Schema Validation with Zod and Express.js** ⭐

**Overview**

In the past, I have done articles on what is [Zod](https://zod.dev/), and how to use Zod to declare a validator once and compose simpler types into complex data structures.

**Today's example**

Today, I will write an article where we will create middleware to validate a specific route's schema.

The idea is quite simple, let's create a middleware that will receive a schema as a single argument and then validate it.

**Project setup**

As a first step, create a project directory and navigate into it:

```bash
mkdir zod-expressjs-sample
cd zod-expressjs-sample
```

Next, initialize an Express.js project and add the necessary dependencies:

```bash
npm init -y
npm install express zod
```

Now let's update some configs in our **package.json** file.

```json
{
  ...
  "type": "module",
  "main": "index.mjs",
  ...
}
```

**Let's code**

And now let's create a simple API:

```js
// Path: index.mjs

import express from 'express'
import cors from 'cors'

const app = express()

const port = 3000

app.use(cors())
app.use(express.json({ limit: '50mb' }))

app.get('/', (req, res) => {
  res.json({ message: 'Hello World!' })
})

app.listen(port, () => {
  console.log(`Example app listening on port ${port}`)
})

```

For the API to be initialized on port 3000 just run the following command:

```bash
node index.mjs
```

Now we can start working with Zod, and first, let's define our schema. In this example, we will only validate the response body. And let's hope the body contains two properties, the **fullName** and the **email**. This way:

```js
// Path: index.mjs

import express from 'express'
import cors from 'cors'
import zod from 'zod'

const app = express()

const port = 3000

app.use(cors())
app.use(express.json({ limit: '50mb' }))

const dataSchema = zod.object({
  body: zod.object({
    fullName: zod.string({
      required_error: 'Full name is required',
    }),
    email: zod
      .string({
        required_error: 'Email is required',
      })
      .email('Not a valid email'),
  }),
})

// ...
```

Now we can create our middleware. When the user calls our middleware, validate and receive schema validation in the arguments.

Finally, if it is properly filled in, we will go to the controller.

Otherwise, we will send an error message to the user.

```js
// Path: index.mjs

// ...

const validate = (schema) => async (req, res, next) => {
  try {
    await schema.parseAsync({
      body: req.body,
      query: req.query,
      params: req.params,
    })
    return next()
  } catch (error) {
    return res.status(400).json(error)
  }
}

// ...
```

Finally, we are going to create a route with the HTTP verb of POST type, which we will use our middleware to perform the validation of the body, and if successful, we will send the data submitted by the user.

```js
// Path: index.mjs

// ...

app.post('/create', validate(dataSchema), (req, res) => {
  return res.json({ ...req.body })
})

// ...
```

The final code of the example would be as follows:

```js
// Path: index.mjs

import express from 'express'
import cors from 'cors'
import zod from 'zod'

const app = express()

const port = 3000

app.use(cors())
app.use(express.json({ limit: '50mb' }))

const dataSchema = zod.object({
  body: zod.object({
    fullName: zod.string({
      required_error: 'Full name is required',
    }),
    email: zod
      .string({
        required_error: 'Email is required',
      })
      .email('Not a valid email'),
  }),
})

const validate = (schema) => async (req, res, next) => {
  try {
    await schema.parseAsync({
      body: req.body,
      query: req.query,
      params: req.params,
    })
    return next()
  } catch (error) {
    return res.status(400).json(error)
  }
}

app.post('/create', validate(dataSchema), (req, res) => {
  return res.json({ ...req.body })
})

app.listen(port, () => {
  console.log(`Example app listening on port ${port}`)
})
```

**Testing**

1️⃣ **Case 1: Blank object**

```json
{}
```

We get a response with the status code **400 Bad Request**:
```json
{
    "issues": [
        {
            "code": "invalid_type",
            "expected": "string",
            "received": "undefined",
            "path": [
                "body",
                "fullName"
            ],
            "message": "Full name is required"
        },
        {
            "code": "invalid_type",
            "expected": "string",
            "received": "undefined",
            "path": [
                "body",
                "email"
            ],
            "message": "Email is required"
        }
    ],
    "name": "ZodError"
}
```

2️⃣ **Case 2: fullName and email are empty**

```json
{
    "fullName": "",
    "email": ""
}
```

We get a response with the status code **400 Bad Request**:

```json
{
    "issues": [
        {
            "validation": "email",
            "code": "invalid_string",
            "message": "Not a valid email",
            "path": [
                "body",
                "email"
            ]
        }
    ],
    "name": "ZodError"
}
```

3️⃣ **Case 3: valid fullName, invalid email format**

```json
{
    "fullName": "Nhan Nguyen",
    "email": "sample@gmail"
}
```

We also get a response with the status code **400 Bad Request**:

```json
{
    "issues": [
        {
            "validation": "email",
            "code": "invalid_string",
            "message": "Not a valid email",
            "path": [
                "body",
                "email"
            ]
        }
    ],
    "name": "ZodError"
}
```

4️⃣ **Case 4: both fullName and email are valid**

```json
{
    "fullName": "Nhan Nguyen",
    "email": "sample@gmail.com"
}
```

We get a response with the status code **200 OK**:

```json
{
    "fullName": "Nhan Nguyen",
    "email": "sample@gmail.com"
}
```

**Conclusion**

As always, I hope you found it interesting. If you notice any errors in this article, please mention them in the comments. 🧑🏻‍💻

Hope you have a great day! 🤗

---

I hope you found it useful. Thanks for reading. 🙏

Let's get connected! You can find me on:

- **Medium:** https://medium.com/@nhannguyendevjs/
- **Dev**: https://dev.to/nhannguyendevjs/
- **Hashnode**: [https://nhannguyen.hashnode.dev](https://nhannguyen.hashnode.dev/)/
- **Linkedin:** https://www.linkedin.com/in/nhannguyendevjs/
- **X (formerly Twitter)**: https://twitter.com/nhannguyendevjs/
- **Buy Me a Coffee:** https://www.buymeacoffee.com/nhannguyen/