Next.js 기초 입문 21회차: API Routes (Route Handlers) 완벽 가이드

📚
제21주차 학습 목표

API Routes (Route Handlers)

Next.js에서 백엔드 API를 직접 구현하는 방법을 배웁니다.

Next.js 기초 입문 21회차: API Routes (Route Handlers)

안녕하세요! Next.js 기초 입문 과정의 21번째 시간입니다. 이번 회차에서는 Next.js 애플리케이션 내에서 백엔드 API를 직접 구현할 수 있도록 돕는 API Routes (Route Handlers)에 대해 심도 있게 학습하겠습니다. 프론트엔드 개발자도 손쉽게 백엔드 로직을 처리할 수 있게 해주는 이 강력한 기능을 함께 탐구해 봅시다.

📌 이번 회차 학습 목표

  • Next.js API Routes (Route Handlers)의 개념과 필요성을 이해합니다.
  • API Route를 사용하여 GET, POST, PUT, DELETE 요청을 처리하는 방법을 학습합니다.
  • 클라이언트 측에서 API Route를 호출하는 방법을 익힙니다.
  • API Route에서 데이터를 처리하고 응답하는 방법을 실습합니다.
  • API Route 사용 시 주의해야 할 보안 및 에러 처리 방안을 파악합니다.

📝 개념 설명

API Routes (Route Handlers)란 무엇인가요?

Next.js의 API Routes는 프론트엔드 애플리케이션 내에서 백엔드 API 엔드포인트를 생성할 수 있도록 해주는 기능입니다. Next.js 13 버전부터는 app 디렉토리 내에서 Route Handlers라는 이름으로 더욱 강력하고 유연하게 API를 정의할 수 있게 되었습니다. 이는 Node.js 서버리스 함수로 동작하며, 데이터베이스 연동, 외부 API 호출, 인증 처리 등 다양한 서버 측 로직을 수행할 수 있습니다.

 

Route Handlers는 app/api/your-route/route.ts (또는 .js) 파일 내에서 정의됩니다. 이 파일은 HTTP 메서드(GET, POST, PUT, DELETE 등)에 해당하는 함수를 내보내어 해당 요청을 처리합니다.

왜 API Routes (Route Handlers)를 사용해야 할까요?

  • 풀스택 개발 용이성: 프론트엔드와 백엔드 로직을 하나의 Next.js 프로젝트 내에서 관리할 수 있어 개발 생산성이 향상됩니다.
  • 서버리스 함수: 배포 시 별도의 서버 설정 없이 서버리스 환경에서 자동으로 동작하여 확장성이 뛰어납니다.
  • 데이터 은닉: 클라이언트 측에 노출되어서는 안 되는 민감한 정보(API 키, 데이터베이스 자격 증명 등)를 서버 측에서 안전하게 처리할 수 있습니다.
  • SEO 최적화: 서버 측에서 데이터를 미리 가져와 페이지를 렌더링하는 데 활용할 수 있어 검색 엔진 최적화(SEO)에 유리합니다.
📌 핵심 포인트
🔑
API Routes
Next.js 앱 내에서 백엔드 API 엔드포인트 생성.
⚙️
Route Handlers
app 디렉토리에서 API Route를 정의하는 새로운 방식. HTTP 메서드별 함수 내보내기.
🚀
서버리스
별도 서버 없이 자동으로 확장되는 함수로 동작.
🔒
보안
민감한 정보를 서버 측에서 안전하게 처리.

Route Handlers의 기본 구조

Route Handlers는 app/api/[경로]/route.ts 파일 내에서 정의되며, 각 HTTP 메서드에 해당하는 비동기 함수를 내보냅니다. 이 함수들은 Request 객체를 인자로 받아 요청 정보를 처리하고, Response 객체를 반환하여 클라이언트에 응답합니다.

  • GET: 데이터 조회
  • POST: 데이터 생성
  • PUT: 데이터 업데이트 (전체 교체)
  • PATCH: 데이터 업데이트 (부분 수정)
  • DELETE: 데이터 삭제

💡 예제 & 실습

