摘要:在本教程中,您将学习如何使用第三方软件包 Zod 来验证请求的正文、参数和查询字符串。
Express 应用程序通常从客户端接收数据,但您不应该信任这些数据,尤其是在数据来自不可信来源时。
为了使您的应用程序保持安全,在处理输入数据之前始终验证和清理输入数据至关重要。验证的流行库之一是 Zod。
在本教程中,我们将向您展示如何使用 Zod 来验证路由的输入数据,包括请求参数、查询字符串和正文。
我们将继续使用从 Express 路由器教程 中开发的项目。
首先,下载项目 并解压它。
其次,通过在项目目录中运行以下命令,在您的终端中安装 Zod
npm install zod
Code language: JavaScript (javascript)
验证参数
以下是通过 id 检索 todo 项的路由 GET todos/:id
router.get('/:id', (req, res) => {
const { id } = req.params;
res.send(`Get the todo item with id ${id}`);
});
Code language: JavaScript (javascript)
如果您发送以下请求
GET http://localhost:3000/todos/abc
Code language: JavaScript (javascript)
… 您将获得以下响应
Get the todo item with id abc
Code language: JavaScript (javascript)
这是错误的,因为 id 应该是正整数。因此,我们需要验证 id 以防止路由获得错误数据。
添加 Zod 验证
以下显示了如何使用 Zod 库来验证 id 参数
router.get('/:id', (req, res) => {
const schema = z.object({
id: z.coerce.number().int().positive(),
});
try {
const result = schema.parse(req.params);
req.params = result;
} catch (err) {
if (err instanceof ZodError) {
return res.status(400).json({ error: 'Invalid data', details: err });
}
// handle other errors
return res.status(500).json({ error: 'Internal Server Error' });
}
const { id } = req.params;
res.send(`Get the todo item with id ${id}`);
});
Code language: JavaScript (javascript)
工作原理
首先,从 zod
库导入 z
和 ZodError
对象
import { z, ZodError } from 'zod';
Code language: JavaScript (javascript)
其次,定义一个模式来验证 id 字段
const schema = z.object({
id: z.coerce.number().int().positive(),
});
Code language: JavaScript (javascript)
在这个模式中,我们将 id 转换为数字,并确保它是正整数。
第三,调用模式对象的 parse()
方法来解析来自请求对象的参数数据
const result = schema.parse(req.params);
Code language: JavaScript (javascript)
如果验证通过,parse()
方法将返回在模式中定义类型的 value。更具体地说,它将返回 id 作为正整数。
第四,使用来自 parse()
方法结果的 value 来更新 request
对象的 params
属性
req.params = result;
Code language: JavaScript (javascript)
第五,如果验证失败,parse()
方法将抛出一个类型为 ZodError
的错误。我们可以使用 try…catch 语句捕获错误,并更具体地处理 ZodError
if (err instanceof ZodError) {
return res.status(400).json({ error: 'Invalid data', details: err });
}
Code language: JavaScript (javascript)
最后,如果发生其他类型的错误,则向客户端返回内部服务器错误
return res.status(500).json({ error: 'Internal Server Error' });
Code language: JavaScript (javascript)
测试 Zod 验证
以下请求返回一个错误,因为 id 不是数字
GET http://localhost:3000/todos/abc
Code language: JavaScript (javascript)
错误
{
"error": "Invalid data",
"details": {
"issues": [
{
"code": "invalid_type",
"expected": "number",
"received": "nan",
"path": [
"id"
],
"message": "Expected number, received nan"
}
],
"name": "ZodError"
}
}
Code language: JavaScript (javascript)
以下请求返回一个错误,因为 id 不是正整数
GET http://localhost:3000/todos/0
Code language: JavaScript (javascript)
错误
{
"error": "Invalid data",
"details": {
"issues": [
{
"code": "too_small",
"minimum": 0,
"type": "number",
"inclusive": false,
"exact": false,
"message": "Number must be greater than 0",
"path": [
"id"
]
}
],
"name": "ZodError"
}
}
Code language: JavaScript (javascript)
以下请求是有效的,并返回 HTTP 状态代码 200
GET http://localhost:3000/todos/1
Code language: JavaScript (javascript)
验证按预期工作,但代码有点冗长。它还将验证逻辑与路由处理程序的逻辑混合在一起,这使得难以维护。
为了改进这一点,我们可以通过创建一个名为 validate
的中间件函数并像这样在路由中使用它来进行重构
router.get('/:id', validate({ params: todoIdSchema }), (req, res) => {
const { id } = req.params;
res.send(`Get the todo item with id ${id}`);
});
Code language: JavaScript (javascript)
创建 Express 的 Zod 中间件
步骤 1. 在项目目录中创建一个名为 middleware 的新目录
mkdir middleware
Code language: JavaScript (javascript)
步骤 2. 在 middleware
目录中创建一个名为 validation.js
的新文件,其中包含以下代码
import { ZodError } from 'zod';
export const validate = (schemas) => {
return (req, res, next) => {
for (const [key, schema] of Object.entries(schemas)) {
try {
// parse the input data
const result = schema.parse(req[key]);
req[key] = result;
next();
} catch (err) {
// handle validation error
if (err instanceof ZodError) {
return res.status(400).json({ error: 'Invalid data', details: err });
}
// handle other errors
return res.status(500).json({ error: 'Internal Server Error' });
}
}
};
};
Code language: JavaScript (javascript)
工作原理。
首先,从 zod
库导入 ZodError
对象
import { ZodError } from 'zod';
Code language: JavaScript (javascript)
其次,定义一个名为 validate
的函数,该函数验证请求数据,包括正文、参数和查询字符串
export const validate = (schemas) => {
Code language: JavaScript (javascript)
validate()
函数接受一个 schemas
参数,该参数是一个包含请求对象中可用键的对象
- body
- params
- query
每个键的值都是一个 Zod 模式,它定义了验证规则。
以下示例显示了一个 schemas
参数,它可用于验证 todo
资源的查询字符串和请求的正文
{
query: z.object({
id: z.coerce.number().int().positive(),
}),
body: z.object({
title: z.string(),
completed: z.boolean(),
}),
};
Code language: JavaScript (javascript)
在这个例子中
- 验证查询字符串是否包含作为正整数的 id。
- 验证正文是否包含类型为字符串的 title 和类型为布尔值的 completed。
第三,从 validate()
函数返回一个中间件函数
export const validate = (schemas) => {
return (req, res, next) => {
Code language: JavaScript (javascript)
第四,遍历键值对,每个键值对都包含在 schemas
参数中定义的键和模式
for (const [key, schema] of Object.entries(schemas)) {
Code language: JavaScript (javascript)
第五,解析来自 key
的请求数据,它可以是请求对象的 body
、params
或 query
属性
const result = schema.parse(req[key]);
Code language: JavaScript (javascript)
第六,如果验证通过,则更新请求的数据并调用 next()
中间件函数
req[key] = result;
next();
Code language: JavaScript (javascript)
第七,如果验证失败,解析将抛出一个 ZodError
,我们可以在 catch
块中更具体地处理它
} catch (err) {
// handle validation error
if (err instanceof ZodError) {
return res.status(400).json({ error: 'Invalid data', details: err });
}
Code language: JavaScript (javascript)
最后,如果发生其他错误,则返回内部服务器错误
return res.status(500).json({ error: 'Internal Server Error' });
Code language: JavaScript (javascript)
定义 Zod 模式
步骤 1. 在项目目录中创建一个名为 schemas 的目录
mkdir schemas
Code language: JavaScript (javascript)
步骤 2. 定义 Zod 模式以验证 todo 对象和 todo Id
import { z } from 'zod';
export const todoSchema = z.object({
title: z.string(),
completed: z.boolean(),
});
export const todoIdSchema = z.object({
id: z.coerce.number().int().positive(),
});
Code language: JavaScript (javascript)
使用 Zod express 中间件
修改 routes 目录中的 routes/todo.js
,以使用 middleware/validation.js
模块中的 validate 函数和 schema/todo.js
模块中的 Zod 模式
import express from 'express';
import { validate } from '../middleware/validation.js';
import { todoSchema, todoIdSchema } from '../schemas/todos.js';
const router = express.Router();
router.get('/', (req, res) => {
res.send('Get all todo items');
});
router.post('/', validate({ body: todoSchema }), (req, res) => {
res.send('Create a new todo item');
});
router.put(
'/:id',
validate({ params: todoIdSchema, body: todoSchema }),
(req, res) => {
const { id } = req.params;
res.send(`Update the todo item with id ${id}`);
}
);
router.delete('/:id', validate({ params: todoIdSchema }), (req, res) => {
const { id } = req.params;
res.send(`Delete the todo item with id ${id}`);
});
router.get('/:id', validate({ params: todoIdSchema }), (req, res) => {
const { id } = req.params;
res.send(`Get the todo item with id ${id}`);
});
export default router;
Code language: JavaScript (javascript)
工作原理。
首先,从 '../middleware/validation.js'
导入 validate
函数,以及从 '../schemas/todos.js'
模块导入 todoSchema
和 todoIdSchema
import { validate } from '../middleware/validation.js';
import { todoSchema, todoIdSchema } from '../schemas/todos.js';
Code language: JavaScript (javascript)
其次,根据 todoSchema 验证路由 GET /todos 的请求正文
router.post('/', validate({ body: todoSchema }), (req, res) => {
console.log(req.body);
res.send('Create a new todo item');
});
Code language: JavaScript (javascript)
如果您传递一个没有 title 或 completed 字段且具有有效值的 object,它将返回一个错误。
例如,如果您进行以下请求
POST http://localhost:3000/todos
Content-Type: application/json
{
}
Code language: JavaScript (javascript)
… 您将获得一个带有 400 HTTP 状态代码和错误详细信息的响应
{
"error": "Invalid data",
"details": {
"issues": [
{
"code": "invalid_type",
"expected": "string",
"received": "undefined",
"path": [
"title"
],
"message": "Required"
},
{
"code": "invalid_type",
"expected": "boolean",
"received": "undefined",
"path": [
"completed"
],
"message": "Required"
}
],
"name": "ZodError"
}
}
Code language: JavaScript (javascript)
原因是 title 和 completed 都不可用在请求的正文中。
第三,根据 todoIdSchema
和 todoSchema
来验证路由 PUT todos/:id
router.put(
'/:id',
validate({ params: todoIdSchema, body: todoSchema }),
(req, res) => {
const { id } = req.params;
res.send(`Update the todo item with id ${id}`);
}
);
Code language: JavaScript (javascript)
这确保 id 是正整数,并且正文包含 title 和 completed 字段,且具有有效值。
第四,验证路由 DELETE todos/:id
的 id,以确保 id 是正整数
router.delete('/:id', validate({ params: todoIdSchema }), (req, res) => {
const { id } = req.params;
res.send(`Delete the todo item with id ${id}`);
});
Code language: JavaScript (javascript)
最后,验证路由 GET todos/:id
的 id
router.get('/:id', validate({ params: todoIdSchema }), (req, res) => {
const { id } = req.params;
res.send(`Get the todo item with id ${id}`);
});
Code language: JavaScript (javascript)
下载项目源代码
总结
- 使用 Zod 库来验证请求数据。
- 创建一个中间件函数以使代码更简洁。