DEV Community

loading...

How to handle Next.js Api routes more type safely

ryohlan profile image Ryohlan ・2 min read

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)

pic
Editor guide
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 ;)