간단한 할 일 목록(Todo List) API를 구현하며 Route Handlers의 사용법을 익혀보겠습니다. 이 예제에서는 메모리에 데이터를 저장하지만, 실제 애플리케이션에서는 데이터베이스와 연동하게 됩니다.

1. API Route 파일 생성

먼저, app/api/todos/route.ts 파일을 생성합니다. 이 파일은 /api/todos 경로로 들어오는 요청을 처리하게 됩니다.

// app/api/todos/route.ts

import { NextResponse } from 'next/server';

// 임시 데이터베이스 역할을 할 배열
let todos = [
  { id: 1, text: 'Next.js 학습하기', completed: false },
  { id: 2, text: 'API Routes 실습하기', completed: true },
];

export async function GET(request: Request) {
  return NextResponse.json(todos);
}

export async function POST(request: Request) {
  const { text } = await request.json();
  if (!text) {
    return NextResponse.json({ message: 'Text is required' }, { status: 400 });
  }
  const newTodo = { id: todos.length + 1, text, completed: false };
  todos.push(newTodo);
  return NextResponse.json(newTodo, { status: 201 });
}

2. GET 요청 처리 (모든 할 일 조회)

위 코드에서 GET 함수는 /api/todos 경로로 GET 요청이 들어왔을 때 실행됩니다. 현재 todos 배열에 있는 모든 할 일 목록을 JSON 형태로 반환합니다.

  • 터미널에서 npm run dev 또는 yarn dev로 개발 서버를 실행합니다.
  • 브라우저에서 http://localhost:3000/api/todos로 접속하거나, Postman/Insomnia 같은 API 클라이언트를 사용하여 GET 요청을 보냅니다.
  • 결과: [{"id":1,"text":"Next.js 학습하기","completed":false},{"id":2,"text":"API Routes 실습하기","completed":true}]

3. POST 요청 처리 (새로운 할 일 생성)

POST 함수는 /api/todos 경로로 POST 요청이 들어왔을 때 실행됩니다. 요청 본문(body)에서 text 값을 받아 새로운 할 일을 생성하고 todos 배열에 추가합니다. 성공적으로 생성되면 201 Created 상태 코드와 함께 새로 생성된 할 일 객체를 반환합니다.

  • Postman/Insomnia에서 http://localhost:3000/api/todos로 POST 요청을 보냅니다.
  • Headers: Content-Type: application/json
  • Body (Raw, JSON): {"text": "새로운 할 일 추가하기"}
  • 결과: {"id":3,"text":"새로운 할 일 추가하기","completed":false} (상태 코드: 201)
  • 다시 GET 요청을 보내면 새로 추가된 할 일을 확인할 수 있습니다.

4. 동적 경로 세그먼트 (Dynamic Route Segments)

특정 ID를 가진 할 일을 조회, 수정, 삭제하려면 동적 경로를 사용해야 합니다. app/api/todos/[id]/route.ts 파일을 생성합니다.

// app/api/todos/[id]/route.ts

import { NextResponse } from 'next/server';

// 임시 데이터베이스 역할을 할 배열 (실제 앱에서는 DB에서 가져옴)
let todos = [
  { id: 1, text: 'Next.js 학습하기', completed: false },
  { id: 2, text: 'API Routes 실습하기', completed: true },
  { id: 3, text: '새로운 할 일 추가하기', completed: false }, // POST로 추가된 항목 가정
];

export async function GET(request: Request, { params }: { params: { id: string } }) {
  const id = parseInt(params.id);
  const todo = todos.find(t => t.id === id);

  if (!todo) {
    return NextResponse.json({ message: 'Todo not found' }, { status: 404 });
  }
  return NextResponse.json(todo);
}

export async function PUT(request: Request, { params }: { params: { id: string } }) {
  const id = parseInt(params.id);
  const { text, completed } = await request.json();

  const todoIndex = todos.findIndex(t => t.id === id);
  if (todoIndex === -1) {
    return NextResponse.json({ message: 'Todo not found' }, { status: 404 });
  }

  todos[todoIndex] = { ...todos[todoIndex], text: text || todos[todoIndex].text, completed: completed !== undefined ? completed : todos[todoIndex].completed };
  return NextResponse.json(todos[todoIndex]);
}

