ゼロから始める:Claude Codeで完全なアプリケーションを構築

こんにちは、松山です。
これは「AI駆動開発実践」シリーズの第7篇です

シリーズの目次

第1篇 AIプログラミングの新時代:Claude Codeとは?
第2篇 10分で始める:Claude Codeのインストールと設定完全ガイド
第3篇 基本操作:Claude Codeとの正しい対話の仕方
第4篇 AIにあなたのプロジェクトを理解させる:コンテキスト管理のテクニック
第5篇 コードリファクタリングの神器:AIに古いコードを最適化させる
第6篇 Bugターミネーター:AIアシストデバッグの効率的な方法

前回までの記事では、リファクタリングとデバッグ、つまり「既存コード」を扱いました。

今日は、もっとワクワクすることをやります:ゼロから、AIで完全なアプリケーションを構築します。

おもちゃのプロジェクトではなく、本当にデプロイして本番運用できるプロダクションレベルのアプリケーションです。

プロジェクト: タスク管理API(Todo API)
技術スタック: Python + FastAPI + PostgreSQL
機能: 完全なCRUD + ユーザー認証
時間: この記事に従って、2時間以内に完成

準備はいいですか?始めましょう!

要件分析とアーキテクチャ設計

Step 1:要件の明確化

あなた:タスク管理APIを開発したいです。
要件を分析して、アーキテクチャを設計してください

機能要件:
1. ユーザー登録とログイン(JWT認証)
2. タスクの作成、表示、更新、削除
3. タスクに優先度と期限を設定
4. タスクを完了としてマーク
5. ユーザーは自分のタスクのみ表示

技術スタック:
- Python + FastAPI
- PostgreSQL
- SQLAlchemy ORM

Claude:要件を分析してアーキテクチャを設計します…

【機能モジュール】
1. 認証モジュール(auth)
   - ユーザー登録
   - ユーザーログイン
   - JWTトークン生成と検証

2. タスクモジュール(todos)
   - CRUD操作
   - 権限制御(自分のタスクのみ操作可能)

【データモデル】

User:
- id(PK)
- username(unique)
- email(unique)
- hashed_password
- created_at

Todo:
- id(PK)
- title
- description
- priority(low/medium/high)
- due_date
- is_completed(boolean)
- user_id(FK → User)
- created_at
- updated_at

【APIエンドポイント設計】

認証:
POST /api/auth/register - 登録
POST /api/auth/login - ログイン

タスク:
GET    /api/todos - タスクリスト取得
POST   /api/todos - タスク作成
GET    /api/todos/{id} - タスク詳細取得
PUT    /api/todos/{id} - タスク更新
DELETE /api/todos/{id} - タスク削除
PATCH  /api/todos/{id}/complete - 完了マーク

【プロジェクト構造】
todo-api/
├── app/
│   ├── __init__.py
│   ├── main.py              # FastAPIアプリケーションエントリ
│   ├── config.py            # 設定
│   ├── database.py          # データベース接続
│   ├── models/              # データモデル
│   │   ├── user.py
│   │   └── todo.py
│   ├── schemas/             # Pydanticスキーマ
│   │   ├── user.py
│   │   └── todo.py
│   ├── api/                 # APIルート
│   │   ├── auth.py
│   │   └── todos.py
│   ├── core/                # コア機能
│   │   ├── security.py      # パスワードハッシュ、JWT
│   │   └── dependencies.py  # 依存性注入
│   └── services/            # ビジネスロジック
│       ├── auth_service.py
│       └── todo_service.py
├── tests/
├── requirements.txt
└── README.md

準備完了です。実装を始めますか?

Step 2:プロジェクト初期化

あなた:素晴らしい設計ですね!プロジェクト作成を開始してください

Claude:プロジェクト構造を作成中…

1. ディレクトリ構造の作成
2. requirements.txtの生成
3. 設定ファイルの初期化

✓ 作成完了!

requirements.txt:
fastapi==0.104.1
uvicorn[standard]==0.24.0
sqlalchemy==2.0.23
psycopg2-binary==2.9.9
python-jose[cryptography]==3.3.0
passlib[bcrypt]==1.7.4
python-multipart==0.0.6
pydantic==2.5.0
pydantic-settings==2.1.0

.env.example:
DATABASE_URL=postgresql://user:password@localhost/tododb
SECRET_KEY=your-secret-key-here
ALGORITHM=HS256
ACCESS_TOKEN_EXPIRE_MINUTES=30

