こんにちは、松山です。
これは「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篇です。今日はゼロから完全なアプリケーションを作成しました。次回はテスト領域に深く入り込みます!
あなたのプロジェクトを始めてください。次回もお楽しみに! 👋

