Zod is a schema validation library, in other words it helps us to create schemas and compare that specific objects are valid using the schema as rules. We can take advantage of this to validate incoming requests payloads
First we need to create a new Typescript node project
mkdir zod-validation-typescript
cd zod-validation-typescript
npm init -y
This is a list of all the packages needed and the reason why we will install them:
"express" (npm install express): Simplifies the creation of RESTful APIs
using Node
``` mechanism that only allow access to our resources for specified domains
"zod" (npm install zod): Typescript-first Schema declaration and validation
using Node
"dotenv" (npm install dotenv): Loads environment variables from a .env
"typescript" (npm install -D typescript): TypeScript adds optional types to JavaScript that support tools for large-scale JavaScript applications for any browser
"ts-node" (npm install -D ts-node): Transforms TypeScript into JavaScript
"@types/express" (npm install --save @types/express): Types definition for express
npm i express zod dotenv
npm i --save-dev typescript ts-node @types/express
Next we will create a tsconfig.json file
npx tsc --init
Change the tsconfig.json file that was created
{
"compilerOptions": {
"target": "es6",
"module": "commonjs",
"declaration": true,
"sourceMap": true,
"outDir": "dist",
"rootDir": "./",
"strict": true,
"esModuleInterop": true
}
}
Now change the package.json file
{
"name": "zod-validation-typescript",
"version": "1.0.0",
"description": "",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"scripts": {
"start": "ts-node index.ts",
"build": "tsc"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"dotenv": "^16.3.1",
"express": "^4.18.2",
"zod": "^3.22.4"
},
"devDependencies": {
"@types/express": "^4.17.18",
"ts-node": "^10.9.1",
"typescript": "^5.2.2"
}
}
Next we are going to create an index.ts file in the base of the project, we will add some routes that need validation
// ./index.ts
import 'dotenv/config'
import express, { Application, Request, Response } from "express";
import {validateRequest} from "./src/middlewares/validator";
import {bodyParamsValidator, queryParamsValidator, queryStringValidator} from "./src/validators";
const port = 3015;
// New express application instance
const app: Application = express();
// Allow json in body params
app.use(express.json({ limit: "250mb" }))
/**
* Route using query string
*/
app.get("/query-string", [validateRequest(queryStringValidator)], (req: Request, res: Response) => {
/**
* Get variables from query string
*/
const { name, lastName, age, status } = req.query
res.status(200).send({ name, lastName, age, status })
})
/**
* Route using body params
*/
app.post("/body-params", [validateRequest(bodyParamsValidator)], (req: Request, res: Response) => {
/**
* Get variables from body params
*/
const { email, password } = req.body
res.status(200).send({ email, password })
})
/**
* Route using query params
*/
app.get("/query-params/:userId/:bookId", [validateRequest(queryParamsValidator)], (req: Request, res: Response) => {
/**
* Get variables from query params
*/
const { userId, bookId } = req.params
res.status(200).send({ userId, bookId })
})
/**
* Begin listening on port selected
*/
app.listen(port, () => {
console.log(`Server is running on port ${port}...`);
});
Now we are going to create a new directory on the base of the project called src, inside of this new directory create another one called validators, inside this new folder create file called index.ts
// ./src/validators/index.ts
import { z } from "zod";
export enum UserStatus {
ACTIVE = "active",
DELETED = "deleted"
}
export const queryStringValidator = z.object({
query: z.object({
// Name is required and must have 4 or more characters
name: z.string({
required_error: "Name is required",
}).min(4, "Name must be bigger than 4 characters"),
// Last name is optional
lastName: z.string().optional(),
// Age is required, we expect a number but, because we are receiving values from query string all will be stirngs
age: z.string({
required_error: "Age is required",
}),
// Status is required and must one of the value defined in the enum
status: z.nativeEnum(UserStatus, { required_error: "Status is required", invalid_type_error: "Status is not valid" }),
}),
});
export const bodyParamsValidator = z.object({
body: z.object({
// Email is required and must have email format
email: z.string({
required_error: "Email is required",
}).email("Email is not valid"),
// Password is required and must have 8 or more characters
password: z.string({ required_error: "Password is required" }).min(8, "Password must be minimum 8 characters long")
}),
});
export const queryParamsValidator = z.object({
params: z.object({
// User id is required
userId: z.string({
required_error: "User id is required",
}),
// Book id is required
bookId: z.string({
required_error: "Book id is required",
}),
}),
});
Now we are going to create a middleware that will validate that the requests have all the fields needed, before we hit the routes logic. Create a new directory inside of src called middlewares, inside of this new folder create a file called validator.ts
// ./src/middlewares/validator.ts
import {AnyZodObject, ZodError} from "zod";
import { Request, Response, NextFunction } from "express";
/**
* desc validates that the request query, params and body are valid
* @param validator the Zod validation that will be used to validate the request
*/
export const validateRequest = (validator: AnyZodObject) => async (req: Request, res: Response, next: NextFunction) => {
try {
// We use parse to validate the request is valid
await validator.parseAsync({
body: req.body,
query: req.query,
params: req.params,
});
// Validation was successfully continue
next();
} catch (error) {
// If error is instance of ZodError then return error to client to show it to user
if (error instanceof ZodError) {
return res.status(400).send({ msg: error.issues[0].message } );
}
// If error is not from zod then return generic error message
return res.status(500).send("Error making request, contact support" );
}
};
Now start the server, and try to access any of the specified routes you will get the error messages if the request is not valid
npm run start
In this repository https://github.com/obravocedillo/ZodValidation you can find the code.
In this link https://documenter.getpostman.com/view/11491178/2s9YJhwKV2 you can find the Postman documentation with all the links and required params.