Validating Request Data in Express.js using Zod and TypeScript: A Comprehensive Guide
Tags: node, beginners, api, typescript
Published: 2024-01-15 19:49:00

## Introduction

Express.js is a popular web framework for building Node.js applications, and validation is a crucial aspect of handling incoming data securely. In this article, we'll explore how to create a generic validation middleware using Zod, a TypeScript-first schema declaration and validation library. This middleware can be reused across different routes to ensure that incoming data adheres to predefined schemas.

## Prerequisites

Before diving into the implementation, make sure you have the following prerequisites installed:

- Node.js and npm
- TypeScript
- Express.js
- Zod

Install the necessary packages by running:

```bash
npm install express body-parser zod
```

## Directory Structure

To keep our code organized, we'll structure our project as follows:

```plaintext
project-root
|-- src
|   |-- routes
|   |   |-- userRoutes.ts
|   |
|   |-- middleware
|   |   |-- validationMiddleware.ts
|   |
|   |-- schemas
|   |   |-- userSchemas.ts
|   |
|   |-- index.ts
|
|-- package.json
|-- tsconfig.json
|-- ...
```

## Implementing Generic Validation Middleware

### Step 1: Set Up Express App

In your `index.ts` file, set up an Express app and configure middleware:

```typescript
// src/index.ts
import express from 'express';
import bodyParser from 'body-parser';
import userRouter from './routes/userRoutes';

const app = express();
const PORT = 3000;

app.use(bodyParser.json());
app.use('/api/user', userRouter);

app.listen(PORT, () => {
  console.log(`Server is running on http://localhost:${PORT}`);
});
```

### Step 2: Create Zod Schemas

Define Zod schemas for different sections in your `userSchemas.ts` file:

```typescript
// src/schemas/userSchemas.ts
import { z } from 'zod';

export const userRegistrationSchema = z.object({
  username: z.string(),
  email: z.string().email(),
  password: z.string().min(8),
});

export const userLoginSchema = z.object({
  username: z.string(),
  password: z.string().min(8),
});
```

### Step 3: Build Generic Validation Middleware

Create a generic validation middleware in `validationMiddleware.ts`:

```typescript
// src/middleware/validationMiddleware.ts
import { Request, Response, NextFunction } from 'express';
import { z, ZodError } from 'zod';

import { StatusCodes } from 'http-status-codes';

export function validateData(schema: z.ZodObject<any, any>) {
  return (req: Request, res: Response, next: NextFunction) => {
    try {
      schema.parse(req.body);
      next();
    } catch (error) {
      if (error instanceof ZodError) {
      const errorMessages = error.errors.map((issue: any) => ({
            message: `${issue.path.join('.')} is ${issue.message}`,
        }))
        res.status(StatusCodes.BAD_REQUEST).json({ error: 'Invalid data', details: errorMessages });
      } else {
        res.status(StatusCodes.INTERNAL_SERVER_ERROR).json({ error: 'Internal Server Error' });
      }
    }
  };
}
```

### Step 4: Utilize the Generic Validation Middleware in Routes

In your `userRoutes.ts`, use the generic middleware for different routes:

```typescript
// src/routes/userRoutes.ts
import express from 'express';
import { validateData } from '../middleware/validationMiddleware';
import { userRegistrationSchema, userLoginSchema } from '../schemas/userSchemas';

const userRouter = express.Router();

import { registerUser, loginUser } from './userController';

userRouter.post('/register', validateData(userRegistrationSchema), registerUser);
userRouter.post('/login', validateData(userLoginSchema), loginUser);

export default userRouter;
```

### Step 5: Implement User Controller Logic

Create a user controller in `userController.ts` to handle user registration and login:

```typescript
// src/routes/userController.ts
import { Request, Response } from 'express';

export const registerUser = (req: Request, res: Response) => {
  // Handle user registration logic using validated data from req.body
  res.json({ message: 'User registered successfully', data: req.body });
};

export const loginUser = (req: Request, res: Response) => {
  // Handle user login logic using validated data from req.body
  res.json({ message: 'User logged in successfully', data: req.body });
};
```

## Conclusion

Congratulations! You've successfully implemented a generic validation middleware using Zod in your Express.js application. This approach allows you to create reusable validation logic for different sections of your application, promoting maintainability and scalability.

Feel free to customize the schemas and middleware according to your specific validation requirements. This structure not only enhances the readability of your code but also facilitates easier testing and code maintenance.