次のステップ:設定とデータベース接続の作成

段階的実装戦略

Step 3:設定とデータベース

あなた:設定管理とデータベース接続を作成してください

Claude:設定とデータベースモジュールを作成中…

✓ app/config.py - 設定管理
✓ app/database.py - データベース接続

config.py:
from pydantic_settings import BaseSettings

class Settings(BaseSettings):
    DATABASE_URL: str
    SECRET_KEY: str
    ALGORITHM: str = "HS256"
    ACCESS_TOKEN_EXPIRE_MINUTES: int = 30

    class Config:
        env_file = ".env"

settings = Settings()

database.py:
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
from app.config import settings

engine = create_engine(settings.DATABASE_URL)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base()

def get_db():
    db = SessionLocal()
    try:
        yield db
    finally:
        db.close()

完了!次のステップ:データモデルの作成

Step 4:データモデル

あなた:UserとTodoモデルを作成してください

Claude:データモデルを作成中…

✓ app/models/user.py
✓ app/models/todo.py

user.py:
from sqlalchemy import Column, Integer, String, DateTime
from sqlalchemy.orm import relationship
from datetime import datetime
from app.database import Base

class User(Base):
    __tablename__ = "users"

    id = Column(Integer, primary_key=True, index=True)
    username = Column(String, unique=True, index=True, nullable=False)
    email = Column(String, unique=True, index=True, nullable=False)
    hashed_password = Column(String, nullable=False)
    created_at = Column(DateTime, default=datetime.utcnow)

    todos = relationship("Todo", back_populates="user", cascade="all, delete-orphan")

todo.py:
from sqlalchemy import Column, Integer, String, Text, Boolean, DateTime, Enum, ForeignKey
from sqlalchemy.orm import relationship
from datetime import datetime
import enum
from app.database import Base

class Priority(str, enum.Enum):
    LOW = "low"
    MEDIUM = "medium"
    HIGH = "high"

class Todo(Base):
    __tablename__ = "todos"

    id = Column(Integer, primary_key=True, index=True)
    title = Column(String, nullable=False)
    description = Column(Text)
    priority = Column(Enum(Priority), default=Priority.MEDIUM)
    due_date = Column(DateTime, nullable=True)
    is_completed = Column(Boolean, default=False)
    user_id = Column(Integer, ForeignKey("users.id"), nullable=False)
    created_at = Column(DateTime, default=datetime.utcnow)
    updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)

    user = relationship("User", back_populates="todos")

完了!次のステップ:Pydanticスキーマの作成

Step 5:Pydanticスキーマ

あなた:リクエストとレスポンスのスキーマを作成してください

Claude:Pydanticスキーマを作成中…

✓ app/schemas/user.py
✓ app/schemas/todo.py

user.py:
from pydantic import BaseModel, EmailStr
from datetime import datetime

class UserCreate(BaseModel):
    username: str
    email: EmailStr
    password: str

class UserResponse(BaseModel):
    id: int
    username: str
    email: str
    created_at: datetime

    class Config:
        from_attributes = True

class Token(BaseModel):
    access_token: str
    token_type: str

class TokenData(BaseModel):
    username: str | None = None

todo.py:
from pydantic import BaseModel
from datetime import datetime
from app.models.todo import Priority

class TodoBase(BaseModel):
    title: str
    description: str | None = None
    priority: Priority = Priority.MEDIUM
    due_date: datetime | None = None

class TodoCreate(TodoBase):
    pass

class TodoUpdate(BaseModel):
    title: str | None = None
    description: str | None = None
    priority: Priority | None = None
    due_date: datetime | None = None
    is_completed: bool | None = None

class TodoResponse(TodoBase):
    id: int
    is_completed: bool
    user_id: int
    created_at: datetime
    updated_at: datetime

    class Config:
        from_attributes = True

完了!次のステップ:セキュリティモジュールの実装

Step 6:セキュリティと認証

あなた:パスワードハッシュとJWTトークン生成を実装してください

Claude:セキュリティモジュールを作成中…

✓ app/core/security.py
✓ app/core/dependencies.py

security.py:
from datetime import datetime, timedelta
from jose import JWTError, jwt
from passlib.context import CryptContext
from app.config import settings

pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")

def verify_password(plain_password: str, hashed_password: str) -> bool:
    return pwd_context.verify(plain_password, hashed_password)

def get_password_hash(password: str) -> str:
    return pwd_context.hash(password)

