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)에 유리합니다.
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 });
}
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의 할 일을 업데이트합니다. 요청 본문에서 text와 completed 값을 받아 해당 할 일을 찾아 수정합니다.
- 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])를 사용하여 특정 리소스에 대한 작업을 수행할 수 있습니다. - 클라이언트 컴포넌트에서는
fetchAPI를 사용하여 API Route를 호출합니다. - 적절한 HTTP 메서드 사용, 에러 처리, 상태 코드 반환이 중요합니다.
🔗 다음 회차 예고
이번 회차에서는 Next.js API Routes (Route Handlers)를 통해 기본적인 백엔드 API를 구현하는 방법을 학습했습니다. 다음 22회차에서는 데이터베이스 연동 및 환경 변수 관리에 대해 다룰 예정입니다. 실제 애플리케이션에서는 데이터를 영구적으로 저장해야 하므로, 데이터베이스와 연동하는 방법과 민감한 정보를 안전하게 관리하는 환경 변수의 중요성을 깊이 있게 살펴보겠습니다. 다음 회차에서 만나요!
