Next.js API 处理器代码组织与错误处理最佳实践咨询
Next.js Pages Router API 处理器最优组织实践
核心优化思路
核心是拆分HTTP方法处理逻辑 + 通用能力高阶封装,避免所有逻辑耦合在单一根handler的switch分支中。
1. 封装通用高阶工具
首先封装可复用的错误处理、中间件高阶函数,解决重复代码问题:
// lib/api/withErrorHandler.ts import { NextApiRequest, NextApiResponse } from 'next'; type ApiHandler = (req: NextApiRequest, res: NextApiResponse) => Promise<void>; type ErrorHandler = (err: unknown, req: NextApiRequest, res: NextApiResponse) => void; const defaultErrorHandler: ErrorHandler = (err, req, res) => { console.error(err); res.status(500).json({ error: err instanceof Error ? err.message : '服务器内部错误' }); }; export const withErrorHandler = (handler: ApiHandler, customErrorHandler?: ErrorHandler): ApiHandler => { return async (req, res) => { try { await handler(req, res); } catch (err) { customErrorHandler ? customErrorHandler(err, req, res) : defaultErrorHandler(err, req, res); } }; };
鉴权中间件示例:
// lib/api/withProtected.ts import { NextApiRequest, NextApiResponse } from 'next'; import { verifyToken } from 'lib/auth'; export const withProtected = (handler: ApiHandler): ApiHandler => { return async (req, res) => { const token = req.headers.authorization?.split(' ')[1]; if (!token) return res.status(401).json({ error: '未授权访问' }); try { req.user = verifyToken(token); await handler(req, res); } catch { return res.status(401).json({ error: 'token无效' }); } }; };
2. 拆分路由方法实现,按需组合高阶能力
改造后的pages/api/users/index.ts代码:
// pages/api/users/index.ts import { NextApiRequest, NextApiResponse } from 'next'; import { hash } from 'bcryptjs'; import prisma from 'lib/prisma'; import { withErrorHandler } from 'lib/api/withErrorHandler'; import { withProtected, withRole } from 'lib/api/middlewares'; // 单独实现GET方法:获取所有用户,需要管理员权限 const getUsersHandler = async (req: NextApiRequest, res: NextApiResponse) => { const users = await prisma.user.findMany(); res.status(200).json({ users }); }; // 给GET方法套鉴权、角色校验、错误处理 const wrappedGetUsers = withErrorHandler(withRole('admin')(withProtected(getUsersHandler))); // 单独实现POST方法:创建用户,无需鉴权,自定义错误处理 const createUserHandler = async (req: NextApiRequest, res: NextApiResponse) => { const { name, username, email, password: _password } = req.body; if (!name || !username || !email || !_password) { return res.status(400).json({ error: '缺少必填字段' }); } const existUser = await prisma.user.findFirst({ where: { email } }); if (existUser) throw new Error(`邮箱 ${email} 已被注册`); const password = await hash(_password, 10); const user = await prisma.user.create({ data: { name, username, email, password } }); res.status(201).json({ user }); }; // 自定义POST方法的错误处理 const postCustomErrorHandler = (err: unknown, req: NextApiRequest, res: NextApiResponse) => { if (err instanceof Error && err.message.includes('已被注册')) { return res.status(409).json({ error: err.message }); } res.status(500).json({ error: '用户创建失败' }); }; const wrappedCreateUser = withErrorHandler(createUserHandler, postCustomErrorHandler); // 根handler仅做方法分发 export default async function handler( req: NextApiRequest, res: NextApiResponse ): Promise<void> { switch (req.method) { case 'GET': return wrappedGetUsers(req, res); case 'POST': return wrappedCreateUser(req, res); default: res.setHeader('Allow', ['GET', 'POST']); res.status(405).end(`方法 ${req.method} 不被允许`); } }
3. 复杂业务逻辑抽离到Service层
如果业务逻辑复杂度高,可将数据库操作、业务规则判断抽离到单独的service文件,例如services/user.service.ts,路由层仅负责参数接收、校验、调用service、返回响应,进一步降低路由文件的冗余度。
常见疑问解答
- 错误处理方案合理性:将try catch封装为高阶函数是完全合理的工程化方案,既避免了每个分支重复编写try catch的冗余代码,也支持传入自定义错误处理逻辑满足不同分支的差异化需求,平衡了通用性和灵活性。
- 中间件接入问题:拆分单个HTTP方法的处理函数后,每个方法可以独立按需组合任意中间件,不会对其他方法产生影响,完美解决了单handler处理多接口时中间件适配复杂的问题。
内容的提问来源于stack exchange,提问作者marko kraljevic
相关产品推荐
相关产品推荐