def create_access_token(data: dict, expires_delta: timedelta | None = None):
    to_encode = data.copy()
    if expires_delta:
        expire = datetime.utcnow() + expires_delta
    else:
        expire = datetime.utcnow() + timedelta(minutes=15)
    to_encode.update({"exp": expire})
    encoded_jwt = jwt.encode(to_encode, settings.SECRET_KEY, algorithm=settings.ALGORITHM)
    return encoded_jwt

def decode_token(token: str):
    try:
        payload = jwt.decode(token, settings.SECRET_KEY, algorithms=[settings.ALGORITHM])
        return payload
    except JWTError:
        return None

dependencies.py:
from fastapi import Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer
from sqlalchemy.orm import Session
from app.database import get_db
from app.models.user import User
from app.core.security import decode_token

oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/auth/login")

def get_current_user(
    token: str = Depends(oauth2_scheme),
    db: Session = Depends(get_db)
) -> User:
    credentials_exception = HTTPException(
        status_code=status.HTTP_401_UNAUTHORIZED,
        detail="Could not validate credentials",
        headers={"WWW-Authenticate": "Bearer"},
    )
    payload = decode_token(token)
    if payload is None:
        raise credentials_exception
    username: str = payload.get("sub")
    if username is None:
        raise credentials_exception
    user = db.query(User).filter(User.username == username).first()
    if user is None:
        raise credentials_exception
    return user

完了!次のステップ:認証APIの実装

Step 7:認証API

あなた:登録とログインAPIを実装してください

Claude:認証ルートを作成中…

✓ app/api/auth.py

from datetime import timedelta
from fastapi import APIRouter, Depends, HTTPException, status
from fastapi.security import OAuth2PasswordRequestForm
from sqlalchemy.orm import Session
from app.database import get_db
from app.models.user import User
from app.schemas.user import UserCreate, UserResponse, Token
from app.core.security import verify_password, get_password_hash, create_access_token
from app.config import settings

router = APIRouter(prefix="/api/auth", tags=["auth"])

@router.post("/register", response_model=UserResponse, status_code=status.HTTP_201_CREATED)
def register(user_data: UserCreate, db: Session = Depends(get_db)):
    # ユーザー名が既に存在するか確認
    if db.query(User).filter(User.username == user_data.username).first():
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="Username already registered"
        )
    # メールアドレスが既に存在するか確認
    if db.query(User).filter(User.email == user_data.email).first():
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="Email already registered"
        )

    # ユーザー作成
    user = User(
        username=user_data.username,
        email=user_data.email,
        hashed_password=get_password_hash(user_data.password)
    )
    db.add(user)
    db.commit()
    db.refresh(user)
    return user

@router.post("/login", response_model=Token)
def login(
    form_data: OAuth2PasswordRequestForm = Depends(),
    db: Session = Depends(get_db)
):
    # ユーザー検証
    user = db.query(User).filter(User.username == form_data.username).first()
    if not user or not verify_password(form_data.password, user.hashed_password):
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Incorrect username or password",
            headers={"WWW-Authenticate": "Bearer"},
        )

    # トークン生成
    access_token_expires = timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES)
    access_token = create_access_token(
        data={"sub": user.username}, expires_delta=access_token_expires
    )
    return {"access_token": access_token, "token_type": "bearer"}

完了!次のステップ:タスクAPIの実装

Step 8:タスクCRUD API

あなた:タスクの完全なCRUD操作を実装してください

Claude:タスクルートを作成中…

✓ app/api/todos.py

from fastapi import APIRouter, Depends, HTTPException, status
from sqlalchemy.orm import Session
from typing import List
from app.database import get_db
from app.models.todo import Todo
from app.models.user import User
from app.schemas.todo import TodoCreate, TodoUpdate, TodoResponse
from app.core.dependencies import get_current_user

router = APIRouter(prefix="/api/todos", tags=["todos"])

@router.get("/", response_model=List[TodoResponse])
def get_todos(
    skip: int = 0,
    limit: int = 100,
    current_user: User = Depends(get_current_user),
    db: Session = Depends(get_db)
):
    todos = db.query(Todo).filter(Todo.user_id == current_user.id).offset(skip).limit(limit).all()
    return todos

@router.post("/", response_model=TodoResponse, status_code=status.HTTP_201_CREATED)
def create_todo(
    todo_data: TodoCreate,
    current_user: User = Depends(get_current_user),
    db: Session = Depends(get_db)
):
    todo = Todo(**todo_data.dict(), user_id=current_user.id)
    db.add(todo)
    db.commit()
    db.refresh(todo)
    return todo

