如何在Next.js API路径中添加TypeScript类型到请求体?

mtb9vblg  于 2023-02-08  发布在  TypeScript
关注(0)|答案(8)|浏览(218)

问题

我喜欢使用TypeScript的主要原因之一是检查是否向给定的函数调用传递了类型正确的参数。
但是,在使用Next.js时,我遇到了这样一个问题:传递给Next.js API端点的参数在被"降级"为NextApiRequest类型时最终会丢失它们的类型。
通常,我会使用类似req.body.[param_name]的参数,但整个链的类型为any,因此我丢失了任何有意义的类型信息。

示例

让我们假设我在pages/api/add.ts的Next.js项目中有一个API端点,负责两个数的相加,在这个文件中,我们还有一个类型化函数,用于两个数的相加,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!" });
  }
};

我遇到的问题,我希望得到帮助,是如何输入req.body.number_onereq.body.number_two或来自请求对象的任何类型的参数。这可能吗?
由于请求对象的类型为any,因此即使您尝试使用类型不正确的参数调用API端点,TypeScript也不会抱怨。

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

TypeScript编译器对上述API端点的调用不会有任何问题,即使类型不正确,它也会将body视为any类型,因此会丢失任何类型信息。
如果我可以输入从发送到Next.js中的API端点的请求主体转换而来的参数,那就太好了

bn31dyow

bn31dyow1#

您可以创建一个新接口,扩展NextApiRequest并添加两个字段的类型。

interface ExtendedNextApiRequest extends NextApiRequest {
  body: {
    number_one: number;
    number_two: number;
  };
}

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

export default async (req: ExtendedNextApiRequest, res: NextApiResponse) => {
    //...
};

虽然扩展NextApiRequest类型将阻止TypeScript抱怨,但它不能防止潜在的运行时错误发生。
要想找到一种更好的、类型安全的方法来缩小类型范围,请查看@Matthieu Gellé的答案。

e4eetjau

e4eetjau2#

胡利奥的回答很有效,但官方文件并不鼓励这样做:
使用TypeScript扩展req/res对象

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!" });
  }
};

我还没有修改你的代码,使您可以很容易地集成它,如果你有勇气来修改这个砖尽管事实上,它“工程”

ujv3wf0j

ujv3wf0j3#

只要做一个Type Guard并在你的处理程序中使用它。Matthieu的答案很棒,但是当有很多期望的属性时就很糟糕了。
当你发现自己要处理5个以上的属性时,检查通过主体发送的东西是否是某种类型的东西可能会花费相当多的时间。如果这些属性是嵌套的多层,那就更麻烦了。只要使用合适的验证器并为它们编写模式就行了。
为此,正如Matthieu所指出的,不应该通过覆盖现有属性来扩展NextApiRequestNextApiResponse,而应该扩展它们以添加其他属性。
相反,我会编写一个泛型,如下所示:

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

并像这样使用它:

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
}
  • 引用:* 使用类型 predicate
vql8enpb

vql8enpb4#

注:我是Remult的维护者之一
如果可以接受另一种依赖关系,那么使用Remult时,可以对所有API调用使用类型安全代码(CRUD用于TypeScript模型,RPC用于函数)。
add函数如下所示:

// utils.ts
import { BackendMethod } from "remult";

export class ApiUtils {
   @BackendMethod({ allowed: true })
   static async add(a: number, b: number) {
      return a + b;
   }
}

你可以在前端导入它并直接调用它

// App.tsx
import { ApiUtils } from 'utils'

alert(await ApiUtils.add(1,2));

Remult将发送POST请求,在Next.js API路由上处理它,并将值传递给后端的add函数。
如果您想进一步了解,可以使用Next.js tutorial

yrwegjxp

yrwegjxp5#

虽然有点晚了,但是我在请求查询参数方面遇到了类似的问题。我已经解决了这个问题,并且我相信它仍然是类型安全的。只是在 intended available参数中添加了一些自动完成和代码级文档:https://github.com/vercel/next.js/discussions/36373
应易于修改,以支持身体以及

mutmk8jj

mutmk8jj6#

这里的挑战是,您既需要一个解决方案来确保类型安全,又需要对数据进行运行时验证。正如@dvlden所指出的,使用类型保护将提供这两种功能,这是实现这一目标的常见做法。然而,在更复杂的场景中,它可能会相当麻烦。
另一个可以帮助OP的dilema的工具是Zod, a TypeScript-first schema validation library,它可以确保你的数据具有正确的形状,如果是这样,也提供正确的类型。

const Body = z.object({
    number_one: z.number(),
    number_two: z.number(),
});

type Body = z.infer<typeof Body>;

export default async (req: NextApiRequest, res: NextApiResponse) => {
    const result = Body.safeParse(req.body);
    if (result.success) {
        console.log(result.data) // type is Body, TS is happy
    });
    // ...
}

我过去使用过Elm,Zod的方法与Elm的非常相似,对于每个HTTP请求,您必须提供一个类型正确的JSON decoder that parses the input data值,如果不能成功,则返回一个错误。我提到这一点是因为我认为Elm的实现在简单性和安全性方面是一个标准。

zzzyeukh

zzzyeukh7#

我认为我们可以在使用前使用express-validator验证主体。
然后,可以从使用TypeScript扩展req/res对象中使用一个简单的方法

type ExtendedRequest = NextApiRequest & {
  body: {
    number_one: number;
    number_two: number;
  };
}
wwtsj6pe

wwtsj6pe8#

也有这个问题!最后定义了body对象,并将其传递到fetch中,其中包含一个接口,用于API中的req body
在你的例子中--

pages/api/add.ts

    export interface addBody {
       number_one:number;
       number_two:number
    }

    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!" });
      }
    };

还有你所谓的“取”

import { addBody } from "pages/api/add.ts";

    const body:addBody = { number_one: 1, number_two: "two" }

    fetch("/api/add", {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
      },
      body: JSON.stringify(body),
    });

最后为我工作了,当我没有所有想要的参数时, typescript 抛出了一个错误。希望这能有所帮助!

相关问题