介绍
作为Fullstack开发人员,使用良好的错误处理构建强大的API端点是一项非常重要的技能。这篇文章将展示如何使用ZOD模式验证/声明库来制作固体API端点,并在几行代码中使用良好的错误反馈。
。什么是Zod?
Zod is a schema validation npm library used to check types at runtime。听起来很整洁,但是为什么我们需要这个呢?这不是什么打字稿是为了什么?很好的打字稿类型很棒,但是它们只能在开发过程中真正有所帮助。虽然Typescript类型确实提供了一些运行时检查,但它并不像ZOD那样全面。具体而言,Typescript类型仅验证预期属性的存在,而ZOD可以验证这些属性的值。
这是zod架构对象的一个示例:
const schema = z.object({
user: z.object({
firstName: z.string().min(2),
lastName:z.string().optional(),
age: z.int(),
email: z.string().email()
})
})
然后,我们可以使用Safeparse()函数使用此架构来处理一些数据
const data: unknown = ...
const parsedData = schema.safeParse(data);
if (parsedData.success) {
const safeData = parsedData.data
// safeData passes validation and is safe to use! ☑️
}
通过通过我们的ZOD架构对象解析数据,我们可以保证safeData
对象包含具有至少2个字符的firstName字符串属性,一个lastname属性(字符串或不定义),整数年龄和有效的用户电子邮件地址。所有这些都是用几行代码完成的。对于下一步,我们将研究如何在明确的JS端点上实现此目标。
将ZOD与Express一起使用
对于此演示,我们将使用中间件使用ZOD验证Express路线上的共同请求标头。在此中间件相同情况下的任何处理程序也将具有相同的验证。我们将通过为我们的验证制定架构来启动此任务。
模式
我们要确保每个请求都有一个带有gid-
前缀的标题guest-user-id
,最小长度为12个字符。
const requestSchema = z.object({
headers: z.object({
"guest-user-id": z.string().min(12).startsWith("gid-"),
})
});
中间件
接下来,我们要创建一个中间件功能,该功能检查架构的输入数据并在输入无效(无效)时返回400 (bad request)
。
app.use((req, res, next) => {
const input = requestSchema.safeParse(req);
if (!input.success) {
return res.status(400).send(input.error.issues);
}
res.locals = input;
next();
});
此功能将ZOD解析的所有问题发送为响应,概述了无效的参数以及原因。然后,我们在res.locals
中设置了清洁数据。这不会将任何数据发送回任何数据,但这只是在我们的请求生命周期中设置自定义数据的一种方式,可以在以后的任何时候拾取。
例如,如果我们以guest-user-id
标头设置为guest-123
的糟糕请求,我们将获得我们请求的所有问题的完整列表。这使调试问题非常容易。
[
{
"code": "too_small",
"minimum": 12,
"type": "string",
"inclusive": true,
"exact": false,
"message": "String must contain at least 12 character(s)",
"path": [
"headers",
"guest-user-id"
]
},
{
"code": "invalid_string",
"validation": {
"startsWith": "gid-"
},
"message": "Invalid input: must start with \"gid-\"",
"path": [
"headers",
"guest-user-id"
]
}
]
警告:在返回
input.error.issues
时,请勿在ZOD验证架构(如API代币)中包含敏感信息,因为请求者将能够查看为什么拒绝其请求,从而了解其敏感信息。这些检查应分别使用适当的错误代码(例如HTTP 401)分开处理,并且不应将ZOD错误发送回。
端点处理程序
最后,我们可以从res.locals
中访问处理程序上的安全输入,我们将其设置在中间件中。我们还可以通过将输入定义为ZOD架构的推断类型来使此输入打字条安全。
app.get("/", (req, res) => {
const input = res.locals as z.infer<typeof requestSchema>;
const guestUserId = input.headers["guest-user-id"];
return res.send({ message: `Your guest user ID is ${guestUserId}` });
});
现在都在一起了!
这是完整的端点,上述所有步骤都放在一起。如您所见,在不多的代码行中,我们有一个强大且强大的API。
// schema
const requestSchema = z.object({
headers: z.object({
"guest-user-id": z.string().min(12).startsWith("gid-"),
})
});
// middleware
app.use((req, res, next) => {
const input = requestSchema.safeParse(req);
if (!input.success) {
return res.status(400).send(input.error.issues);
}
res.locals = input;
next();
});
// endpoint handler
app.get("/", (req, res) => {
const input = res.locals as z.infer<typeof requestSchema>;
const guestUserId = input.headers["guest-user-id"];
return res.send({ message: `Your guest user ID is ${guestUserId}` });
});
老式的替代方案
如果我们想在没有ZOD的情况下实现相同的安全性,我们的中间件功能看起来像这样。这是验证一个属性的许多代码。如果我们有多个属性,您可以想象此验证功能将有多大!这种类型的验证容易出现错误,在处理多个属性时可能会很快变得笨拙,而ZOD提供了更简化且较少的错误解决方案。
router.use((req, res, next) => {
const guestUserId = req.headers.guestUserId;
if (typeof guestUserId !== "string") {
return res.status(400).send({ message: "guestUserId is missing" });
}
if (guestUserId.length < 12) {
return res.status(400).send({ message: "guestUserId is too short" });
}
if (!guestUserId.startsWith("gid-")) {
return res.status(400).send({ message: "guestUserId is invalid" });
}
res.locals = { guestUserId };
next();
});
概括
在这篇博客文章中,我们探索了如何使用打字稿优先架构验证库ZOD来验证Express API中的数据。我们研究了ZOD如何轻松地定义和验证数据模式,以及如何在开发过程的早期捕获错误。通过将ZOD与Express集成,我们可以确保只有我们的API接受有效数据,并且任何无效的数据都会在几行代码中使用信息的错误消息拒绝。
。验证数据是构建安全可靠的API的重要组成部分,ZOD提供了一种强大而便捷的方法。通过采用数据验证等最佳实践,我们可以构建更强大的应用程序并避免常见的安全漏洞。
如果您有兴趣了解有关构建安全可靠的软件be sure to check out my other article on hacking BeReal的更多信息。在其中,我们探索了Bereal的API端点,并拦截了上传我们喜欢的照片的请求。