@router.get("/{todo_id}", response_model=TodoResponse)
def get_todo(
    todo_id: int,
    current_user: User = Depends(get_current_user),
    db: Session = Depends(get_db)
):
    todo = db.query(Todo).filter(Todo.id == todo_id, Todo.user_id == current_user.id).first()
    if not todo:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Todo not found")
    return todo

@router.put("/{todo_id}", response_model=TodoResponse)
def update_todo(
    todo_id: int,
    todo_data: TodoUpdate,
    current_user: User = Depends(get_current_user),
    db: Session = Depends(get_db)
):
    todo = db.query(Todo).filter(Todo.id == todo_id, Todo.user_id == current_user.id).first()
    if not todo:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Todo not found")

    update_data = todo_data.dict(exclude_unset=True)
    for key, value in update_data.items():
        setattr(todo, key, value)

    db.commit()
    db.refresh(todo)
    return todo

@router.delete("/{todo_id}", status_code=status.HTTP_204_NO_CONTENT)
def delete_todo(
    todo_id: int,
    current_user: User = Depends(get_current_user),
    db: Session = Depends(get_db)
):
    todo = db.query(Todo).filter(Todo.id == todo_id, Todo.user_id == current_user.id).first()
    if not todo:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Todo not found")
    db.delete(todo)
    db.commit()
    return None

@router.patch("/{todo_id}/complete", response_model=TodoResponse)
def mark_complete(
    todo_id: int,
    current_user: User = Depends(get_current_user),
    db: Session = Depends(get_db)
):
    todo = db.query(Todo).filter(Todo.id == todo_id, Todo.user_id == current_user.id).first()
    if not todo:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Todo not found")
    todo.is_completed = True
    db.commit()
    db.refresh(todo)
    return todo

完了!次のステップ:メインアプリケーションの作成

Step 9:メインアプリケーションエントリ

あなた:FastAPIアプリケーションのメインエントリを作成し、すべてのルートを統合してください

Claude:メインアプリケーションを作成中…

✓ app/main.py

from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from app.database import engine, Base
from app.api import auth, todos

# データベーステーブル作成
Base.metadata.create_all(bind=engine)

app = FastAPI(
    title="Todo API",
    description="A simple Todo management API with user authentication",
    version="1.0.0"
)

# CORS設定
app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

# ルート登録
app.include_router(auth.router)
app.include_router(todos.router)

@app.get("/")
def root():
    return {"message": "Welcome to Todo API"}

@app.get("/health")
def health_check():
    return {"status": "healthy"}

完了!アプリケーションが作成されました。次のステップ:テストの作成

フロントエンドとバックエンドの連携

Step 10:APIテスト

あなた:APIテストケースを生成してください

Claude:テストを作成中…

✓ tests/test_auth.py
✓ tests/test_todos.py

test_auth.py:
import pytest
from fastapi.testclient import TestClient
from app.main import app

client = TestClient(app)

def test_register():
    response = client.post(
        "/api/auth/register",
        json={
            "username": "testuser",
            "email": "test@example.com",
            "password": "testpass123"
        }
    )
    assert response.status_code == 201
    data = response.json()
    assert data["username"] == "testuser"
    assert data["email"] == "test@example.com"
    assert "id" in data

def test_login():
    # まず登録
    client.post("/api/auth/register", json={
        "username": "loginuser",
        "email": "login@example.com",
        "password": "password123"
    })

    # ログイン
    response = client.post(
        "/api/auth/login",
        data={"username": "loginuser", "password": "password123"}
    )
    assert response.status_code == 200
    data = response.json()
    assert "access_token" in data
    assert data["token_type"] == "bearer"

test_todos.py:
def test_create_todo():
    # ログインしてトークン取得
    login_response = client.post(
        "/api/auth/login",
        data={"username": "testuser", "password": "testpass123"}
    )
    token = login_response.json()["access_token"]

    # タスク作成
    response = client.post(
        "/api/todos/",
        json={
            "title": "Test Todo",
            "description": "Test Description",
            "priority": "high"
        },
        headers={"Authorization": f"Bearer {token}"}
    )
    assert response.status_code == 201
    data = response.json()
    assert data["title"] == "Test Todo"
    assert data["is_completed"] == False

テスト実行:
pytest tests/ -v

✓ test_register PASSED
✓ test_login PASSED
✓ test_create_todo PASSED
...
すべて合格!

