Next.js 풀스택 개인 프로젝트를 하면서, API를 개발하고 있다.
안정적인 서버를 만들기 위해서 에러 처리에 신경을 쓰고 있다.
이런 저런 고민을 하면서 2가지 정도 도입을 해봤다.
toRespnose()
를 활용해서 NextResponse
형태로 반환할 수 있도록 했다.import { NextResponse } from 'next/server';
export class ApiError extends Error {
public readonly statusCode: number;
constructor(message: string, statusCode?: number) {
super(message);
this.name = this.constructor.name;
this.statusCode = statusCode || 500;
}
public toResponse(): NextResponse<{ error: string }> {
return NextResponse.json({ error: this.message }, { status: this.statusCode });
}
}
/* 400 Bad Request */
export class BadRequestError extends ApiError {
constructor(message?: string) {
super(message ?? 'Bad Request', 400);
}
}
/* 401 Unauthorized */
export class UnauthorizedError extends ApiError {
constructor(message?: string) {
super(message ?? 'Unauthorized', 401);
}
}
/* 403 Forbidden */
export class ForbiddenError extends ApiError {
constructor(message?: string) {
super(message ?? 'Forbidden', 403);
}
}
/* 404 Not Found */
export class NotFoundError extends ApiError {
constructor(message?: string) {
super(message ?? 'Not Found', 404);
}
}
/* 405 Method Not Allowed */
export class MethodNotAllowedError extends ApiError {
constructor(message?: string) {
super(message ?? 'Method Not Allowed', 405);
}
}
/* 409 Conflict */
export class ConflictError extends ApiError {
constructor(message?: string) {
super(message ?? 'Conflict', 409);
}
}
/* 418 I'm a teapot 😝 */
export class ImATeapotError extends ApiError {
constructor(message?: string) {
super(message ?? "I'm a teapot", 418);
}
}
/* 429 Too Many Requests */
export class TooManyRequestsError extends ApiError {
constructor(message?: string) {
super(message ?? 'Too Many Requests', 429);
}
}
/* 500 Internal Server Error */
export class InternalServerError extends ApiError {
constructor(message?: string) {
super(message ?? 'Internal Server Error', 500);
}
}
/* 501 Not Implemented */
export class NotImplementedError extends ApiError {
constructor(message?: string) {
super(message ?? 'Not Implemented', 501);
}
}
// some api function
export function GET(request: NextRequest) {
return new InternalServerError("서버 에러!!").toResponse();
}
// some api function
export function GET(request: NextRequest) {
try {
// some code...
throw new InternalServerError("서버 에러!!");
// ...
} catch (error) {
if (error instanceof ApiError) {
return error.toResponse();
}
console.error('Unexpected error:', error);
return new InternalServerError('서버에서 알 수 없는 오류가 발생했습니다.').toResponse();
}
}
handler
를 작성해주었다.return next()
로 반환되지 않고, NextResponse
가 반환되었으면 다음 미들웨어를 실행하지 않는다.import { NextRequest } from 'next/server';
export * from './middleware.auth';
export * from './middleware.validate';
type NextAPIContext = { params?: Record<string, string>; searchParams?: URLSearchParams }
export function handler(...middleware: Function[]) {
return async (request: NextRequest, context: NextAPIContext) => {
let result;
for (const fn of middleware) {
const symbol = Symbol('next');
const next = () => symbol;
result = await fn(request, context, next);
if (result !== symbol) {
break;
}
}
if (result) {
return result;
}
throw new Error('Handler or middleware must return a NextResponse!');
};
}
인증 여부를 확인하는 미들웨어
export async function tokenMiddleware(request: NextRequest, context: NextAPIContext, next: () => symbol) {
const token = request.cookies.get('token')?.value;
if (!token) {
return new UnauthorizedError('로그인이 필요합니다.').toResponse();
}
try {
const user = await auth.verifyIdToken(token);
request.headers.set('x-uid', user.uid);
} catch (error) {
return new UnauthorizedError('토큰이 유효하지 않습니다. 다시 로그인해주세요.').toResponse();
}
return next();
}
export function getUserId(request: NextRequest) {
const uid = request.headers.get('x-uid');
if (!uid) {
throw new InternalServerError('인증 미들웨어에 문제가 있습니다. 개발자에게 문의해주세요.');
}
return uid;
}
function gateway(request: NextRequest) {
try {
const userId = getUserId(request);
// 할 일...
} catch (error) {
if (error instanceof ApiError) {
return error.toResponse();
}
console.error('Unexpected error:', error);
return new InternalServerError('서버에서 알 수 없는 오류가 발생했습니다.').toResponse();
}
}
export const GET = handler(
tokenMiddleware,
gateway
);