用ZOD构建Bulletproof ExpressJS API
#网络开发人员 #typescript #node #expressjs

介绍

作为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端点,并拦截了上传我们喜欢的照片的请求。