설계는 AI에게 맡겨라 — ERD, API, 와이어프레임 자동화

@JavaPark · April 04, 2026 · 17 min read

안녕하세요, 자바파커입니다.

"코드부터 짜고 싶은 충동, 이해합니다. 하지만 설계 없이 시작한 프로젝트의 결말은 대부분 같습니다."

지난 포스팅에서 AI와 함께 아이디어를 검증하고 PRD를 작성하는 과정을 다뤘습니다. 이번에는 그다음 단계인 설계를 AI에게 맡기는 방법을 공유합니다. ERD, API 명세, 와이어프레임, 프로젝트 구조까지 — Claude Code 하나로 해결하는 과정을 보여드리겠습니다.


설계를 건너뛰면 생기는 일

1인 개발자가 가장 흔히 하는 실수 중 하나는 "일단 코드부터 짜자"입니다. 저도 수없이 겪었습니다.

  • 테이블 하나 추가하려니 기존 구조와 충돌
  • API 엔드포인트 네이밍이 중구난방
  • 화면을 만들다 보니 필요한 데이터가 빠져 있어서 DB 수정
  • 결국 전체 리팩토링 — 사실상 처음부터 다시 만들기

설계에 2-3시간만 투자하면, 개발 과정에서 수십 시간을 아낄 수 있습니다. 그리고 이제는 그 2-3시간마저 AI가 크게 줄여줍니다.


ERD 설계 — Claude Code에게 데이터베이스 설계 맡기기

프롬프트: Supabase 테이블 설계 요청

Claude Code에 아래와 같이 요청합니다. 핵심은 지난 포스팅에서 만든 PRD를 컨텍스트로 함께 전달하는 것입니다.

이 PRD를 기반으로 Supabase(PostgreSQL) 데이터베이스를 설계해줘.

요구사항:
- users, books, reading_records, memos 테이블
- Supabase Auth의 auth.users와 연동
- RLS(Row Level Security) 정책 포함
- created_at, updated_at 자동 관리
- 적절한 인덱스 포함

결과물:
1. ERD (텍스트 기반)
2. Supabase SQL (테이블 생성 + RLS + 인덱스)

Claude Code가 생성한 ERD

┌─────────────────┐     ┌──────────────────────┐
│     profiles     │     │        books          │
├─────────────────┤     ├──────────────────────┤
│ id (uuid, PK)   │──┐  │ id (uuid, PK)         │
│ username         │  │  │ isbn (varchar, UQ)     │
│ avatar_url       │  │  │ title                  │
│ created_at       │  │  │ author                 │
│ updated_at       │  │  │ publisher              │
└─────────────────┘  │  │ cover_image_url        │
                      │  │ page_count             │
                      │  │ published_date         │
                      │  │ description            │
                      │  │ created_at             │
                      │  └──────────────────────┘
                      │
                      │  ┌──────────────────────┐
                      │  │   reading_records      │
                      │  ├──────────────────────┤
                      ├──│ user_id (uuid, FK)     │
                      │  │ book_id (uuid, FK)     │
                      │  │ status (enum)          │
                      │  │ start_date             │
                      │  │ end_date               │
                      │  │ rating (1-5)           │
                      │  │ created_at             │
                      │  │ updated_at             │
                      │  └──────────────────────┘
                      │
                      │  ┌──────────────────────┐
                      │  │       memos            │
                      │  ├──────────────────────┤
                      └──│ user_id (uuid, FK)     │
                         │ book_id (uuid, FK)     │
                         │ content (text)         │
                         │ page_number (int)      │
                         │ memo_type (enum)       │
                         │ created_at             │
                         │ updated_at             │
                         └──────────────────────┘

Supabase SQL — 테이블 생성

-- 1. 커스텀 타입 정의
CREATE TYPE reading_status AS ENUM ('want_to_read', 'reading', 'finished');
CREATE TYPE memo_type AS ENUM ('note', 'quote', 'review');

