| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | 5 | 6 | |
| 7 | 8 | 9 | 10 | 11 | 12 | 13 |
| 14 | 15 | 16 | 17 | 18 | 19 | 20 |
| 21 | 22 | 23 | 24 | 25 | 26 | 27 |
| 28 | 29 | 30 | 31 |
Tags
- 클래스 속성
- 파이썬 반복문
- Kotlin Class
- Kotlin 조건문
- 넥스트js
- git
- 희망
- activate 오류
- 파이썬 장고
- 파이썬 제어문
- Kotlin If
- Kotlin 클래스
- Python
- Kotlin else if
- Kotlin 클래스 속성정의
- NextJs
- 장고 가상환경
- 도전
- python Django
- 성공
- 파이썬
- Python Class
- 파이썬 클래스
- github
- 자바 기본타입
- 좋은글
- 다중조건문
- Variable declaration
- 강제 타입변환
- django virtualenv
Archives
- Today
- Total
키모스토리
#22. Route Handlers 본문
반응형
Next.js Route Handler로 댓글 “완전 CRUD” 만들기
(GET 목록 + POST 추가 + GET/PATCH/DELETE)
Next.js App Router에서는 app/**/route.ts 파일로 Route Handler(API 엔드포인트) 를 만들 수 있습니다.
Route Handler는 Web 표준 Request / Response 기반이며, Next.js가 제공하는 NextResponse.json() 같은 편의 API도 사용할 수 있습니다. nextjs.org+1
또한 같은 라우트 세그먼트 레벨에 page.tsx와 route.ts를 동시에 둘 수는 없습니다. (URL 충돌 방지 규칙) nextjs.org
즉, 아래 예제는 /comments가 “페이지(UI)”가 아니라 “API(JSON)”로 동작하는 구조입니다. /comments에 UI도 만들고 싶으시다면 /api/comments로 API를 옮기는 방식을 권장드립니다.
1) 폴더 구조
src/app/comments/
data.ts
route.ts // ✅ /comments (GET 목록, POST 추가)
[id]/
route.ts // ✅ /comments/:id (GET 1건, PATCH 수정, DELETE 삭제)
2) 예제 데이터 (data.ts)
// src/app/comments/data.ts
export const comments = [
{ id: 1, text: "This is the first comment" },
{ id: 2, text: "This is the second comment" },
{ id: 3, text: "This is the third comment" },
];
참고: 메모리 배열이므로 서버 재시작 시 초기화됩니다. 학습용 예제로만 적합합니다.
3) /comments 라우트 (목록 조회 + 생성) — src/app/comments/route.ts
아래 파일 하나로 다음이 처리됩니다.
- GET /comments : 전체 목록 반환
- POST /comments : { text }를 받아 새 댓글 생성(201 반환)
또한 Route Handler는 기본적으로 캐시되지 않지만(특히 POST 등), 학습 예제에서 “항상 요청 시점 실행”을 확실히 하고 싶으시면 dynamic = 'force-dynamic' 같은 Route Segment 옵션을 둘 수도 있습니다. nextjs.org+1
// src/app/comments/route.ts
import { NextResponse } from "next/server";
import { comments } from "./data";
// (선택) 항상 요청 시점 실행을 강제하고 싶으시면 사용하세요.
export const dynamic = "force-dynamic";
export async function GET() {
// 목록 조회
return NextResponse.json(comments, { status: 200 });
}
export async function POST(request: Request) {
// JSON 파싱(잘못된 JSON이면 request.json()에서 예외가 날 수 있습니다)
let body: any;
try {
body = await request.json();
} catch {
return NextResponse.json(
{ error: "INVALID_JSON", message: "요청 본문이 올바른 JSON 형식이 아닙니다." },
{ status: 400 }
);
}
const text = body?.text;
if (typeof text !== "string" || text.trim().length === 0) {
return NextResponse.json(
{ error: "INVALID_BODY", message: "text는 비어있지 않은 문자열이어야 합니다." },
{ status: 400 }
);
}
// 새 id 생성(학습용: 단순 max + 1)
const nextId = (comments.reduce((max, c) => Math.max(max, c.id), 0) || 0) + 1;
const newComment = { id: nextId, text: text.trim() };
comments.push(newComment);
// 201 Created + Location 헤더(선택)
return NextResponse.json(newComment, {
status: 201,
headers: { Location: `/comments/${newComment.id}` },
});
}
4) /comments/:id 라우트 (단건 조회 + 수정 + 삭제) — src/app/comments/[id]/route.ts
여기서는 아래를 처리합니다.
- GET /comments/:id : 단건 조회
- PATCH /comments/:id : { text }로 수정
- DELETE /comments/:id : 삭제 후 삭제된 객체 반환
핵심 포인트는 findIndex() 결과 체크를 반드시 index === -1로 하셔야 한다는 점입니다. (0번 인덱스 오판 방지)
// src/app/comments/[id]/route.ts
import { NextResponse } from "next/server";
import { comments } from "../data";
// (선택) 항상 요청 시점 실행
export const dynamic = "force-dynamic";
function parseId(id: string): number | null {
const n = Number(id);
return Number.isInteger(n) ? n : null;
}
export async function GET(
_request: Request,
{ params }: { params: { id: string } }
) {
const commentId = parseId(params.id);
if (commentId === null) {
return NextResponse.json(
{ error: "INVALID_ID", message: "id 값이 올바르지 않습니다." },
{ status: 400 }
);
}
const comment = comments.find((c) => c.id === commentId);
if (!comment) {
return NextResponse.json(
{ error: "COMMENT_NOT_FOUND", message: "요청하신 답변을 찾을 수 없습니다." },
{ status: 404 }
);
}
return NextResponse.json(comment, { status: 200 });
}
export async function PATCH(
request: Request,
{ params }: { params: { id: string } }
) {
const commentId = parseId(params.id);
if (commentId === null) {
return NextResponse.json(
{ error: "INVALID_ID", message: "id 값이 올바르지 않습니다." },
{ status: 400 }
);
}
let body: any;
try {
body = await request.json();
} catch {
return NextResponse.json(
{ error: "INVALID_JSON", message: "요청 본문이 올바른 JSON 형식이 아닙니다." },
{ status: 400 }
);
}
const text = body?.text;
if (typeof text !== "string" || text.trim().length === 0) {
return NextResponse.json(
{ error: "INVALID_BODY", message: "text는 비어있지 않은 문자열이어야 합니다." },
{ status: 400 }
);
}
const index = comments.findIndex((c) => c.id === commentId);
if (index === -1) {
return NextResponse.json(
{ error: "COMMENT_NOT_FOUND", message: "요청하신 답변을 찾을 수 없습니다." },
{ status: 404 }
);
}
comments[index].text = text.trim();
return NextResponse.json(comments[index], { status: 200 });
}
export async function DELETE(
_request: Request,
{ params }: { params: { id: string } }
) {
const commentId = parseId(params.id);
if (commentId === null) {
return NextResponse.json(
{ error: "INVALID_ID", message: "id 값이 올바르지 않습니다." },
{ status: 400 }
);
}
const index = comments.findIndex((c) => c.id === commentId);
if (index === -1) {
return NextResponse.json(
{ error: "COMMENT_NOT_FOUND", message: "요청하신 답변을 찾을 수 없습니다." },
{ status: 404 }
);
}
const deleted = comments[index];
comments.splice(index, 1);
return NextResponse.json(deleted, { status: 200 });
}
5) 테스트 예시 (요청/응답)
(1) 목록 조회
GET http://localhost:3000/comments
응답(200)
[
{ "id": 1, "text": "This is the first comment" },
{ "id": 2, "text": "This is the second comment" },
{ "id": 3, "text": "This is the third comment" }
]
(2) 추가
POST http://localhost:3000/comments
Content-Type: application/json
{ "text": "new comment" }
응답(201)
{ "id": 4, "text": "new comment" }
(3) 단건 조회
GET http://localhost:3000/comments/4
(4) 수정
PATCH http://localhost:3000/comments/4
Content-Type: application/json
{ "text": "updated comment" }
(5) 삭제
DELETE http://localhost:3000/comments/4
6) 실무에서 꼭 알아두실 점(간단)
- 이 예제는 메모리 배열이라서 “서버 재시작/멀티 인스턴스” 환경에서는 데이터 일관성이 보장되지 않습니다.
- 실서비스에서는 DB(예: PostgreSQL/MSSQL) + 트랜잭션 기반으로 CRUD를 구현하셔야 합니다.
- /comments에 UI도 필요하시면, 라우트 충돌을 피하기 위해 API는 /api/comments로 분리하는 구성이 일반적입니다. nextjs.org
반응형
'Web Devlopment > NextJs' 카테고리의 다른 글
| #23. URL Query Parameters (0) | 2025.12.29 |
|---|---|
| #20. Parallel Routes (0) | 2025.12.28 |
| #19. Error Handling 실전 (0) | 2025.12.28 |
| #18. Loading UI , Error Handling (0) | 2025.12.28 |
| #17. Templates (vs layout) (0) | 2025.12.28 |
