2025-08-11 22:05

Tags:

Next.js 개발자를 위한 RESTful API 핸드북 API 라우트부터 데이터 페칭까지

“풀스택 프레임워크의 힘을 API로 완성하다.” Next.js 개발자에게 RESTful API는 외부 서비스와의 통신 수단을 넘어, 애플리케이션 내부의 프론트엔드와 백엔드를 잇는 강력한 다리 역할을 합니다. Next.js는 API를 만드는 서버의 역할과, API를 사용하는 클라이언트의 역할을 한 프로젝트 안에서 모두 수행할 수 있는 독특한 환경을 제공합니다.

이 핸드북은 Next.js의 핵심 기능인 **API 라우트(Route Handlers)**를 사용하여 RESTful API를 구축하고, 서버 컴포넌트와 클라이언트 컴포넌트에서 이 API를 효율적으로 사용하는 방법에 대한 실전 가이드를 제공합니다.

1. Next.js, API의 생산자 되기: API 라우트 (Route Handlers)

Next.js app 디렉토리의 Route Handlers는 웹 표준 RequestResponse 객체를 기반으로 RESTful API 엔드포인트를 손쉽게 만들 수 있는 기능입니다.

가. 기본 구조: app/apiroute.ts

API 엔드포인트는 app/api 폴더 내에 경로를 만들고, 그 안에 route.ts(또는 .js) 파일을 생성하여 만듭니다. 파일 안에서는 HTTP 메서드(GET, POST 등)의 이름을 딴 함수를 export 합니다.

비유: app/api 폴더는 ‘민원실’ 건물이고, users/route.ts와 같은 파일은 ‘가족관계증명서 발급 창구’입니다. 각 창구에는 ‘조회(GET)’, ‘신규 발급(POST)’ 등 정해진 업무(HTTP 메서드)가 있습니다.

app/api/users/route.ts 예시:

// app/api/users/route.ts
 
import { NextResponse } from 'next/server';
 
// 모든 사용자 목록 조회 (GET /api/users)
export async function GET(request: Request) {
  // 실제로는 여기서 데이터베이스를 조회합니다.
  const users = [
    { id: 1, name: '홍길동' },
    { id: 2, name: '이순신' },
  ];
 
  return NextResponse.json({ users });
}
 
// 새로운 사용자 생성 (POST /api/users)
export async function POST(request: Request) {
  const newUser = await request.json();
 
  // 실제로는 여기서 데이터베이스에 사용자를 추가합니다.
  console.log('새로운 사용자:', newUser);
 
  return NextResponse.json({ message: '사용자 생성 완료', user: newUser }, { status: 201 });
}

나. 동적 라우팅: [id]로 특정 자원 다루기

개별 자원을 다루기 위해 대괄호([])를 사용한 동적 라우팅을 활용합니다.

app/api/users/[id]/route.ts 예시:

// app/api/users/[id]/route.ts
 
import { NextResponse } from 'next/server';
 
// 특정 사용자 정보 조회 (GET /api/users/123)
export async function GET(
  request: Request,
  { params }: { params: { id: string } }
) {
  const userId = params.id;
  // 실제로는 DB에서 userId로 사용자를 찾습니다.
  const user = { id: userId, name: `사용자 ${userId}` };
 
  if (!user) {
    return NextResponse.json({ error: '사용자를 찾을 수 없음' }, { status: 404 });
  }
 
  return NextResponse.json({ user });
}
 
// 특정 사용자 정보 수정 (PATCH /api/users/123)
export async function PATCH(
    request: Request,
    { params }: { params: { id: string } }
) {
    const userId = params.id;
    const updatedData = await request.json();
    
    // DB에서 userId로 사용자를 찾아 updatedData로 수정
    console.log(`${userId}번 사용자를 다음 정보로 수정:`, updatedData);
 
    return NextResponse.json({ message: '수정 완료', user: {id: userId, ...updatedData} });
}

2. Next.js, API의 소비자 되기: 데이터 페칭 (Data Fetching)

Next.js에서는 컴포넌트의 종류에 따라 API를 호출하는 방식이 다릅니다.

가. 서버 컴포넌트: 가장 간단하고 강력한 방법