-- 2. profiles 테이블 (Supabase Auth 연동)
CREATE TABLE profiles (
  id UUID REFERENCES auth.users(id) ON DELETE CASCADE PRIMARY KEY,
  username TEXT UNIQUE,
  avatar_url TEXT,
  created_at TIMESTAMPTZ DEFAULT NOW(),
  updated_at TIMESTAMPTZ DEFAULT NOW()
);

-- 3. books 테이블
CREATE TABLE books (
  id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
  isbn VARCHAR(13) UNIQUE,
  title TEXT NOT NULL,
  author TEXT NOT NULL,
  publisher TEXT,
  cover_image_url TEXT,
  page_count INTEGER,
  published_date DATE,
  description TEXT,
  created_at TIMESTAMPTZ DEFAULT NOW()
);

-- 4. reading_records 테이블
CREATE TABLE reading_records (
  id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
  user_id UUID REFERENCES profiles(id) ON DELETE CASCADE NOT NULL,
  book_id UUID REFERENCES books(id) ON DELETE CASCADE NOT NULL,
  status reading_status DEFAULT 'want_to_read',
  start_date DATE,
  end_date DATE,
  rating SMALLINT CHECK (rating >= 1 AND rating <= 5),
  created_at TIMESTAMPTZ DEFAULT NOW(),
  updated_at TIMESTAMPTZ DEFAULT NOW(),
  UNIQUE(user_id, book_id)
);

-- 5. memos 테이블
CREATE TABLE memos (
  id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
  user_id UUID REFERENCES profiles(id) ON DELETE CASCADE NOT NULL,
  book_id UUID REFERENCES books(id) ON DELETE CASCADE NOT NULL,
  content TEXT NOT NULL,
  page_number INTEGER,
  memo_type memo_type DEFAULT 'note',
  created_at TIMESTAMPTZ DEFAULT NOW(),
  updated_at TIMESTAMPTZ DEFAULT NOW()
);

-- 6. 인덱스
CREATE INDEX idx_reading_records_user ON reading_records(user_id);
CREATE INDEX idx_reading_records_status ON reading_records(user_id, status);
CREATE INDEX idx_memos_user_book ON memos(user_id, book_id);
CREATE INDEX idx_books_isbn ON books(isbn);

-- 7. updated_at 자동 갱신 트리거
CREATE OR REPLACE FUNCTION update_updated_at()
RETURNS TRIGGER AS $$
BEGIN
  NEW.updated_at = NOW();
  RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER profiles_updated_at
  BEFORE UPDATE ON profiles
  FOR EACH ROW EXECUTE FUNCTION update_updated_at();

CREATE TRIGGER reading_records_updated_at
  BEFORE UPDATE ON reading_records
  FOR EACH ROW EXECUTE FUNCTION update_updated_at();

CREATE TRIGGER memos_updated_at
  BEFORE UPDATE ON memos
  FOR EACH ROW EXECUTE FUNCTION update_updated_at();

RLS 정책 설정 — 보안의 핵심

Supabase에서 RLS는 선택이 아니라 필수입니다. 각 사용자가 자신의 데이터만 읽고 쓸 수 있도록 정책을 설정합니다.

-- RLS 활성화
ALTER TABLE profiles ENABLE ROW LEVEL SECURITY;
ALTER TABLE reading_records ENABLE ROW LEVEL SECURITY;
ALTER TABLE memos ENABLE ROW LEVEL SECURITY;

-- profiles: 본인만 조회/수정
CREATE POLICY "Users can view own profile"
  ON profiles FOR SELECT
  USING (auth.uid() = id);

CREATE POLICY "Users can update own profile"
  ON profiles FOR UPDATE
  USING (auth.uid() = id);

-- reading_records: 본인만 CRUD
CREATE POLICY "Users can manage own records"
  ON reading_records FOR ALL
  USING (auth.uid() = user_id);

-- memos: 본인만 CRUD
CREATE POLICY "Users can manage own memos"
  ON memos FOR ALL
  USING (auth.uid() = user_id);

-- books: 누구나 조회 가능, 인증된 사용자만 추가 가능
ALTER TABLE books ENABLE ROW LEVEL SECURITY;

CREATE POLICY "Anyone can view books"
  ON books FOR SELECT
  USING (true);

