Tips & Tricks

Claude Code로 REST API 만들기 | 초보자를 위한 실전 입문 가이드

Claude Code와 함께 REST API 기초를 배우는 입문 가이드. 엔드포인트 설계부터 유효성 검사, 에러 처리까지 복붙 가능한 코드로 친절하게 설명합니다.

“REST API 어디서부터 시작해야 할지 모르겠어요” — 저도 처음엔 그랬습니다.

문서를 읽어도 너무 추상적이고, 결국 뭘 만들어야 할지 감이 오질 않았죠. 그때 Claude Code를 사용하기 시작하면서 “일단 동작하는 걸 만들면서 배우는” 접근법이 가장 빠르다는 걸 깨달았습니다.

이 글에서는 REST API 기초 지식이 전혀 없는 상태에서 실제로 동작하는 API를 만들기까지를 Claude Code와 함께 해봅니다. 코드는 모두 복붙하면 바로 동작하는 수준으로 작성했습니다.


REST API란 무엇인가요? (3줄 요약)

REST API는 웹 상의 리소스(데이터)를 HTTP로 조작하기 위한 약속입니다.

브라우저/앱  →  HTTP 요청  →  서버 (API)
            ←  JSON 응답  ←

사용자 정보를 관리하는 API라면:

하고 싶은 것HTTP 메서드URL
사용자 목록 조회GET/users
특정 사용자 조회GET/users/123
사용자 생성POST/users
사용자 수정PUT/users/123
사용자 삭제DELETE/users/123

이것만 이해하면 Claude Code와 함께 만들기 시작할 수 있습니다.


환경 셋업

이번에는 Hono (경량 웹 프레임워크의 TypeScript 버전)를 사용합니다. Express보다 타입 안전하고 Claude Code와의 궁합도 최고입니다.

mkdir my-first-api
cd my-first-api
npm init -y
npm install hono
npm install -D typescript @types/node ts-node
npx tsc --init

src/index.ts를 만들고 Claude Code에게 물어봅니다:

claude -p "
Hono를 사용한 REST API 보일러플레이트를 src/index.ts에 만들어줘.
- 포트 3000으로 서버 시작
- 헬스 체크용 GET /health 엔드포인트
- JSON 반환 설정
실행 명령: npx ts-node src/index.ts
"

Claude Code가 다음과 같은 코드를 생성해 줍니다:

// src/index.ts
import { Hono } from "hono";
import { serve } from "@hono/node-server";

const app = new Hono();

app.get("/health", (c) => {
  return c.json({ status: "ok", timestamp: new Date().toISOString() });
});

serve({ fetch: app.fetch, port: 3000 }, () => {
  console.log("Server running at http://localhost:3000");
});
npx ts-node src/index.ts
# → Server running at http://localhost:3000

curl http://localhost:3000/health
# → {"status":"ok","timestamp":"2026-04-27T07:30:00.000Z"}

동작했나요? 이게 REST API의 출발점입니다.


Step 1: 기본 CRUD 만들기

다음으로 Todo 목록을 관리하는 API를 만들어 봅니다. Claude Code에게 부탁해 봅시다.

claude -p "
src/index.ts에 Todo CRUD 엔드포인트를 추가해줘.
- GET  /todos         — 목록 조회
- GET  /todos/:id     — 단건 조회
- POST /todos         — 생성 (title: string 필수)
- PUT  /todos/:id     — 수정 (title: string)
- DELETE /todos/:id   — 삭제

데이터는 메모리 배열로 관리.
Todo 타입: { id: string, title: string, done: boolean, createdAt: string }
id는 crypto.randomUUID()로 생성
"

생성되는 코드:

import { Hono } from "hono";
import { serve } from "@hono/node-server";

const app = new Hono();

// 메모리 데이터 스토어
type Todo = { id: string; title: string; done: boolean; createdAt: string };
let todos: Todo[] = [];

// 목록 조회
app.get("/todos", (c) => c.json(todos));

// 단건 조회
app.get("/todos/:id", (c) => {
  const todo = todos.find((t) => t.id === c.req.param("id"));
  if (!todo) return c.json({ error: "Not found" }, 404);
  return c.json(todo);
});