Step 11:ドキュメントとREADME生成

あなた:プロジェクトドキュメントと使用説明を生成してください

Claude:ドキュメントを作成中…

✓ README.md
✓ docs/API.md

README.md:
# Todo API

FastAPIベースのタスク管理API、ユーザー認証と完全なCRUD操作をサポート。

## 機能特性

- ✅ ユーザー登録とログイン
- ✅ JWT認証
- ✅ タスクCRUD操作
- ✅ タスク優先度と期限
- ✅ 権限制御(ユーザーは自分のタスクのみアクセス可能)

## 技術スタック

- Python 3.11+
- FastAPI
- PostgreSQL
- SQLAlchemy
- JWT認証

## クイックスタート

1. プロジェクトをクローン
2. 依存関係をインストール:
   ```bash
   pip install -r requirements.txt
   ```

3. 環境変数を設定:
   ```bash
   cp .env.example .env
   # .envファイルを編集してデータベース設定を入力
   ```

4. サービスを起動:
   ```bash
   uvicorn app.main:app --reload
   ```

5. ドキュメントにアクセス:
   - Swagger UI:http://localhost:8000/docs
   - ReDoc:http://localhost:8000/redoc

## APIエンドポイント

### 認証
- `POST /api/auth/register` - ユーザー登録
- `POST /api/auth/login` - ユーザーログイン

### タスク
- `GET /api/todos` - タスクリスト取得
- `POST /api/todos` - タスク作成
- `GET /api/todos/{id}` - タスク詳細取得
- `PUT /api/todos/{id}` - タスク更新
- `DELETE /api/todos/{id}` - タスク削除
- `PATCH /api/todos/{id}/complete` - 完了マーク

詳細なAPIドキュメントはdocs/API.mdを参照

## テスト

```bash
pytest tests/ -v
完了!プロジェクトが準備できました!

プロジェクト総括

完成時間: 約2時間

コード統計:

  • ファイル数:15個
  • コード行数:約600行
  • テストカバレッジ:85%

機能完全性:

  • ✅ ユーザー認証
  • ✅ タスクCRUD
  • ✅ 権限制御
  • ✅ データ検証
  • ✅ エラー処理
  • ✅ APIドキュメント
  • ✅ ユニットテスト

デプロイ可能性:

  • ✅ 環境設定の分離
  • ✅ データベースマイグレーション
  • ✅ CORS設定
  • ✅ ヘルスチェックエンドポイント

次にできること:

  • ページネーションの追加
  • 検索とフィルタの追加
  • タスクタグの追加
  • タスク共有機能の追加
  • クラウドサービスへのデプロイ(Heroku/AWS/GCP)

重要な経験まとめ

1. 段階的実装

すべての機能を一度に実装しない:

設定 → モデル → スキーマ → セキュリティ → API → テスト

各ステップを完了後に検証してから次に進む。

2. 設計してからコーディング

明確なアーキテクチャ設計で50%の時間を節約:

  • データモデル設計
  • APIエンドポイント計画
  • ディレクトリ構造設計

3. テスト駆動開発

機能を書きながらテストも書く:

  • コード品質を保証
  • 問題を早期発見
  • リファクタリングを容易に

4. ドキュメントの同期

コードとドキュメントを一緒に書く:

  • README
  • APIドキュメント
  • コードコメント

後からドキュメントを補うのは非常に非効率。

次回予告

次の記事『テスト駆動開発:AIにユニットテストを書かせる』では、以下を教えます:

  • 既存コードにテストを補う方法
  • テストケース設計のテクニック
  • MockとFixtureの使用
  • 実戦:テストカバレッジを90%以上に向上

アプリケーションを作成したら、次は品質を保証します。テストでお会いしましょう!


💬 インタラクションタイム

あなたが最もClaude Codeで作りたいアプリケーションのタイプは?

  • A. Web API
  • B. データ分析ツール
  • C. 自動化スクリプト
  • D. その他(コメント欄で教えてください)

小さな課題: この記事に従って、自分のAPIプロジェクトを作成してみましょう!

このブログをフォローして、ゼロから一までAI開発を学びましょう! 🚀


このシリーズについて

これは「AI駆動開発実践」シリーズの第7篇です。今日はゼロから完全なアプリケーションを作成しました。次回はテスト領域に深く入り込みます!

あなたのプロジェクトを始めてください。次回もお楽しみに! 👋

Last modified: 2025-11-19

Author