'How to handle errors in Next.js api?

What is the best practice how to organize code in Next.js api handlers? I saw this video and he puts all REST routes in two files:

  1. pages/api/users/index.ts handles all operations that don't require id so GET /api/users - get all users and POST pages/api/users - create a new user

  2. pages/api/users/[id].ts handles all operations that require id so GET api/users/1 - get user by id, PUT /api/users/1 - update user, and DELETE /api/users/1 - delete a user

This means a lot of code will go into just 2 files and handled by a switch case statement. How should all this code be organized?

Every case statement should have its own try catch block for handling database calls which is a lot of repetition, I could make single try catch around entire switch but that will wrap a lot of unnecessary code, and maybe each case needs different handling? I could put single try catch in higher order function and wrap each case block with it but I'm not sure that's nice either?

Also later I will need to protect some routes with withProtected and withRole middlewares but that wont be easy because now multiple api's are handled inside single handler call.

What is the best way to organize this? Is there already good complete example existing?


// pages/api/users/index.ts

import { NextApiRequest, NextApiResponse } from 'next';
import { hash } from 'bcryptjs';
import prisma from 'lib/prisma';

/**
 * POST /api/users
 * Required fields in body: name, username, email, password
 */

export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse
): Promise<void> {
  const { method, body } = req;

  switch (method) {
    case 'GET':
      // get all
      // try catch again?
      const users = await prisma.user.findMany();
      res.status(200).json({ users });

      break;
    case 'POST':
      // create
      try {
        const { name, username, email, password: _password } = body;

        // todo: validate...

        const _user = await prisma.user.findFirst({
          where: { email },
        });

        if (_user)
          throw new Error(`The user with email: ${email} already exists.`);

        const password = await hash(_password, 10);
        const user = await prisma.user.create({
          data: {
            name,
            username,
            email,
            password,
          },
        });

        res.status(201).json({ user });
      } catch (error) {
        res.status(500).json({ error });
      }

      break;
    default:
      res.setHeader('Allow', ['GET', 'POST']);
      res.status(405).end(`Method ${method} Not Allowed`);
  }
}



Solution 1:[1]

This is how I've done things. It also covers method not allowed

Working code example from my project

src/somewhere/globalAPICall.js

/**
 * 
 * @param {http.IncomingMessage} req
 * @param {http.ServerResponse} res
 * @param {{[key: string]: () => Promise<void>}} actions 
 */
export default async function globalAPICall(req, res, actions) {
    try {
      const method = req.method
      // check an action exists with request.method else throw method not allowed
      if (!Object.keys(actions).includes(method)) {
        console.log('method not allowed')
        throw new MyError('Method not allowed', 405)
      }
      // run the action matching the request.method
      await actions[method]()
    } catch(err) {
      if (err instanceof MyError) {
        res.status(err.code).send(err.message);
      } else {
        res.status(500).send("Internal server error");
      }
    }
}

src/pages/api/users.js


/**
 *
 * @param {http.IncomingMessage} req
 * @param {http.ServerResponse} res
 */
export default async function handler(req, res) {

    async function POST() {
      const { email, password, username } = req.body;
      if (!username) {
        throw new MyError('Username required', 400)
      }
      await CreateUser(email, password, username)
      res.status(201).send();
    }

    async function GET() {
      const result = await ListUsers()
      res.status(200).json(result);
    }


    await globalAPICall.js(req, res, {POST, GET})

}

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Arda O?ul Üçp?nar