CREATE POLICY "Authenticated users can insert books"
  ON books FOR INSERT
  WITH CHECK (auth.role() = 'authenticated');

API 명세 자동 생성 — RESTful 엔드포인트 설계

Supabase는 테이블 기반으로 자동 REST API를 제공하지만, 프론트엔드에서 사용할 서비스 레이어의 함수 명세를 미리 정의해두면 개발이 훨씬 매끄럽습니다.

API 명세 생성 프롬프트

위 DB 스키마 기반으로 프론트엔드에서 사용할 API 함수 명세를 만들어줘.

조건:
- Supabase JS Client 사용
- TypeScript 타입 정의 포함
- 에러 처리 패턴 포함

카테고리:
1. Auth (로그인, 로그아웃, 프로필)
2. Books (검색, 등록)
3. Reading Records (CRUD, 상태 변경)
4. Memos (CRUD)

생성된 TypeScript 타입 정의

// types/database.ts

export type ReadingStatus = 'want_to_read' | 'reading' | 'finished';
export type MemoType = 'note' | 'quote' | 'review';

export interface Profile {
  id: string;
  username: string | null;
  avatar_url: string | null;
  created_at: string;
  updated_at: string;
}

export interface Book {
  id: string;
  isbn: string | null;
  title: string;
  author: string;
  publisher: string | null;
  cover_image_url: string | null;
  page_count: number | null;
  published_date: string | null;
  description: string | null;
  created_at: string;
}

export interface ReadingRecord {
  id: string;
  user_id: string;
  book_id: string;
  status: ReadingStatus;
  start_date: string | null;
  end_date: string | null;
  rating: number | null;
  created_at: string;
  updated_at: string;
  book?: Book; // join 시
}

export interface Memo {
  id: string;
  user_id: string;
  book_id: string;
  content: string;
  page_number: number | null;
  memo_type: MemoType;
  created_at: string;
  updated_at: string;
}

서비스 함수 명세 예시

// services/books.ts
import { supabase } from '@/lib/supabase';
import type { Book } from '@/types/database';

// ISBN으로 책 검색 (외부 API)
export async function searchBookByISBN(isbn: string): Promise<Book | null> {
  // 1. 먼저 DB에서 검색
  const { data } = await supabase
    .from('books')
    .select('*')
    .eq('isbn', isbn)
    .single();

  if (data) return data;

  // 2. DB에 없으면 알라딘 API로 검색 후 DB에 저장
  const bookInfo = await fetchFromAladinAPI(isbn);
  if (!bookInfo) return null;

  const { data: newBook, error } = await supabase
    .from('books')
    .insert(bookInfo)
    .select()
    .single();

  if (error) throw error;
  return newBook;
}

// 제목으로 책 검색
export async function searchBooksByTitle(
  query: string
): Promise<Book[]> {
  const { data, error } = await supabase
    .from('books')
    .select('*')
    .ilike('title', `%${query}%`)
    .limit(20);

  if (error) throw error;
  return data ?? [];
}

와이어프레임 — AI로 화면 구조 잡기

디자이너 없이 화면을 설계해야 하는 1인 개발자에게, 텍스트 기반 와이어프레임은 매우 실용적인 방법입니다.

와이어프레임 생성 프롬프트

독서 기록 웹앱의 주요 화면 와이어프레임을 텍스트로 만들어줘.

필요한 화면:
1. 대시보드 (내 독서 목록)
2. 책 상세 페이지
3. 책 등록 페이지 (ISBN 검색)
4. 메모 작성 페이지

각 화면에 포함할 것:
- 레이아웃 구조 (ASCII 아트)
- 필요한 컴포넌트 목록
- 사용자 인터랙션 흐름

대시보드 와이어프레임

