چگونه به بدنه درخواست در مسیر API در Next.js نوعهای TypeScript اضافه کنیم؟
مشکل
یکی از دلایل اصلی که من از تایپاسکریپت استفاده میکنم این است که بررسی کنم پارامترهای با نوع صحیح را به فراخوانیهای تابع داده شده پاس میکنم.
با این حال، هنگام استفاده از Next.js، من با مشکلی روبرو میشوم که در آن پارامترهایی که به یک نقطه پایانی API در Next.js ارسال میشوند، هنگام «تنزل» به نوع NextApiRequest، نوع خود را از دست میدهند.
به طور معمول، من پارامترها را با کاری شبیه به req.body.[param_name] دریافت میکنم، اما کل این زنجیره نوع any دارد، بنابراین هر گونه اطلاعات نوع معنادار را از دست میدهم.
نمونه
بیایید فرض کنیم که من یک نقطه پایانی API در یک پروژه Next.js دارم در 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!" });
}
};
مشکلی که من با آن مواجه هستم و میخواهم در مورد آن کمک بگیرم این است که چگونه req.body.number_one و req.body.number_two یا هر نوع پارامتری که از شیء درخواست میآید را تایپ کنم. آیا این امکانپذیر است؟
از آنجایی که نوعهای شیء درخواست any هستند، تایپاسکریپت هیچ اخطاری نمیدهد، حتی اگر سعی کنید با پارامترهای تایپشده نادرست، به نقطه پایانی API فراخوانی کنید.
fetch("/api/add", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ number_one: 1, number_two: "two" }),
});
کامپایلر TypeScript با فراخوانی فوق به نقطه پایان API مشکلی نخواهد داشت، حتی اگر نوعها نادرست باشند. این مقدار body را به عنوان نوع any در نظر میگیرد، بنابراین هرگونه اطلاعات نوعی را از دست میدهد.
عالی میشد اگر میتوانستم پارامترهای cast شده از بدنهٔ درخواست که به نقطههای پایانی API در Next.js ارسال میشوند را تایپ کنم
پاسخ شماره ۱
شما میتوانید یک رابط جدید ایجاد کنید که NextApiRequest را گسترش دهد و تایپهای دو فیلد را اضافه کند.
interface ExtendedNextApiRequest extends NextApiRequest {
body: {
number_one: number;
number_two: number;
};
}
سپس از آن برای تایپ کردن شیء req در تابع handler استفاده کنید.
export default async (req: ExtendedNextApiRequest, res: NextApiResponse) => {
//...
};
در حالی که با گسترش نوع NextApiRequest، TypeScript از شکایت کردن باز میایستد، این کار از رخ دادن خطاهای احتمالی در زمان اجرا جلوگیری نمیکند.
برای یک رویکرد بهتر و ایمن که به جای آن انواع را محدود می کند، پاسخ @Matthieu Gelle را بررسی کنید.
پاسخ شماره ۲
پاسخ جولیو کار میکند اما توسط مستندات رسمی توصیه نمیشود :
گسترش اشیاء req/res با 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!" });
}
};
من کد شما را زیاد تغییر ندادهام تا بتوانید به راحتی آن را یکپارچه کنید، اگر جرأت دارید بیایید و این بلوک را با وجود این که «کار میکند» تغییر دهید
پاسخ شماره ۳
فقط یک Type Guard بسازید و آن را در داخل هندلر خود استفاده کنید. پاسخ متیو عالی است اما وقتی تعداد زیادی ویژگی مورد انتظار دارید، ناخوشایند است.
بررسی اینکه آیا چیزی که از طریق بدن ارسال شده از نوع خاصی است میتواند زمان زیادی ببرد هنگامی که خود را در حال بررسی بیش از ۵ ویژگی مییابید. بدتر از آن، سردردهای بیشتری اگر آنها چندین سطح تو در تو باشند. فقط از اعتبارسنجیهای مناسب استفاده کنید و برای آنها اسکیمها بنویسید.
برای این منظور، همانطور که متیو مشخص کرد، نباید 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))
}
و اینطور از آن استفاده کنید:
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
}
مرجع: استفاده از پیشبینهای نوع
