Hvordan tilføjer man TypeScript-typer til forespørgselslegemet i en Next.js API-rute?
Problem
En af de primære grunde til, at jeg kan lide at bruge TypeScript, er at kontrollere, at jeg sender korrekt typede parametre til givne funktionskald.
Men når jeg bruger Next.js, støder jeg på problemet, hvor parametre, der sendes til en Next.js API-endpoint, mister deres typer, når de bliver "nedgraderet" til NextApiRequest-typen.
Typisk ville jeg hente parametre ved at gøre noget som req.body.[param_name], men hele den kæde har typen any, så jeg mister al meningsfuld typeinformation.
Eksempel
Lad os antage, at jeg har en API-endpoint i et Next.js-projekt på pages/api/add.ts, som er ansvarlig for at lægge to tal sammen. I denne fil har vi også en typet funktion til at lægge to tal sammen, som API-endpointet vil kalde.
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!" });
}
};
Problemet, jeg støder på, og som jeg gerne vil have hjælp til, er, hvordan man skriver req.body.number_one og req.body.number_two eller en hvilken som helst parameter, der kommer fra request-objektet. Er dette muligt?
Da typerne af request-objektet er any, klager TypeScript ikke, selvom du prøver at kalde API-endpointet med forkert typede parametre.
fetch("/api/add", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ number_one: 1, number_two: "two" }),
});
TypeScript-kompilatoren vil ikke have problemer med ovenstående kald til API-endpointet, selvom typerne er forkerte. Den behandler body som en any-type, så den mister enhver typeinformation.
Det ville være fantastisk, hvis jeg kunne skrive parametre cast fra anmodningsbody, der sendes til API-endpoints i Next.js
Svar #1
Du kan oprette en ny grænseflade, der udvider NextApiRequest og tilføjer typingerne for de to felter.
interface ExtendedNextApiRequest extends NextApiRequest {
body: {
number_one: number;
number_two: number;
};
}
Brug det derefter til at skrive req-objektet i handlerfunktionen.
export default async (req: ExtendedNextApiRequest, res: NextApiResponse) => {
//...
};
Selvom det at udvide NextApiRequest-typen vil stoppe TypeScript fra at klage, forhindrer det ikke, at potentielle runtime-fejl opstår.
For en bedre, typesikker tilgang, der i stedet indsnævrer typerne, se @Matthieu Gellés svar.
Svar #2
julios svar virker, men det anbefales ikke af den officielle dokumentation:
Udvidelse af req/res-objekterne med 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!" });
}
};
Jeg har ikke ændret din kode meget, så du kan integrere den nemt, hvis du har modet til at komme og ændre denne blok på trods af, at den "virker"
Svar #3
Lav bare en Type Guard og brug den inden for din handler. Matthieus svar er godt, men besværligt, når man har mange forventede egenskaber.
At kontrollere, om noget sendt gennem kroppen er af en bestemt type, kan tage ret lang tid, når man skal igennem 5+ egenskaber. Endnu værre bliver det, hvis de er indlejret flere niveauer. Brug bare korrekte validatorer og skriv skemaer for dem.
Til dette formål, som Matthieu specificerede, bør du ikke udvide NextApiRequest og NextApiResponse ved at overskrive eksisterende egenskaber, kun udvide dem for at tilføje yderligere.
I stedet ville jeg skrive en generisk som dette:
function isValidBody<T extends Record<string, unknown>>(
body: any,
fields: (keyof T)[],
): body is T {
return Object.keys(body).every((key) => fields.includes(key))
}
Og brug det sådan her:
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
}
Reference: Brug af typepredikater
