如何在 Next.js API 路由中将 TypeScript 类型添加到请求正文?

Mic*_*ras 29 typescript next.js

问题

我喜欢使用 TypeScript 的主要原因之一是检查我是否将正确类型的参数传递给给定的函数调用。

但是,在使用 Next.js 时,我遇到了这样的问题:传递到 Next.js API 端点的参数在“降级”为该类型时最终会丢失其类型NextApiRequest

通常,我会执行类似的操作req.body.[param_name],但整个链都有类型any,因此我会丢失任何有意义的类型信息。

例子

假设我在 Next.js 项目中有一个 API 端点,pages/api/add.ts负责将两个数字相加。在此文件中,我们还有一个用于添加两个数字的类型化函数,API 端点将调用该函数。

const add = (a: number, b: number): number => a + b;

export default async (req: NextApiRequest, res: NextApiResponse) => {
  try {
    const result = add(req.body.number_one, req.body.number_two);
    res.status(200).json(result);
  } catch (err) {
    res.status(403).json({ err: "Error!" });
  }
};
Run Code Online (Sandbox Code Playgroud)

我遇到的问题以及我想要帮助的问题是如何键入req.body.number_onereq.body.number_two/或来自请求对象的任何类型的参数。这可能吗?

由于请求对象的类型是anyTypeScript 不会抱怨,即使您尝试使用错误类型的参数调用 API 端点。

fetch("/api/add", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ number_one: 1, number_two: "two" }),
});
Run Code Online (Sandbox Code Playgroud)

即使类型不正确,TypeScript 编译器对上述 API 端点的调用也不会出现问题。它将 视为body一种any类型,因此它会丢失任何类型信息。

如果我可以输入从发送到 Next.js 中的 API 端点的请求正文中转换的参数,那就太好了

jul*_*ves 39

您可以创建一个新界面来扩展NextApiRequest并添加这两个字段的类型。

\n
interface ExtendedNextApiRequest extends NextApiRequest {\n  body: {\n    number_one: number;\n    number_two: number;\n  };\n}\n
Run Code Online (Sandbox Code Playgroud)\n

req然后用它在处理函数中输入对象。

\n
export default async (req: ExtendedNextApiRequest, res: NextApiResponse) => {\n    //...\n};\n
Run Code Online (Sandbox Code Playgroud)\n
\n

虽然扩展NextApiRequest类型可以阻止 TypeScript 发出抱怨,但它并不能阻止潜在的运行时错误的发生。

\n

要获得更好的类型安全方法来缩小类型范围,请查看@Matthieu Gell\xc3\xa9\ 的答案

\n


dvl*_*den 16

只需制作一个类型防护并在您的处理程序中使用它即可。马蒂厄的答案很好,但当有许多预期的属性时,它就很令人讨厌。

当您发现自己要检查 5 个以上的属性时,检查通过身体发送的东西是否属于某种类型可能会花费相当多的时间。如果这些是多层嵌套的,那就更令人头痛了。只需使用适当的验证器并为它们编写模式即可。

为此,正如 Matthieu 所指定的,您不应该通过覆盖现有属性来扩展NextApiRequest它们NextApiResponse,而只能扩展它们以添加其他属性。

相反,我会写一个像这样的泛型:

function isValidBody<T extends Record<string, unknown>>(
  body: any,
  fields: (keyof T)[],
): body is T {
  return Object.keys(body).every((key) => fields.includes(key))
}
Run Code Online (Sandbox Code Playgroud)

并像这样使用它:

type RequestBody = {
  id: string
}

async function handler(req: NextApiRequest, res: NextApiResponse) {
  if (!isValidBody<RequestBody>(req.body, ['id'])) {
    return res.status(402)
  }

  // req.body.id - is expected to be a string down the road
}
Run Code Online (Sandbox Code Playgroud)

参考: 使用类型谓词

  • 但这实际上并没有检查每个键的类型。`RequestyBody.id` 可以是字符串、数字、对象等,并且 isValidBody 为 true。 (3认同)

小智 14

julio的答案有效,但官方文档并不鼓励这样做:
Extending the req/res objects with TypeScript

const add = (a: number, b: number): number => a + b;

export default async (req: NextApiRequest, res: NextApiResponse) => {
  const { body } = req;
  try {
    if (
      "number_one" in body &&
      typeof body.number_one === "number" &&
      "number_two" in body &&
      typeof body.number_two === "number"
    ) {
      const result = add(body.number_one, body.number_two);
      return res.status(200).json(result);
    }
    throw new Error("number_one or number_two is not a number");
  } catch (err) {
    return res.status(403).json({ err: "Error!" });
  }
};
Run Code Online (Sandbox Code Playgroud)

我没有对你的代码进行太多修改,以便你可以轻松地集成它,如果你有勇气来修改这个砖块,尽管它“有效”


Jer*_*yal 5

如果您还想types添加NextApiResponse

import type { NextApiRequest, NextApiResponse } from 'next'

type Data = {
  name: string
}

export default function handler(
  req: NextApiRequest,
  res: NextApiResponse<Data>
) {
  res.status(200).json({ name: 'John Doe' })
}

Run Code Online (Sandbox Code Playgroud)