5

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`);
  }
}

1 Answer 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})

}
Sign up to request clarification or add additional context in comments.

2 Comments

Looks great! Can you provide your code for "MyError"? I'm guessing it's an extended class of Error. But I still have a lot to learn in terms of error handling
MyError is just an Error type, you can use the basic error type of JS, or create your own, it doesn't matter. And, sorry for late response, I've lost my grandpa by COVID, so did not have time to check SO lately.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.