Looking to create a wrapper function in NextJS for API routes that can handle multiple HTTP methods with different handlers.
For example, check out the TS playground
interface GetResponse {
hello: string,
}
// empty object
type PostResponse = Record<string, never>;
export default withMethodHandlers({
"GET": (req, res: NextApiResponse<GetResponse>) => {
res.status(200).json({hello: "world"})
},
// this does not work
"POST": (req, res: NextApiResponse<PostResponse>) => {
// do something with req.body
return res.status(204).end();
}
})
The function is defined as follows:
export enum HTTP_METHODS {
"GET" = "GET",
"POST" = "POST",
"PUT" = "PUT",
"DELETE" = "DELETE",
"PATCH" = "PATCH",
}
type MethodHandler<T> = (req: NextApiRequest, res: NextApiResponse<T>)=>void|Promise<void>;
function isObjKey<T>(key: PropertyKey, obj: T): key is keyof T {
return key in obj;
}
// v there could be up to HTTP_METHODS.length generics v These can be all different
export function withMethodHandlers<T, U, V>(handlers: Partial<Record<HTTP_METHODS, MethodHandler<T | U | V>>>){
return async (req: NextApiRequest, res: NextApiResponse<T | U | V | ApiError>) => {
if(isObjKey(req.method, handlers)){
// need to use ! here because TS thinks handlers[req.method] might be undefined. Is my typeguard wrong?
return handlers[req.method]!(req, res);
}
return res.status(405).json({message: `HTTP Method ${req.method} not allowed`});
}
}
The issue I'm facing is keeping it generic without being too verbose. While I could simplify it like this:
export function withMethodHandlers2<GetResponseType, PostResponseType, PutResponseType /*...*/>(handlers: {
"GET"?: MethodHandler<GetResponseType>,
"POST"?: MethodHandler<PostResponseType>,
"PUT"?: MethodHandler<PutResponseType>,
/*...*/
}){
return async (req: NextApiRequest, res: NextApiResponse<GetResponseType | PostResponseType | PutResponseType /* ... */| ApiError>) => {
if(isObjKey(req.method, handlers)){
// need to use ! here because TS thinks handlers[req.method] might be undefined. Is my typeguard wrong?
return handlers[req.method]!(req, res);
}
return res.status(405).json({message: `HTTP Method ${req.method} not allowed`});
}
}
it still seems quite lengthy. Any suggestions for a more elegant solution?