综合练习:迷你待办 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:
- POST
/todos:body 填{"title": "学完 FastAPI"}→ 201,返回{"id": 1, ...};再创建两条 - GET
/todos→ 200,看到 3 条;试done=true过滤(全是 false 应该返回空列表) - PUT
/todos/1:body 填{"title": "学完 FastAPI", "done": true}→ 200,勾完成 - GET
/todos?done=true→ 只剩刚勾的那条 - DELETE
/todos/2→ 200;再 GET 单条/todos/2→ 404 - 故意 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 的活,进阶再学。
动手改(按难度递增)
- 给 GET
/todos加keyword: str = ""参数:标题包含关键字才返回(if keyword in it["title"]) - 接上 第 6 章的 SQLite,把
todosdict 换成真表,重启数据不丢 - 加「分页」:
?skip=0&limit=10,用列表切片实现(想想和done过滤的先后顺序)
学完这章去哪
恭喜!你已经能独立设计并实现一套 REST API——这就是现代后端的基石。三件事紧接着做:
- 去 Postman 教程,把你这五个接口建成一个 Collection 正式验收——接口调试的看家本领
- 回头把仓库换成 SQLite,体验「数据落盘」的产品感
- 想让页面消费你的 API?去 AJAX 教程学 fetch,或直接上 Vue 教程