// 생성
app.post("/todos", async (c) => {
  const body = await c.req.json();
  if (!body.title) return c.json({ error: "title is required" }, 400);
  const todo: Todo = {
    id: crypto.randomUUID(),
    title: body.title,
    done: false,
    createdAt: new Date().toISOString(),
  };
  todos.push(todo);
  return c.json(todo, 201);
});

// 수정
app.put("/todos/:id", async (c) => {
  const idx = todos.findIndex((t) => t.id === c.req.param("id"));
  if (idx === -1) return c.json({ error: "Not found" }, 404);
  const body = await c.req.json();
  todos[idx] = { ...todos[idx], ...body };
  return c.json(todos[idx]);
});

// 삭제
app.delete("/todos/:id", (c) => {
  const idx = todos.findIndex((t) => t.id === c.req.param("id"));
  if (idx === -1) return c.json({ error: "Not found" }, 404);
  todos.splice(idx, 1);
  return c.json({ message: "Deleted" });
});

serve({ fetch: app.fetch, port: 3000 }, () => {
  console.log("Server running at http://localhost:3000");
});

테스트해 봅시다:

# Todo 생성
curl -X POST http://localhost:3000/todos \
  -H "Content-Type: application/json" \
  -d '{"title": "Claude Code 써보기"}'

# 목록 조회
curl http://localhost:3000/todos

# 삭제 (위에서 반환된 id 사용)
curl -X DELETE http://localhost:3000/todos/<id>

Step 2: 유효성 검사 추가

“title이 비어 있어도 만들어진다” “id가 UUID 형식이 아니어도 접근된다” — 이런 문제를 유효성 검사로 방지합니다. Claude Code에게 부탁해 봅시다.

claude -p "
zod를 사용해서 POST /todos와 PUT /todos/:id의 유효성 검사를 추가해줘.
- title: 1자 이상 100자 이하의 문자열
- done: (PUT 전용) boolean
유효성 검사 실패 시 400 에러와 구체적인 에러 메시지 반환
"
npm install zod

Claude Code가 zod 스키마를 추가해 줍니다:

import { z } from "zod";

const CreateTodoSchema = z.object({
  title: z.string().min(1, "제목은 1자 이상이어야 합니다").max(100, "제목은 100자 이하여야 합니다"),
});

const UpdateTodoSchema = z.object({
  title: z.string().min(1).max(100).optional(),
  done: z.boolean().optional(),
});

// POST /todos (유효성 검사 포함)
app.post("/todos", async (c) => {
  const body = await c.req.json().catch(() => ({}));
  const result = CreateTodoSchema.safeParse(body);
  if (!result.success) {
    return c.json({ error: result.error.flatten().fieldErrors }, 400);
  }
  const todo: Todo = {
    id: crypto.randomUUID(),
    title: result.data.title,
    done: false,
    createdAt: new Date().toISOString(),
  };
  todos.push(todo);
  return c.json(todo, 201);
});

유효성 검사 오류 확인:

# title 없이 생성 → 에러가 발생해야 함
curl -X POST http://localhost:3000/todos \
  -H "Content-Type: application/json" \
  -d '{}'
# → {"error":{"title":["제목은 1자 이상이어야 합니다"]}}

Step 3: 에러 처리 통일

지금 코드는 각 엔드포인트마다 에러를 제각각 반환합니다. Claude Code에게 통일해 달라고 합시다.

claude -p "
에러 처리를 통일해줘.
- 공통 에러 응답 형식: { error: { code: string, message: string } }
- 404: NOT_FOUND
- 400: VALIDATION_ERROR
- 500: INTERNAL_SERVER_ERROR
Hono의 onError로 전역 에러 핸들러 구현
"
// 에러 타입 정의
class AppError extends Error {
  constructor(
    public code: string,
    public message: string,
    public statusCode: number = 400
  ) {
    super(message);
  }
}

// 전역 에러 핸들러
app.onError((err, c) => {
  if (err instanceof AppError) {
    return c.json(
      { error: { code: err.code, message: err.message } },
      err.statusCode as any
    );
  }
  console.error(err);
  return c.json(
    { error: { code: "INTERNAL_SERVER_ERROR", message: "예기치 않은 오류가 발생했습니다" } },
    500
  );
});

