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

Changing the state object without using the setState function, but rather utilizing an object method

Utilizing a class within my "useState" hook, with a method to alter its content, here's a concise example: class Example { bar: string; constructor() { this.bar = 'bar'; } changeBar() { this.bar = 'baz ...

What is the recommended data type for Material UI Icons when being passed as props?

What specific type should I use when passing Material UI Icons as props to a component? import {OverridableComponent} from "@mui/material/OverridableComponent"; import {SvgIconTypeMap} from "@mui/material"; interface IconButtonProps { ...

After installing the latest version of [email protected], I encountered an error stating "Module 'webpack/lib/node/NodeTemplatePlugin' cannot be found."

Upon updating to nextjs version 10.1.3, I encountered an error when running yarn dev. Error - ./public/static/style.scss Error: Cannot find module 'webpack/lib/node/NodeTemplatePlugin' Require stack: - /path_to/node_modules/mini-css-extract-plugi ...

Angular - Set value only if property is present

Check if the 'rowData' property exists and assign a value. Can we approach it like this? if(this.tableObj.hasOwnProperty('rowData')) { this.tableObj.rowData = this.defVal.rowData; } I encountered an error when attempting this, specif ...

Typescript best practice: limiting global variables per file

I found it very useful in jslint to require declaring all used globals at the beginning of a file using the following syntax: /*global console, document */ Is there a similar feature available in Typescript? I managed to disable the implicit availabilit ...

I encountered an unexpected obstacle while reloading my Next.js application with animejs. The error message reads: "SyntaxError: Unexpected token 'export'." This unwelcome occurrence took place during the

Encountering an error with animejs when reloading my Next.js app: An unexpected token 'export' is causing a SyntaxError. This issue occurred during the page generation process. The error originates from file:///Users/.../node_modules/animejs/lib ...

Compilation issues in node-modules arise following the Vue package and i18next updates

Recently, I decided to upgrade from the i18n package to the newer version called i18next in my project. However, this update led to numerous errors popping up during compilation. Fortunately, by adding 'skipLibCheck' to the compiler options in th ...

Angular 7's Singleton Service Feature

My service setup looks like this: export abstract class ILoggingService { // abstract functions here } export class LoggingService implements ILoggingService { // service implementation } export class MockLoggingService implements ILoggingServic ...

"Once the queryParams have been updated, the ActivatedRoute.queryParams event is triggered once

Within my Angular component, I am making an API call by passing a hash string extracted from the current query parameters. Upon receiving the API result, a new hash is also obtained and set as the new hash query parameter. Subsequently, the next API call w ...

How is it possible for my search results page to retrieve the words I input?

Currently, I am in the process of creating the search result page and I plan to utilize dynamic routing for implementation. Here is a snippet of my search bar code: <Link href={`/productSearchResult/${searchWord}`}> <a className={styles.navbar_s ...

I've added a check, so why is TypeScript still complaining about the possibility of my property being undefined?

const handleLinkClick = (index: number) => () => { const hasUrl = !!searchItems[index]?.url; if (hasUrl) { navigateToLink(searchItems[index]?.url); } else { setItemSelected(index); } }; However, the issue I encountered is: (property) ...

Incorporating Ionic Elements

I've been attempting to set a default segment as active in my app. After looking through other threads and questions, the solution appears to involve making changes in the segment js file located in the components folder. However, I can't seem t ...

ng-click is not triggering my function

I'm really struggling to understand how AngularJS and Typescript can work together efficiently. My main goal is to make a simple method call, but I seem to be stuck due to some constraints in the architecture I have chosen. I must have made a mistake ...

Using the VSCode debugger to place a breakpoint within a Typescript package that has been symlinked using `npm link`

I'm currently troubleshooting a NodeJS application and its associated typescript packages, which have been linked using `npm link`. The directory structure is as follows: /root/package-a # typescript package /root/package-b # another typescript packa ...

Can you explain the significance of <this> within TypeScript generics?

Our application employs express along with TypeScript. While exploring their type definitions, I stumbled upon the following snippet and I'm curious about its meaning: export interface IRouter extends RequestHandler { all: IRouterMatcher<this& ...

Encountering an error when attempting to show user details on a webpage using Angular and Ionic with Promise functionality

On my app's AccountSettingsPage, I am fetching user data from a SQLite DB and displaying it on an Ionic page. However, I encountered the following error: Error: TypeError: Cannot read property 'name' of undefined at Object.eval [as upd ...

Encountering a favicon issue in Next.js 14 after restructuring the project to implement Internationalization

Having trouble with favicon display in Next.js 14.0.2. The issue cropped up after implementing internalization and reconfiguring my project structure. Here is a simplified view of my project structure: - app - [lang] - _components - _dictionaries ...

Typescript having issues compiling to commonjs/es2015 accurately

I currently have Node v14.5.0 installed and I'm using ts-node-dev in my development environment However, I am encountering an error every time I try to compile to JS. Initially, I attempted with the following tsconfig: "target": "es5& ...

What could be causing ConnectedProps to incorrectly infer the type?

My redux state is rooted and defined as: interface RootState { users: User[] } When working with components, I want to utilize ConnectedProps to generate the props type automatically from my state mapping and dispatch mapping: const mapState = (state: ...

I am unable to display my JSON data in Next.js using a fetch request

I'm having trouble understanding the code, and it seems like the API is open to anyone since it's just a simple JSON. However, I keep getting an error: Error Message: Reason: `undefined` cannot be serialized as JSON. Please use `null` or omit th ...