Hvordan legger man til TypeScript-typer for request body i Next.js API-rute?
Problem
En av hovedgrunnene til at jeg liker å bruke TypeScript er å sjekke at jeg sender korrekt typede parametere til gitte funksjonskall.
Men når jeg bruker Next.js, støter jeg på problemet der parametere som sendes til et Next.js API-endepunkt mister typene sine når de blir «nedgradert» til NextApiRequest-typen.
Vanligvis ville jeg hentet ut parametere ved å gjøre noe som req.body.[param_name], men hele kjeden har typen any, så jeg mister all meningsfull typeinformasjon.
Eksempel
La oss anta at jeg har et API-endepunkt i et Next.js-prosjekt på pages/api/add.ts som er ansvarlig for å legge sammen to tall. I denne filen har vi også en typet funksjon for å legge sammen to tall, som API-endepunktet vil kalle.
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øter på, og det jeg gjerne vil ha hjelp med, er hvordan jeg kan skrive req.body.number_one og req.body.number_two eller en hvilken som helst parameter som kommer fra forespørselsobjektet. Er dette mulig?
Siden typene til request-objektet er any, klager ikke TypeScript, selv om du prøver å kalle API-endepunktet med feiltypede parametere.
fetch("/api/add", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ number_one: 1, number_two: "two" }),
});
TypeScript-kompilatoren vil ikke ha noen problemer med ovennevnte kall til API-endepunktet, selv om typene er feil. Den behandler body som en any-type, så den mister all typeinformasjon.
Det hadde vært flott om jeg kunne skrive parametre kastet fra forespørselskroppen som sendes til API-endepunkter i Next.js
Svar #1
Du kan lage et nytt grensesnitt som utvider NextApiRequest og legger til typene for de to feltene.
interface ExtendedNextApiRequest extends NextApiRequest {
body: {
number_one: number;
number_two: number;
};
}
Bruk det deretter til å skrive req-objektet i handlerfunksjonen.
export default async (req: ExtendedNextApiRequest, res: NextApiResponse) => {
//...
};
Selv om utvidelse av NextApiRequest-typen vil stoppe TypeScript fra å klage, forhindrer det ikke at potensielle kjørefeil kan oppstå.
For en bedre, typesikker tilnærming som heller avgrenser typene, sjekk ut svaret til @Matthieu Gellé.
Svar #2
julios svar fungerer, men det anbefales ikke av den offisielle dokumentasjonen :
Utvidelse av req/res-objektene 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 endret koden din mye, slik at du kan integrere den enkelt, hvis du har mot til å komme og endre denne brikken til tross for at den «fungerer»
Svar #3
Lag bare en Type Guard og bruk den i handleren din. Matthieus svar er flott, men vanskelig når man har mange forventede egenskaper.
Å sjekke om noe sendt gjennom kroppen er av en viss type kan ta ganske mye tid når du må gå gjennom 5+ egenskaper. Enda verre hodepine hvis de er nestet flere nivåer. Bare bruk riktige validatorer og skriv skjemaer for dem.
For dette formålet, som Matthieu spesifiserte, bør du ikke utvide NextApiRequest og NextApiResponse ved å overstyre eksisterende egenskaper, bare utvid dem for å legge til nye.
I stedet ville jeg skrevet en generell 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 bruk det slik:
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
}
Referanse: Bruk av typepredikater
