如何在Next.js API路由中为请求体参数添加TypeScript类型?
Great question! Type safety for Next.js API routes is definitely achievable—we just need to add explicit type definitions for request bodies both on the backend (API route) and frontend (fetch calls). Here's a step-by-step solution to fix your issue:
Backend: Type-Safe API Route
First, let's fix the API route so TypeScript recognizes the structure of req.body and validates it at runtime (since TypeScript types disappear after compilation).
1. Define a Request Body Type
Start by creating a TypeScript type for your request body:
type AddRequestBody = { number_one: number; number_two: number; };
2. Add a Runtime Type Guard
To ensure the incoming request actually matches your type (since frontend could send invalid data), use a type guard function. This tells TypeScript to narrow the type of req.body once validation passes:
function isValidAddRequestBody(body: unknown): body is AddRequestBody { return ( typeof body === 'object' && body !== null && 'number_one' in body && typeof (body as AddRequestBody).number_one === 'number' && 'number_two' in body && typeof (body as AddRequestBody).number_two === 'number' ); }
3. Update Your API Route
Use the type guard in your API route to validate the request body before processing it:
import type { NextApiRequest, NextApiResponse } from 'next'; type AddRequestBody = { number_one: number; number_two: number; }; const add = (a: number, b: number): number => a + b; function isValidAddRequestBody(body: unknown): body is AddRequestBody { return ( typeof body === 'object' && body !== null && 'number_one' in body && typeof (body as AddRequestBody).number_one === 'number' && 'number_two' in body && typeof (body as AddRequestBody).number_two === 'number' ); } export default async (req: NextApiRequest, res: NextApiResponse) => { try { // Validate request body first if (!isValidAddRequestBody(req.body)) { return res.status(400).json({ err: 'Invalid request: number_one and number_two must be numbers' }); } // Now TypeScript knows req.body is AddRequestBody—no more `any`! const result = add(req.body.number_one, req.body.number_two); res.status(200).json(result); } catch (err) { res.status(500).json({ err: 'Server error' }); } };
Frontend: Type-Safe Fetch Calls
Next, let's ensure your frontend fetch calls enforce the same type constraints, so TypeScript catches invalid parameters before they're sent.
1. Reuse the Request Body Type
You can reuse the same AddRequestBody type (consider moving it to a shared types folder for DRY code):
// Import or define the type here type AddRequestBody = { number_one: number; number_two: number; };
2. Create a Type-Safe Fetch Wrapper
Wrap your fetch call in a function that accepts only AddRequestBody as the body parameter:
async function callAddApi(body: AddRequestBody) { const response = await fetch("/api/add", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body), }); if (!response.ok) { throw new Error('API request failed'); } return response.json(); }
3. Test Type Enforcement
Now TypeScript will throw an error if you pass invalid data:
// ✅ Valid call (no TypeScript errors) callAddApi({ number_one: 1, number_two: 2 }); // ❌ Invalid call (TypeScript will highlight the error: "Type 'string' is not assignable to type 'number'") callAddApi({ number_one: 1, number_two: "two" });
Optional: Simplify with Zod (For Shared Validation)
If you want to avoid writing manual type guards and share validation logic between frontend and backend, use Zod—a popular library for schema declaration and validation.
1. Define a Zod Schema
import { z } from 'zod'; // Define schema (generates TypeScript type automatically) const AddRequestBodySchema = z.object({ number_one: z.number(), number_two: z.number(), }); // Infer TypeScript type from the schema type AddRequestBody = z.infer<typeof AddRequestBodySchema>;
2. Use the Schema in Backend
Zod handles runtime validation and type narrowing automatically:
import { z } from 'zod'; import type { NextApiRequest, NextApiResponse } from 'next'; const AddRequestBodySchema = z.object({ number_one: z.number(), number_two: z.number(), }); type AddRequestBody = z.infer<typeof AddRequestBodySchema>; const add = (a: number, b: number): number => a + b; export default async (req: NextApiRequest, res: NextApiResponse) => { try { // Validate and parse the request body const validatedBody = AddRequestBodySchema.parse(req.body); const result = add(validatedBody.number_one, validatedBody.number_two); res.status(200).json(result); } catch (err) { if (err instanceof z.ZodError) { // Return detailed validation errors return res.status(400).json({ err: 'Invalid request', details: err.errors }); } res.status(500).json({ err: 'Server error' }); } };
3. Reuse the Schema in Frontend
You can even validate the data on the frontend before sending it:
import { z } from 'zod'; const AddRequestBodySchema = z.object({ number_one: z.number(), number_two: z.number(), }); type AddRequestBody = z.infer<typeof AddRequestBodySchema>; async function callAddApi(body: AddRequestBody) { // Optional: Frontend validation (catches errors early) AddRequestBodySchema.parse(body); const response = await fetch("/api/add", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body), }); return response.json(); }
内容的提问来源于stack exchange,提问作者Michael Gingras