export async function DELETE(request: Request, { params }: { params: { id: string } }) {
  const id = parseInt(params.id);
  const initialLength = todos.length;
  todos = todos.filter(t => t.id !== id);

  if (todos.length === initialLength) {
    return NextResponse.json({ message: 'Todo not found' }, { status: 404 });
  }
  return NextResponse.json({ message: 'Todo deleted' }, { status: 200 });
}
🔄 흐름
클라이언트 요청 (GET /api/todos/1)
Next.js 라우터
app/api/todos/[id]/route.ts의 GET 함수 실행
params.id로 1 추출
데이터 조회 및 응답

5. GET 요청 처리 (특정 할 일 조회)

app/api/todos/[id]/route.ts 파일의 GET 함수는 URL의 [id] 부분에 해당하는 값을 params 객체로 받아 특정 할 일을 조회합니다.

  • 브라우저에서 http://localhost:3000/api/todos/1로 접속합니다.
  • 결과: {"id":1,"text":"Next.js 학습하기","completed":false}

6. PUT 요청 처리 (할 일 업데이트)

PUT 함수는 특정 ID의 할 일을 업데이트합니다. 요청 본문에서 textcompleted 값을 받아 해당 할 일을 찾아 수정합니다.

  • Postman/Insomnia에서 http://localhost:3000/api/todos/1로 PUT 요청을 보냅니다.
  • Headers: Content-Type: application/json
  • Body (Raw, JSON): {"text": "Next.js API Routes 마스터하기", "completed": true}
  • 결과: {"id":1,"text":"Next.js API Routes 마스터하기","completed":true}

7. DELETE 요청 처리 (할 일 삭제)

DELETE 함수는 특정 ID의 할 일을 삭제합니다.

  • Postman/Insomnia에서 http://localhost:3000/api/todos/2로 DELETE 요청을 보냅니다.
  • 결과: {"message":"Todo deleted"} (상태 코드: 200)
  • 다시 http://localhost:3000/api/todos로 GET 요청을 보내면 ID가 2인 할 일이 삭제된 것을 확인할 수 있습니다.

8. 클라이언트 측에서 API Route 호출하기

이제 프론트엔드 컴포넌트에서 위에서 만든 API Route를 호출하는 방법을 살펴보겠습니다. app/page.tsx 파일을 수정하여 할 일 목록을 표시하고 추가하는 기능을 구현해 봅시다.

// app/page.tsx
'use client';

import { useEffect, useState } from 'react';

interface Todo {
  id: number;
  text: string;
  completed: boolean;
}

export default function Home() {
  const [todos, setTodos] = useState([]);
  const [newTodoText, setNewTodoText] = useState('');

  useEffect(() => {
    fetchTodos();
  }, []);

  const fetchTodos = async () => {
    const res = await fetch('/api/todos');
    const data = await res.json();
    setTodos(data);
  };

  const addTodo = async () => {
    if (!newTodoText.trim()) return;
    const res = await fetch('/api/todos', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({ text: newTodoText }),
    });
    const newTodo = await res.json();
    if (res.ok) {
      setTodos(prevTodos => [...prevTodos, newTodo]);
      setNewTodoText('');
    } else {
      alert(newTodo.message || '할 일 추가 실패');
    }
  };

  const toggleTodo = async (id: number, completed: boolean) => {
    const res = await fetch(`/api/todos/${id}`, {
      method: 'PUT',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({ completed: !completed }),
    });
    const updatedTodo = await res.json();
    if (res.ok) {
      setTodos(prevTodos =>
        prevTodos.map(todo => (todo.id === id ? updatedTodo : todo))
      );
    } else {
      alert(updatedTodo.message || '할 일 업데이트 실패');
    }
  };

  const deleteTodo = async (id: number) => {
    const res = await fetch(`/api/todos/${id}`, {
      method: 'DELETE',
    });
    if (res.ok) {
      setTodos(prevTodos => prevTodos.filter(todo => todo.id !== id));
    } else {
      const error = await res.json();
      alert(error.message || '할 일 삭제 실패');
    }
  };

  return (
    

나의 할 일 목록

setNewTodoText(e.target.value)} placeholder='새로운 할 일을 입력하세요' style={{ padding: '8px', marginRight: '10px', width: '70%' }} />
    {todos.map((todo) => (
  • toggleTodo(todo.id, todo.completed)} style={{ marginRight: '10px' }} /> {todo.text}
  • ))}
); }