┌─────────────────────────────────────────────────┐
│  [Logo] BookLog        [검색]    [프로필 아바타] │
├─────────────────────────────────────────────────┤
│                                                  │
│  ┌─ 탭 ──────────────────────────────────────┐  │
│  │ [읽는 중 (3)]  [완료 (12)]  [읽고 싶은 (5)]│  │
│  └───────────────────────────────────────────┘  │
│                                                  │
│  ┌─────────┐  ┌─────────┐  ┌─────────┐         │
│  │ [커버]  │  │ [커버]  │  │ [커버]  │         │
│  │ 제목    │  │ 제목    │  │ 제목    │         │
│  │ 저자    │  │ 저자    │  │ 저자    │         │
│  │ ★★★★☆ │  │ ★★★☆☆ │  │         │         │
│  │ 메모 3  │  │ 메모 1  │  │ [등록]  │         │
│  └─────────┘  └─────────┘  └─────────┘         │
│                                                  │
│              [+ 새 책 등록]                      │
│                                                  │
├─────────────────────────────────────────────────┤
│  올해 읽은 책: 12권 / 목표 24권  [████░░] 50%   │
└─────────────────────────────────────────────────┘

이 와이어프레임을 기반으로 실제 UI를 구현할 때, Claude Code에게 "이 와이어프레임대로 Tailwind CSS로 구현해줘"라고 요청하면 꽤 정확한 결과를 얻을 수 있습니다.


기술 스택 선정 — Next.js + Supabase + Vercel 조합의 이유

1인 개발자에게 기술 스택 선택은 생산성이 최우선입니다. 화려한 스택보다 빠르게 만들고, 쉽게 배포하고, 저렴하게 운영할 수 있는 조합이 중요합니다.

레이어 선택 이유
프론트엔드 Next.js (App Router) React 생태계, SSR/SSG 지원, Vercel 최적화
백엔드/DB Supabase PostgreSQL + Auth + Storage + Realtime 올인원
스타일링 Tailwind CSS 유틸리티 기반, AI가 생성하기 쉬움
배포 Vercel Git push로 자동 배포, 무료 플랜 충분
AI 개발 도구 Claude Code 코드 생성, 리팩토링, 디버깅

왜 Supabase인가?

솔직히 말하면, Firebase도 좋은 선택입니다. 하지만 Supabase를 선택한 이유는 세 가지입니다.

  1. PostgreSQL — 관계형 DB의 강력함 (JOIN, 트랜잭션)
  2. RLS — 별도 백엔드 서버 없이 보안 처리 가능
  3. 무료 플랜 — 사이드 프로젝트에 충분한 무료 티어 (500MB DB, 1GB Storage)

프로젝트 구조 생성 — Claude Code로 보일러플레이트 자동 생성

프로젝트 구조 생성 프롬프트

Next.js 14 (App Router) + Supabase + Tailwind CSS 프로젝트 구조를 만들어줘.

구조 규칙:
- app/ 디렉토리 사용 (App Router)
- 컴포넌트는 components/ 하위에 기능별로 분류
- Supabase 클라이언트는 lib/supabase.ts
- 타입 정의는 types/
- 서비스 로직은 services/

기능:
- 인증 (로그인/회원가입)
- 책 관리 (검색, 등록)
- 독서 기록 (CRUD)
- 메모 (CRUD)

Claude Code가 생성한 프로젝트 구조

book-log/
├── app/
│   ├── layout.tsx              # 루트 레이아웃
│   ├── page.tsx                # 랜딩 페이지
│   ├── globals.css
│   ├── (auth)/
│   │   ├── login/page.tsx      # 로그인
│   │   └── callback/route.ts   # OAuth 콜백
│   ├── (main)/
│   │   ├── layout.tsx          # 인증된 사용자 레이아웃
│   │   ├── dashboard/page.tsx  # 대시보드
│   │   ├── books/
│   │   │   ├── page.tsx        # 책 검색/등록
│   │   │   └── [id]/page.tsx   # 책 상세
│   │   └── memos/
│   │       └── [bookId]/page.tsx # 메모 목록
│   └── api/
│       └── books/
│           └── search/route.ts  # ISBN 검색 API
├── components/
│   ├── ui/                     # 공통 UI 컴포넌트
│   │   ├── Button.tsx
│   │   ├── Input.tsx
│   │   ├── Card.tsx
│   │   └── Modal.tsx
│   ├── auth/
│   │   └── AuthButton.tsx
│   ├── books/
│   │   ├── BookCard.tsx
│   │   ├── BookSearch.tsx
│   │   └── BookList.tsx
│   ├── records/
│   │   ├── StatusBadge.tsx
│   │   └── RecordForm.tsx
│   └── memos/
│       ├── MemoCard.tsx
│       └── MemoEditor.tsx
├── lib/
│   ├── supabase.ts             # Supabase 클라이언트 (브라우저)
│   └── supabase-server.ts      # Supabase 클라이언트 (서버)
├── services/
│   ├── auth.ts
│   ├── books.ts
│   ├── records.ts
│   └── memos.ts
├── types/
│   └── database.ts             # DB 타입 정의
├── middleware.ts                # 인증 미들웨어
└── .env.local                  # 환경 변수

