DEV Community

Ryohlan
Ryohlan

Posted on

How to handle Next.js Api routes more type safely

Create a utility method.

// handleMethods.ts
type ApiResponse<Data = {}, Error = { message: string }> =
  | { result: true; data: Data }
  | { result: false; data: Error }

export const handleMethods = <Q extends Record<string, string> = {}>() => {
  const handlers: { [key: string]: NextApiHandler | undefined } = {}
  const methodHandler = {
    get: <T, E = string>(
      handler: (
        req: NextApiRequest & { query: Q },
        res: NextApiResponse<ApiResponse<T, E>>,
      ) => void | Promise<void>,
    ) => {
      handlers['GET'] = handler
      return methodHandler
    },
    post: <T, E = string>(
      handler: (
        req: NextApiRequest & { query: Q },
        res: NextApiResponse<ApiResponse<T, E>>,
      ) => void | Promise<void>,
    ) => {
      handlers['POST'] = handler
      return methodHandler
    },
    put: <T, E = string>(
      handler: (
        req: NextApiRequest & { query: Q },
        res: NextApiResponse<ApiResponse<T, E>>,
      ) => void | Promise<void>,
    ) => {
      handlers['PUT'] = handler
      return methodHandler
    },
    delete: <T, E = string>(
      handler: (
        req: NextApiRequest & { query: Q },
        res: NextApiResponse<ApiResponse<T, E>>,
      ) => void | Promise<void>,
    ) => {
      handlers['DELETE'] = handler
      return methodHandler
    },
    prepare: (): NextApiHandler<ApiResponse> => (req, res) => {
      if (handlers[req.method]) {
        return handlers[req.method](req, res)
      } else {
        return res.status(404).json({ result: false, data: { message: 'not found' } })
      }
    },
  }
  return methodHandler
}
Enter fullscreen mode Exit fullscreen mode

Usage

// pages/api/users/index.ts

type User = {...}

export default handleMethods()
  .get<Array<User>>(async (req, res) => {
      try {
         const result = await findUsers()
         return res.json({ result: true, data: result })
      } catch(e) {
         return res.status(400).json({ result: false, data: e })
      }
  })
  .post<User>(async (req, res) => {
      try {
         const result = await createUser()
         return res.json({ result: true, data: result })
      } catch(e) {
         return res.status(400).json({ result: false, data: e })
      }
  })
  .prepare()

Enter fullscreen mode Exit fullscreen mode
// pages/api/users/[id].ts
export default handleMethods({ id: string })
  .get<Array<User>>(async (req, res) => {
      try {
         // you can access query.id type safely.
         const result = await findUsers({ id: req.query.id })
         return res.json({ result: true, data: result })
      } catch(e) {
         return res.status(400).json({ result: false, data: e })
      }
  })
  .prepare()
Enter fullscreen mode Exit fullscreen mode

Discussion (4)

Collapse
hassnainabass profile image
Hassnain Abass

This is good,
But don't you think you have made very simple thing into a very complicated one?

Collapse
ryohlan profile image
Ryohlan Author

Next.js NextApiHandler can define only one response body type.
So if a route has multiple methods and response body types, you will use the Type Assertion and you will need to write many codes more.
This utility method can decrease codes a little.

Collapse
teo_garcia profile image
Mateo Garcia

Nice article, btw you have a little typo in your Array types :)

Collapse
ryohlan profile image
Ryohlan Author

I missed it. Thank you ;)