Claude Code 从空目录到完整 API 全记录


前几篇分别在老项目上改功能、重构、写测试。这篇更进一步——从一个空目录开始。

我要建一个书签管理 API。收藏网页、打标签、搜索、导出。需求不复杂,但涉及模型设计、路由分层、测试、Docker 部署、文档。从头手写大概半个下午。

这次全交给 Claude Code,看看一个空目录能变成什么。


需求描述

在一个空目录下启动 Claude Code,输入:

我要建一个书签管理 REST API,技术栈 FastAPI + SQLite + SQLAlchemy。

功能:

  1. POST /bookmarks — 添加书签(url, title, tags 数组)
  2. GET /bookmarks — 获取所有书签,支持 ?tag=python 过滤
  3. GET /bookmarks/{id} — 获取单个书签
  4. DELETE /bookmarks/{id} — 删除书签
  5. GET /bookmarks/export — 导出为 JSON
  6. GET /health — 健康检查

要求:

  • URL 要做基本验证(http/https 开头)
  • tags 去重
  • 带 pytest 测试,用 parametrize
  • Dockerfile + docker-compose.yml
  • README.md 包含安装和使用说明
  • 所有测试通过后才能算完成

第一步:设计架构

Claude Code 没有直接撸代码。它先做了规划:

→ Project: bookmark-api
→ Plan:

  1. Project structure:
     bookmark_api/
     ├── main.py          # FastAPI app entry
     ├── models.py        # SQLAlchemy Bookmark model
     ├── schemas.py       # Pydantic request/response schemas
     ├── database.py      # DB connection + session
     ├── routes.py        # API endpoints
     ├── requirements.txt
     ├── Dockerfile
     ├── docker-compose.yml
     ├── README.md
     └── tests/
         ├── conftest.py      # fixtures (test client, test DB)
         └── test_bookmarks.py

  2. Data model:
     Bookmark: id, url, title, tags (JSON array), created_at

  3. Key decisions:
     - tags stored as JSON string in SQLite (no separate tags table
       for simplicity — this is a lightweight personal tool)
     - URL validation via Pydantic field validator
     - Test DB uses SQLite in-memory with session override

这个架构选择值得注意:tags 用 JSON 字符串存而不是独立表。Claude Code 给的判断依据是"轻量个人工具,不需要复杂关系查询"。如果是一个团队协作的标签系统,它会用第三范式。


第二步:一层一层建

Claude Code 从底层往上层建,每建一层跑一次验证。

database.py — 数据库连接层

[+] bookmark_api/database.py
  - SQLAlchemy engine with SQLite
  - SessionLocal factory
  - get_db dependency
  - Base declarative base

models.py — 数据模型

[+] bookmark_api/models.py
  class Bookmark(Base):
      id: int (PK)
      url: str (unique=True, indexed)
      title: str
      tags: str (JSON-encoded)
      created_at: datetime (default utcnow)

建完 model,Claude Code 立刻验证表能正常创建:

→ Tool: Bash
  $ python -c "from bookmark_api.database import engine, Base; \
    Base.metadata.create_all(engine); print('Tables created OK')"
  → Tables created OK

schemas.py — 请求/响应模型

[+] bookmark_api/schemas.py
  BookmarkCreate: url (HttpUrl), title (str, 1-200), tags (list[str])
  BookmarkResponse: id, url, title, tags, created_at
  → URL validation via Pydantic HttpUrl type
  → tags deduplication in model validator

用了 Pydantic 的 HttpUrl 类型做 URL 验证,tag 去重用 set() 在 validator 里处理。

routes.py — API 路由

[+] bookmark_api/routes.py
  POST /bookmarks        — create bookmark, return 201
  GET /bookmarks         — list all, ?tag= filter
  GET /bookmarks/{id}    — get by ID, 404 if missing
  DELETE /bookmarks/{id} — delete, 404 if missing
  GET /bookmarks/export  — export all as JSON with metadata
  GET /health            — {"status": "ok"}

GET /bookmarks 的 tag 过滤是这样实现的:

@app.get("/bookmarks")
def list_bookmarks(tag: str | None = None, db: Session = Depends(get_db)):
    query = db.query(Bookmark)
    if tag:
        query = query.filter(Bookmark.tags.contains(f'"{tag}"'))
    return query.order_by(Bookmark.created_at.desc()).all()

没有用原始的 LIKE '%tag%',而是 contains(f'"{tag}"')——因为 tags 存的是 ["python", "web"] 这种 JSON 字符串,用引号包裹匹配避免了 “py” 匹配到 “python” 的部分字符串问题。

main.py — 入口

[+] bookmark_api/main.py
  - FastAPI app with CORS middleware
  - Include router
  - Startup event: create tables

第三步:写测试

每个路由都配了测试。Claude Code 用了 conftest.py 里的 fixture 来管理测试数据库和应用客户端。

[+] tests/conftest.py
  - app fixture: creates FastAPI test app
  - client fixture: TestClient for HTTP testing
  - db_session fixture: overrides get_db with in-memory SQLite

