Creating an HTTP method handler function in Next.js API routes with an unspecified number of generic parameters

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?

Answer №1

This middleware simplifies the process of handling different http methods without the need for generics to determine the response type. You can easily specify the response type within each http method handler.

In this example, an enum for HttpMethod is used, but you could opt for a generic approach if desired.

Middleware

export const handleHttpMethods = (
  handlers: Partial<
    Record<HttpMethod, (req: NextApiRequest, res: NextApiResponse) => Promise<unknown> | undefined>
  >
) => {
  return async (req: NextApiRequest, res: NextApiResponse) => {
    const method = req.method as HttpMethod | undefined;
    if (!method) {
      res
        .status(StatusCodes.BAD_REQUEST)
        .setHeader(HttpResponseHeader.Error, 'No HTTP method specified')
        .end();
      return;
    }

    const handler = handlers[method];
    if (!handler) {
      res
        .status(StatusCodes.BAD_REQUEST)
        .setHeader(
          HttpResponseHeader.Error,
          'No handler specified for HTTP method'
        )
        .end();
      return;
    }

    return handler(req, res);
  };
};

Usage

export default handleHttpMethods({
[HttpMethod.GET]: (req: NextApiRequest, res: NextApiResponse<YourTypeHere>) => { ... }
})

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Encountered a TypeError in Angular printjs: Object(...) function not recognized

I'm currently working on integrating the printJS library into an Angular project to print an image in PNG format. To begin, I added the following import statement: import { printJS } from "print-js/dist/print.min.js"; Next, I implemented the pri ...

The data structure does not match the exact object type

Why isn't this code snippet functioning as expected? It seems that 'beta' has keys of type string and their values are compatible (id is a number type, and temp is also a number type). Additionally, the Record function should make the values ...

The dropdown will close automatically when the user clicks outside of it

My dropdown setup requires it to close when the user clicks outside of it. <div [class.open]="qtydropdownOpened"> <button (click)="qtydropdownOpened = !qtydropdownOpened" type="button" data-toggle="dropdown" aria-haspopup="true" [att ...

What is the reason behind Typescript errors vanishing after including onchange in the code?

When using VSCode with appropriate settings enabled, the error would be displayed in the following .html file: <!DOCTYPE html> <html> <body> <div> <select> </select> </div> <script&g ...

What is the reason behind receiving the message "_next/image?url=example.com/assets/photo.png" when attempting to display an external image from a different domain?

My goal is to load images from a different domain called "api.domain.com". I've made adjustments in my next.config.js file to prevent external URL errors. const nextConfig = { images: { remotePatterns: [ { protocol: "http", ...

Both the Stripe webhook events customer.subscription.created and customer.subscription.updated are fired simultaneously

With nextjs 13 API and Stripe integration, a challenge arises after creating a customer subscription where two events are triggered simultaneously: `customer.subscription.created` and `customer.subscription.updated`. The desired outcome is to have only one ...

Is there a way to combine compiling TypeScript and running the resulting .js file into one build command in Sublime Text 3?

I have successfully installed the TypeScript plugin on Sublime Text 3. Once installed, a build system is added to the menu for easy access. https://i.stack.imgur.com/m21bT.png You can simply press "Command + B" to build a .ts file. My goal is to compile ...

Informing typescript that an argument is specifically an array when accepting both a single string and an array of strings

How can I inform TypeScript that the code is functionally valid? It keeps suggesting it could be a string, but I am unsure how that would happen. Is this a bug in my code or am I inputting something wrong? For example: const i18nInstance = { options ...

How can one retrieve the selected value from a dropdown menu in Angular?

Objective: My goal is to create a dropdown menu where users can select a value, which will then dynamically change the address of the website based on their selection. Issue: Although I managed to make the address change upon selection, it did so for ever ...

Is there a way to activate a function in one component from another within a Next.js application?

As mentioned in the title, I am working with 2 custom components within a basic NextJS application: <section> <CompA></CompA> <CompB></CompB> </section> I am trying to figure out how to have a button inside Comp ...

Tips for making the onChange event of the AntDesign Cascader component functional

Having an issue with utilizing the Cascader component from AntDesign and managing its values upon change. The error arises when attempting to assign an onChange function to the onChange property of the component. Referencing the code snippet extracted dire ...

Containerize the CMS and NextJS applications and manage their dependencies efficiently. NextJS requires the CMS to be up and running in order to build

Scenario I am currently in the process of dockerizing my application which consists of a headless CMS (Strpi) and a client NextJS. NextJS requires the CMS to be up and running in order to build successfully (as it fetches content from port 1337) Source C ...

Using the MUI `useTheme` hook within a server-side component

Is there a way to utilize my MUI (material ui) theme definition in a server component without using the 'use client' directive? Typically, I rely on the handy useTheme hook. However, this approach requires me to convert it into a client component ...

Is it possible to launch both a Node.js server and a Next.js app simultaneously on our backend using the concurrently package?

Seeking assistance on how to concurrently start a Node.js server and Next.js app on our backend. I've added the script in the package.json file on the backend, but it's not functioning as expected. I'm unsure if 'concurrently' is ...

Nextjs application routers offer dynamic routing functionality and the ability to generate metadata

Struggling with static site generation while building a blog site in nextjs with app router? If you've read through Next.js official documentation and still need assistance, check out my project structure pictured https://i.stack.imgur.com/ZOYkf.png. ...

I need a way to call a function in my Typescript code that will update the total value

I am trying to update my total automatically when the quantity or price changes, but so far nothing is happening. The products in question are as follows: this.products = this.ps.getProduct(); this.form= this.fb.group({ 'total': ...

Official Docs style sidebar for NextJS

The official documentation for NextJS has a sleek sidebar design with both style and functionality. I am curious if it is possible to access the source code of the sidebar on the official website, or perhaps find a React/Next package that supports the Nex ...

Ensure that file (provided as a Buffer) in response remains uninterpreted as JSON in Next.js

I have a server running the Next.js API, and I need to deliver a .csv file with Shift_JIS encoding to the frontend. Currently, my server is set up like this: download(res: NextApiResponse, buffer: Buffer, fileName: string) { res.status(200) res.se ...

What is the best way to deploy Next.js and Node.js on Heroku without utilizing npm run build for the client side?

Hello, I have a dilemma with my Next.js project. If I build it, the connection to the backend breaks, which is not ideal. When I try to launch on Heroku using npm run build, everything works fine except for the backend connectivity issue... Below is an ex ...

The function `React.on('languageChanged')` is not effectively detecting changes in the language

I'm new to working with React and I'm looking for the best way to detect when a user changes their language preference. I am currently using next-translate to handle translations, but for some resources that come from an API, I need to update the ...