← 전체 글로 돌아가기

Next.js

Next.js API route에서 validation은 어디에?

요청 검증을 어느 레이어에서 할지는 성능과 유지보수성에 영향을 미친다.

검증의 위치가 중요한 이유

Next.js API route는 간단하지만 구조화가 필요하다. 특히 입력값 검증을 어디에 할지가 전체 아키텍처에 영향을 미친다.

export default async function handler(req, res) {
  // 어디에 검증을 넣을까?
}

1단계: Route 레벨 검증

HTTP 메서드부터 확인:

export default async function handler(req, res) {
  if (req.method !== 'POST') {
    return res.status(405).end('Method Not Allowed');
  }

  // 이후 로직
}

이건 필수다. 예상하지 않은 메서드를 거부한다.

2단계: 쿼리/바디 파라미터 검증

요청의 필수 필드를 확인:

const { email, password } = req.body;

if (!email || !password) {
  return res.status(400).json({
    error: 'email과 password는 필수입니다'
  });
}

3단계: 비즈니스 로직 검증

데이터의 유효성을 확인:

if (!email.includes('@')) {
  return res.status(400).json({ error: '유효한 이메일이 아닙니다' });
}

if (password.length < 8) {
  return res.status(400).json({ error: '비밀번호는 8자 이상이어야 합니다' });
}

라이브러리 사용하기

검증을 매번 수동으로 하기엔 번거롭다. Zod를 추천한다:

import { z } from 'zod';

const schema = z.object({
  email: z.string().email(),
  password: z.string().min(8),
});

export default async function handler(req, res) {
  try {
    const data = schema.parse(req.body);
    // 안전한 data 사용
  } catch (e) {
    return res.status(400).json({ error: e.message });
  }
}

미들웨어로 중앙화하기

검증 로직을 반복하지 않으려면:

function withValidation(schema) {
  return (handler) => {
    return async (req, res) => {
      try {
        req.validated = schema.parse(req.body);
        return handler(req, res);
      } catch (e) {
        return res.status(400).json({ error: e.message });
      }
    };
  };
}

export default withValidation(schema)(async (req, res) => {
  const { email, password } = req.validated;
  // 검증된 데이터 사용
});

정리

  • Route 레벨: HTTP 메서드 확인 (필수)
  • Request 레벨: 필드 존재 확인
  • Business 레벨: 값의 유효성 확인
  • 중복 제거: 미들웨어나 라이브러리 사용