[+] tests/test_bookmarks.py
  test_create_bookmark_success      — 正常创建,验证 201
  test_create_bookmark_invalid_url   — 非法 URL → 422
  test_create_bookmark_empty_title   — 空标题 → 422
  test_create_bookmark_duplicate_url — 重复 URL → 409
  test_list_bookmarks_empty          — 空数据库 → 空列表
  test_list_bookmarks_with_data      — 有数据 → 返回列表
  test_list_bookmarks_filter_by_tag  — tag 过滤
  test_get_bookmark_by_id            — 按 ID 查
  test_get_bookmark_404              — 不存在 → 404
  test_delete_bookmark               — 删除成功 → 204
  test_delete_bookmark_404           — 删除不存在 → 404
  test_export_bookmarks              — 导出 JSON → 验证结构
  test_health_check                  — 健康检查

跑测试:

→ pytest tests/ -v
  13 passed in 0.47s

13 个测试,一次全绿。


第四步:Docker 和文档

代码和测试都好了。Claude Code 接着建了部署和文档。

Dockerfile

FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
CMD ["uvicorn", "bookmark_api.main:app", "--host", "0.0.0.0", "--port", "8000"]

docker-compose.yml

services:
  api:
    build: .
    ports:
      - "8000:8000"
    volumes:
      - ./data:/app/data
    environment:
      - DATABASE_URL=sqlite:///data/bookmarks.db

README.md

不是那种 “A simple bookmark API” 两行就算了的 README。它包含了:项目简介、技术栈、安装步骤、API 端点表格(含请求/响应示例)、Docker 部署命令、测试运行指令、项目结构树。


最终的项目结构

bookmark-api/
├── bookmark_api/
│   ├── __init__.py
│   ├── main.py          (16 lines — FastAPI app)
│   ├── database.py      (22 lines — SQLAlchemy config)
│   ├── models.py        (18 lines — Bookmark model)
│   ├── schemas.py       (35 lines — Pydantic schemas)
│   └── routes.py        (68 lines — CRUD + export + health)
├── tests/
│   ├── __init__.py
│   ├── conftest.py      (35 lines — fixtures)
│   └── test_bookmarks.py (112 lines — 13 tests)
├── requirements.txt     (4 dependencies)
├── Dockerfile
├── docker-compose.yml
└── README.md            (55 lines)

总代码量:~360 行 Python + 配置。从空目录到完整项目,大约 20 分钟。

跑起来:

$ docker-compose up -d
$ curl http://localhost:8000/health
{"status": "ok"}

$ curl -X POST http://localhost:8000/bookmarks \
  -H 'Content-Type: application/json' \
  -d '{"url": "https://fastapi.tiangolo.com", "title": "FastAPI Docs", "tags": ["python", "web"]}'
{"id": 1, "url": "https://fastapi.tiangolo.com", ...}

$ curl http://localhost:8000/bookmarks?tag=python
[{"id": 1, ...}]

$ curl http://localhost:8000/bookmarks/export
{"exported_at": "2026-05-11T...", "count": 1, "bookmarks": [...]}

Claude Code 在"从零搭建"中的几个亮点

先规划,再动手。 它不是收到需求就开始写 main.py。先分析了模型关系、存储方案、目录结构,然后才动刀。这个"设计方案→征求认可→执行"的过程,跟团队里的技术方案评审很像。

从底层往上层建。 database → models → schemas → routes → tests。每一层建完都做验证,确保依赖正确。这种顺序跟你手写时的思路一致——你不会先写 Controller 再写 Model。

细节经得起看。 contains(f'"{tag}"') 这个 JSON 标签匹配方式,说明了它理解了 tags 的存储格式(JSON 数组)以及 LIKE 匹配的陷阱。这不是"写完就行",是"写好才行"。

文档质量。 README 不是模板填空。有具体的 curl 示例、docker-compose 命令、API 表格。一个接手的人看 README 就能跑起来。


踩到的坑

1. 第一次架构方案要审核

Claude Code 提案的架构,我会快速扫一眼。这次 tags 用 JSON 存储的方案我认同——如果不同意,在这步纠正比重写代码省太多时间。

2. 需求描述里的"不做什么"同样重要

我在需求里写了范围(6 个端点),Claude Code 不会自己加东西。如果只说"书签 API",它可能加上用户认证、分页、全文搜索——功能蔓延。明确边界比写详细需求更重要。

3. 分步验证比一次性全部建完靠谱

建一个模块、跑一下、确认没问题、继续。虽然 Claude Code 也支持一次性生成全部代码,但分层验证每一层都能拦住问题。


什么样的项目适合 AI 从零建

不是所有项目都适合这么搞。能成功的场景有几个共同点:

范围明确——你知道要什么。需求不明确的时候,AI 会帮你"想"需求,但想出来的跟你真正需要的差距可能很大。

模式常见——REST API、CRUD、数据库操作。AI 见过的模式越多,代码质量越高。如果你的需求涉及一个冷门协议或者公司内部框架,AI 帮不上忙。

规模不大——单服务应用,几个到十几个文件。微服务架构、多仓库协同的复杂项目,不是 AI 现在能独立搞定的。


下一篇

从需求描述到项目落地,prompt 的质量直接影响结果。下一篇聊 Prompt Engineering——CLAUDE.md 怎么配、怎么给 AI 设定代码风格、上下文管理的实战技巧。

欢迎大家评论区交流!

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