When it comes to organizing code in Next.js api handlers, what is the most effective practice? In a video I watched, the presenter suggested placing all REST routes in two specific files:
pages/api/users/index.ts
would manage operations that do not require anid
, such as
andGET /api/users - retrieve all users
POST pages/api/users - add a new user
pages/api/users/[id].ts
would handle operations that involve anid
, for instance,
,GET api/users/1 - fetch user by id
PUT /api/users/1 - update user
, andDELETE /api/users/1 - remove a user
This approach would consolidate a significant amount of code into just these 2 files, managed through a switch case
statement. How should this code be structured effectively?
Each case
statement necessitates its own try catch
block for handling database queries, leading to repetition. While wrapping the entire switch
with a single encompassing try catch
could avoid duplicating code, each case
might require distinct treatment. Another option is enclosing the cases within a higher-order function with a single try catch
, though this may not be ideal either.
In addition, future implementation of route protection using withProtected
and withRole
middleware might pose a challenge due to the current setup where multiple APIs are handled within a single handler call.
What would be the best approach to organize this structure efficiently? Are there any comprehensive examples available for reference?
// 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
// another try catch block?
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`);
}
}