// 사용 예시
app.get("/todos/:id", (c) => {
  const todo = todos.find((t) => t.id === c.req.param("id"));
  if (!todo) throw new AppError("NOT_FOUND", "지정한 Todo를 찾을 수 없습니다", 404);
  return c.json(todo);
});

Step 4: Swagger UI로 문서 자동 생성

API를 만들었다면 문서도 필요합니다. Claude Code에게 부탁하면 몇 분 만에 완성됩니다.

claude -p "
@hono/swagger-ui와 @hono/zod-openapi를 사용해서
Swagger UI를 /docs에 추가해줘.
기존 엔드포인트에 OpenAPI 스키마도 추가해줘.
"
npm install @hono/swagger-ui @hono/zod-openapi

완성되면 http://localhost:3000/docs에 접근하면 API 문서가 표시됩니다.


Claude Code와의 API 개발 흐름 (정리)

제가 실제로 하는 개발 플로우입니다.

1. "이런 API가 필요해"라고 Claude Code에 전달
    ↓
2. 생성된 코드 확인 및 동작 테스트
    ↓
3. "여기 수정해줘" "유효성 검사 추가해줘"라고 대화
    ↓
4. 테스트 통과하면 git commit

처음에 막혔던 포인트 (실체험)

처음 API를 만들 때 에러 처리를 전부 생략해버려서 나중에 추가하는 데 시간이 많이 걸렸습니다. 지금은 처음부터 “에러 처리도 포함해서 만들어줘”라는 한 마디를 붙이면, 처음부터 제대로 된 코드가 나옵니다. Claude Code는 처음부터 요건을 전부 전달하는 게 핵심입니다.


자주 빠지는 함정 3가지

함정 1: JSON 파싱 에러를 무시한다

// ❌ 에러가 발생해도 알 수 없음
const body = await c.req.json();

// ✅ 파싱 실패를 잡아낸다
const body = await c.req.json().catch(() => null);
if (!body) return c.json({ error: "Invalid JSON" }, 400);

함정 2: 404와 400을 혼동한다

400: 요청 자체가 잘못됨 (유효성 검사 에러, 필수 항목 없음)
404: 리소스가 존재하지 않음 (ID를 찾을 수 없음)
422: 요청 형식은 맞지만 처리할 수 없음

Claude Code에게 “여기서 404를 반환해야 하나요 400을 반환해야 하나요”라고 물어보면 적절한 판단을 해줍니다.

함정 3: 전부 메모리에 저장한다

이 글의 코드는 데모용으로 메모리 배열을 사용하고 있습니다. 서버를 재시작하면 데이터가 사라집니다. 실제 서비스에는 PostgreSQL이나 MongoDB 같은 DB가 필요합니다. “메모리 대신 SQLite에 저장하도록 해줘”라고 Claude Code에게 부탁하면 마이그레이션해 줍니다.


다음 단계

REST API 기초를 마스터했다면 다음을 시도해 보세요.

단계내용
데이터베이스 연동SQLite → PostgreSQL (Prisma)
인증JWT 토큰 인증 추가
테스트vitest로 API 테스트 작성
배포Vercel / Cloudflare Workers에 공개

모두 ”○○ 추가해줘”라고 Claude Code에게 부탁하면 몇 분 만에 베이스가 완성됩니다. 먼저 동작하는 걸 만드는 것 — 그게 가장 빠른 지름길입니다.

관련 글

#claude-code #rest-api #beginner #typescript #backend #tutorial

Claude Code 워크플로우를 한 단계 업그레이드하세요

지금 바로 Claude Code에 복사해 쓸 수 있는 검증된 프롬프트 템플릿 50선.

무료 제공

무료 PDF: 5분 완성 Claude Code 치트시트

이메일 주소만 등록하시면 A4 한 장짜리 치트시트 PDF를 즉시 보내드립니다.

개인정보는 엄격하게 관리하며 스팸은 보내지 않습니다.

Masa

이 글을 작성한 사람

Masa

Claude Code를 적극 활용하는 엔지니어. 10개 언어, 2,000페이지 이상의 테크 미디어 claudecode-lab.com을 운영 중.