이 컴포넌트는 fetch API를 사용하여 우리가 만든 /api/todos/api/todos/[id] Route Handlers를 호출합니다. useEffect 훅을 사용하여 컴포넌트가 마운트될 때 할 일 목록을 가져오고, 버튼 클릭 시 할 일을 추가, 토글, 삭제하는 기능을 구현합니다.

⚠️ 자주 틀리는 것 / 주의사항

  • ‘use client’ 누락: 클라이언트 컴포넌트에서 useState, useEffect와 같은 React 훅을 사용하려면 파일 상단에 'use client'; 지시어를 반드시 추가해야 합니다. API Route 파일에는 필요 없습니다.
  • 잘못된 HTTP 메서드 사용: 각 HTTP 메서드(GET, POST, PUT, DELETE)는 특정 목적을 가집니다. 예를 들어, 데이터를 조회할 때는 GET을, 생성할 때는 POST를 사용하는 것이 RESTful API 원칙에 부합합니다.
  • 요청 본문(Body) 처리: POST, PUT, PATCH 요청 시 클라이언트에서 전송한 JSON 데이터를 받으려면 await request.json()을 사용해야 합니다. GET, DELETE 요청은 일반적으로 본문을 사용하지 않습니다.
  • 응답 객체: API Route는 NextResponse.json() 또는 NextResponse.text() 등을 사용하여 응답을 반환해야 합니다. 단순히 객체를 반환하면 에러가 발생할 수 있습니다.
  • 에러 처리 및 상태 코드: API 호출 실패 시 적절한 HTTP 상태 코드(예: 400 Bad Request, 404 Not Found, 500 Internal Server Error)와 함께 에러 메시지를 반환하는 것이 중요합니다.
  • 인증/인가: 실제 프로덕션 환경에서는 API Route에 인증 및 인가 로직을 추가하여 무단 접근을 방지해야 합니다.
✅ 이번 회차 핵심 정리
  • Next.js API Routes (Route Handlers)는 app/api/[경로]/route.ts 파일에 정의되어 백엔드 API를 구현합니다.
  • 각 HTTP 메서드(GET, POST, PUT, DELETE)에 해당하는 비동기 함수를 내보내어 요청을 처리합니다.
  • Request 객체로 요청 정보를 받고, NextResponse.json() 등으로 응답을 반환합니다.
  • 동적 경로 세그먼트([id])를 사용하여 특정 리소스에 대한 작업을 수행할 수 있습니다.
  • 클라이언트 컴포넌트에서는 fetch API를 사용하여 API Route를 호출합니다.
  • 적절한 HTTP 메서드 사용, 에러 처리, 상태 코드 반환이 중요합니다.

🔗 다음 회차 예고

이번 회차에서는 Next.js API Routes (Route Handlers)를 통해 기본적인 백엔드 API를 구현하는 방법을 학습했습니다. 다음 22회차에서는 데이터베이스 연동 및 환경 변수 관리에 대해 다룰 예정입니다. 실제 애플리케이션에서는 데이터를 영구적으로 저장해야 하므로, 데이터베이스와 연동하는 방법과 민감한 정보를 안전하게 관리하는 환경 변수의 중요성을 깊이 있게 살펴보겠습니다. 다음 회차에서 만나요!

Wordpress Social Share Plugin powered by Ultimatelysocial
Copy link
URL has been copied successfully!
THREADS
RSS
error: 저작권 콘텐츠보호를 부탁드립니다.