서버 컴포넌트는 서버에서 렌더링되므로, fetch를 사용해 자기 자신의 API 라우트를 직접 호출할 수 있습니다. async/await를 컴포넌트에서 바로 사용할 수 있어 코드가 매우 간결해집니다.

서버 컴포넌트 예시:

// app/users/page.tsx
 
async function getUsers() {
  // 서버 환경이므로 절대 경로 또는 http://localhost... 사용
  const res = await fetch('http://localhost:3000/api/users', {
    cache: 'no-store' // 실시간 데이터를 위해 캐시 사용 안 함
  });
 
  if (!res.ok) {
    throw new Error('사용자 정보를 가져오는 데 실패했습니다.');
  }
  return res.json();
}
 
export default async function UsersPage() {
  const data = await getUsers();
  const users = data.users;
 
  return (
    <div>
      <h1>사용자 목록</h1>
      <ul>
        {users.map((user: any) => (
          <li key={user.id}>{user.name}</li>
        ))}
      </ul>
    </div>
  );
}

✨ 핵심 포인트: 캐싱 전략

Next.js의 fetch는 강력한 캐싱 기능을 내장하고 있습니다. REST의 Cacheable 원칙을 극대화할 수 있습니다.

  • fetch(URL, { cache: 'force-cache' }): 기본값. 빌드 시점에 데이터를 가져와 정적 페이지처럼 만듭니다.

  • fetch(URL, { cache: 'no-store' }): 매 요청마다 새로운 데이터를 가져옵니다.

  • fetch(URL, { next: { revalidate: 3600 } }): 3600초(1시간)마다 데이터를 갱신합니다. (ISR)

나. 클라이언트 컴포넌트: 동적인 상호작용을 위해

사용자 인터랙션(버튼 클릭 등)에 따라 데이터를 가져와야 할 때는 클라이언트 컴포넌트를 사용합니다. 'use client' 지시어를 파일 상단에 명시해야 합니다.

클라이언트 컴포넌트 예시:

'use client';
 
import { useState, useEffect } from 'react';
 
export default function UserProfile() {
  const [user, setUser] = useState(null);
  const [isLoading, setIsLoading] = useState(true);
 
  useEffect(() => {
    // 클라이언트에서는 상대 경로 사용 가능
    fetch('/api/users/1')
      .then(res => res.json())
      .then(data => {
        setUser(data.user);
        setIsLoading(false);
      });
  }, []); // 컴포넌트 마운트 시 1회 실행
 
  if (isLoading) return <p>로딩 중...</p>;
  if (!user) return <p>사용자 정보 없음</p>;
 
  return <h1>{user.name}님, 환영합니다!</h1>;
}

팁: 클라이언트 컴포넌트에서는 SWR이나 React Query 같은 데이터 페칭 라이브러리를 사용하면 캐싱, 재요청, 상태 관리를 훨씬 더 효율적으로 처리할 수 있습니다.

3. Next.js 개발자를 위한 추가 고려사항

  • 서버 액션 (Server Actions): form을 통한 데이터 변경(POST, PUT, DELETE) 작업은 REST API 엔드포인트를 만드는 대신 서버 액션을 사용하는 것이 더 간결하고 효율적일 수 있습니다. 하지만 데이터를 조회(GET)하거나, 외부 서비스에 API를 제공해야 하는 경우에는 여전히 RESTful API 방식이 유효하고 중요합니다.

  • 인증 (Authentication): API 라우트를 보호하기 위해 NextAuth.js나 기타 인증 라이브러리를 사용하여 요청 헤더의 쿠키나 토큰을 검증하는 로직을 추가해야 합니다.

  • 환경 변수: 데이터베이스 접속 정보나 외부 API 키 등 민감한 정보는 .env.local 파일에 저장하고, 서버 측(API 라우트, 서버 컴포넌트)에서만 process.env를 통해 접근해야 합니다.

Next.js 환경에서 RESTful API를 구축하고 활용하는 것은 프론트엔드와 백엔드 로직을 하나의 프로젝트 안에서 유기적으로 연결하는 핵심 기술입니다.

혹시 API를 설계할 때 에러 처리나 응답 데이터 구조화에 대해 더 구체적인 전략이 궁금하신가요?

References

REST