🐸 青蛙编程网
首页 / Python / FastAPI / 7. 综合练习

综合练习:迷你待办 API

这是什么:把全章串成一个完整产品:待办事项 API——查列表、查单条、创建、修改、删除,五个接口一套打完,这就是前后端分离项目里后端的标准形态。

为什么用:CRUD(增删改查)占日常后端工作的八成。亲手把五个方法写顺,你就具备「给任何前端供数据」的基本能力——顺便给 Postman 教程准备了一份现成的练手对象。

如何用:本章为了聚焦路由设计,仓库回到内存 dict(第 6 章教你换 SQLite)。全部接口写进一个 main.py,uvicorn 起服务后在 /docs 里逐个验收。

接口设计

先设计后动手——五个接口一张表:

方法 路径 作用 成功码
GET /todos 列出全部(支持 ?done= 过滤) 200
GET /todos/{todo_id} 查单条,查无 404 200
POST /todos 创建,body 传 {"title": "..."} 201
PUT /todos/{todo_id} 整条更新(改标题 / 勾完成) 200
DELETE /todos/{todo_id} 删除 200

这就是 REST 风格的雏形:URL 管资源,方法管动作。

完整代码(main.py)

from fastapi import FastAPI, HTTPException
from pydantic import BaseModel, Field

app = FastAPI(title="迷你待办 API")

# ---- 数据模型 ----
class TodoIn(BaseModel):
    title: str = Field(min_length=1, max_length=50)
    done: bool = False

# ---- 内存仓库(换 SQLite 见第 6 章)----
todos: dict[int, TodoIn] = {}
next_id = 1

# ---- 1. 列表(可按 done 过滤)----
@app.get("/todos")
def list_todos(done: bool | None = None):
    items = [{"id": i, **t.model_dump()} for i, t in todos.items()]
    if done is not None:
        items = [it for it in items if it["done"] == done]
    return {"total": len(items), "items": items}

# ---- 2. 单条 ----
@app.get("/todos/{todo_id}")
def read_todo(todo_id: int):
    if todo_id not in todos:
        raise HTTPException(status_code=404, detail="待办不存在")
    return {"id": todo_id, **todos[todo_id].model_dump()}

# ---- 3. 创建 ----
@app.post("/todos", status_code=201)
def create_todo(todo: TodoIn):
    global next_id
    todos[next_id] = todo
    next_id += 1
    return {"id": next_id - 1, **todo.model_dump()}

# ---- 4. 更新 ----
@app.put("/todos/{todo_id}")
def update_todo(todo_id: int, todo: TodoIn):
    if todo_id not in todos:
        raise HTTPException(status_code=404, detail="待办不存在")
    todos[todo_id] = todo
    return {"id": todo_id, **todo.model_dump()}

# ---- 5. 删除 ----
@app.delete("/todos/{todo_id}")
def delete_todo(todo_id: int):
    if todo_id not in todos:
        raise HTTPException(status_code=404, detail="待办不存在")
    removed = todos.pop(todo_id)
    return {"deleted": todo_id, "title": removed.title}

验收流程:在 /docs 里走一遍

uvicorn main:app --reload 启动后,打开 /docs:

  1. POST /todos:body 填 {"title": "学完 FastAPI"} → 201,返回 {"id": 1, ...};再创建两条
  2. GET /todos → 200,看到 3 条;试 done=true 过滤(全是 false 应该返回空列表)
  3. PUT /todos/1:body 填 {"title": "学完 FastAPI", "done": true} → 200,勾完成
  4. GET /todos?done=true → 只剩刚勾的那条
  5. DELETE /todos/2 → 200;再 GET 单条 /todos/2 → 404
  6. 故意 POST 空标题 → 422 自动校验拦截

六步全对,这个 API 就算毕业了。

逐段中文精讲

  • 模型区:一个 TodoIn 管住所有写操作的入参——POST 和 PUT 共用同一模型,校验只写一遍。
  • 仓库区:dict[int, TodoIn] 以 id 为键存对象,查找 O(1);next_id 全局自增当主键(global 声明后才可改)。
  • done: bool | None = None:None 表示「没传这个参数,不过滤」;bool 表示「传了就按真假过滤」——可选过滤参数的标准写法(第 3 章的查询参数 + 类型注解组合)。
  • {**t.model_dump()}:dict 解包合并,把模型字段铺进响应 dict 再补一个 id——响应比入参多主键的常见手法。
  • 三个 404:单查、改、删都在「查无」时抛 HTTPException(404)(第 5 章的规范);列表永不 404,空了就回空数组。
  • PUT 用整条替换:body 里 title 和 done 都要给(done 有默认值所以可省);想「只改一个字段」是 PATCH 的活,进阶再学。

动手改(按难度递增)

  1. 给 GET /todos 加 keyword: str = "" 参数:标题包含关键字才返回(if keyword in it["title"])
  2. 接上 第 6 章的 SQLite,把 todos dict 换成真表,重启数据不丢
  3. 加「分页」:?skip=0&limit=10,用列表切片实现(想想和 done 过滤的先后顺序)

学完这章去哪

恭喜!你已经能独立设计并实现一套 REST API——这就是现代后端的基石。三件事紧接着做:

  1. 去 Postman 教程,把你这五个接口建成一个 Collection 正式验收——接口调试的看家本领
  2. 回头把仓库换成 SQLite,体验「数据落盘」的产品感
  3. 想让页面消费你的 API?去 AJAX 教程学 fetch,或直接上 Vue 教程

上一章:数据库入门 · 验收工具:Postman 🐸

📥 下载本章代码

ZIP 内含本章全部代码文件与说明,解压即用,与教程内容同步生成。