Supabase 클라이언트 설정 코드

// lib/supabase.ts
import { createBrowserClient } from '@supabase/ssr';

export const supabase = createBrowserClient(
  process.env.NEXT_PUBLIC_SUPABASE_URL!,
  process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
);
// lib/supabase-server.ts
import { createServerClient } from '@supabase/ssr';
import { cookies } from 'next/headers';

export async function createClient() {
  const cookieStore = await cookies();

  return createServerClient(
    process.env.NEXT_PUBLIC_SUPABASE_URL!,
    process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
    {
      cookies: {
        getAll() {
          return cookieStore.getAll();
        },
        setAll(cookiesToSet) {
          cookiesToSet.forEach(({ name, value, options }) =>
            cookieStore.set(name, value, options)
          );
        },
      },
    }
  );
}

설계 단계에서 Claude Code를 잘 쓰는 팁

CLAUDE.md 파일 활용하기

프로젝트 루트에 CLAUDE.md 파일을 만들어서 프로젝트 컨텍스트를 미리 정의해두면, Claude Code가 훨씬 일관된 결과를 생성합니다.

# BookLog 프로젝트

## 기술 스택
- Next.js 14 (App Router)
- Supabase (PostgreSQL, Auth, Storage)
- Tailwind CSS
- TypeScript

## 코딩 컨벤션
- 컴포넌트: PascalCase (BookCard.tsx)
- 함수/변수: camelCase
- 타입: PascalCase (interface Book)
- 파일 구조: 기능별 분류

## DB 스키마
- profiles, books, reading_records, memos
- RLS 사용, auth.uid() 기반 권한 관리

자주 묻는 질문 (FAQ)

Q1. Supabase를 처음 쓰는데, RLS 설정이 어렵지 않나요?

솔직히 처음에는 헷갈립니다. 하지만 패턴이 정해져 있어서 한 번 이해하면 쉽습니다. 핵심은 auth.uid() = user_id 하나입니다. "이 행의 user_id가 로그인한 사용자의 id와 같으면 허용"이라는 뜻이죠. 이 포스팅의 SQL을 그대로 사용하셔도 됩니다.

Q2. ERD 설계를 AI에게 맡기면 문제가 생기지 않나요?

AI가 생성한 스키마를 그대로 프로덕션에 쓰면 문제가 될 수 있습니다. 하지만 초기 설계의 출발점으로는 훌륭합니다. 중요한 건 생성된 스키마를 직접 검토하는 것입니다. 특히 인덱스, 제약 조건, 데이터 타입은 반드시 확인하세요.

Q3. 와이어프레임을 텍스트로 만드는 게 Figma보다 나은가요?

"나은가"보다는 "빠른가"의 문제입니다. Figma로 완성도 높은 디자인을 만들 수 있지만, 1인 개발자에게 그 시간은 사치일 수 있습니다. 텍스트 와이어프레임으로 구조를 먼저 잡고, 필요하면 나중에 Figma로 다듬는 순서를 추천합니다.


여러분은 사이드 프로젝트 설계할 때 어떤 도구를 쓰고 계신가요? 댓글로 공유해주세요!

다음 포스팅에서는 "AI로 풀스택 개발하기 — Claude Code + Supabase 실전"을 주제로, 실제 코드를 작성하며 웹앱을 완성하는 과정을 다뤄보겠습니다.

@JavaPark
AI 시대의 개발자 도구, 실전 경험을 공유합니다