第五章 增删改查(CRUD)—— 后台的核心工作
本章目标:用 FastAPI + SQLModel 实现对数据库的增删改查,这是后台开发最核心的技能。
这是整个教程最重要的一章,请认真跟着敲代码。
5.1 什么是 CRUD
CRUD 是四个英文单词的首字母缩写:
| 字母 | 英文 | 中文 | HTTP 方法 | SQL 语句 | 通俗理解 |
|---|---|---|---|---|---|
| C | Create | 新增 | POST | INSERT | 往数据库里加一条新数据 |
| R | Read | 查询 | GET | SELECT | 从数据库里找数据 |
| U | Update | 修改 | PUT | UPDATE | 改数据库里已有的数据 |
| D | Delete | 删除 | DELETE | DELETE | 从数据库里删一条数据 |
可以说,绝大多数后台系统的本质就是对数据的 CRUD。
- 用户注册 → Create(新增一个用户)
- 浏览商品 → Read(查询商品列表)
- 修改密码 → Update(更新用户信息)
- 注销账号 → Delete(删除用户数据)
接下来,我们围绕 Todo(待办事项)这个场景,逐个实现。
5.2 准备工作
确保你的项目中已经有这三个文件(上一章创建的):
models.py(数据模型)
from sqlmodel import SQLModel, Field
class TodoCreate(SQLModel):
title: str
done: bool = False
class TodoUpdate(SQLModel):
title: str | None = None
done: bool | None = None
class Todo(SQLModel, table=True):
id: int | None = Field(default=None, primary_key=True)
title: str
done: bool = False
database.py(数据库配置)
from sqlmodel import SQLModel, Session, create_engine
DATABASE_URL = "sqlite:///database.db"
engine = create_engine(DATABASE_URL, echo=True)
def create_db_and_tables():
SQLModel.metadata.create_all(engine)
def get_session():
with Session(engine) as session:
yield session
准备好了,下面开始写 main.py。
5.3 Create —— 新增一条待办事项
接口设计
POST /todos
请求体:{"title": "买牛奶", "done": false}
响应: {"id": 1, "title": "买牛奶", "done": false}
代码实现
from fastapi import FastAPI, Depends, HTTPException
from sqlmodel import Session, select
from contextlib import asynccontextmanager
from database import create_db_and_tables, get_session, engine
from models import Todo, TodoCreate, TodoUpdate
@asynccontextmanager
async def lifespan(app: FastAPI):
create_db_and_tables()
yield
app = FastAPI(lifespan=lifespan)
@app.post("/todos", status_code=201)
def create_todo(todo: TodoCreate):
with Session(engine) as session:
db_todo = Todo.model_validate(todo)
session.add(db_todo)
session.commit()
session.refresh(db_todo)
return db_todo
逐行讲解
@app.post("/todos", status_code=201)
- 定义一个 POST 接口,路径是
/todos status_code=201表示创建成功返回 201 状态码
def create_todo(todo: TodoCreate):
- 参数
todo: TodoCreate表示接收一个 JSON 请求体,格式按照 TodoCreate 模型 - FastAPI 自动把 JSON 解析为 TodoCreate 对象
with Session(engine) as session:
- 打开一个数据库会话
db_todo = Todo.model_validate(todo)
- 把
TodoCreate对象转换为Todo对象(带 id 字段的完整模型)
session.add(db_todo)
- 把这条数据添加到会话中(还没真正写入数据库)
session.commit()
- 提交事务,数据真正写入数据库
session.refresh(db_todo)
- 刷新对象,获取数据库自动生成的 id
return db_todo
- 返回完整的 Todo 对象(包含 id),FastAPI 自动转为 JSON
测试
启动服务后,打开 http://127.0.0.1:8000/docs:
- 找到
POST /todos - 点击 “Try it out”
- 填写请求体:
{"title": "买牛奶"} - 点击 “Execute”
- 看到响应:
{"id": 1, "title": "买牛奶", "done": false}
5.4 Read —— 查询待办事项
我们需要两个查询接口:查全部、查单条。
查询全部
GET /todos
响应:[{"id": 1, "title": "买牛奶", "done": false}, ...]
@app.get("/todos")
def get_todos():
with Session(engine) as session:
todos = session.exec(select(Todo)).all()
return todos
讲解:
todos = session.exec(select(Todo)).all()
select(Todo)→ 构造一个查询语句:SELECT * FROM todosession.exec(...)→ 执行查询.all()→ 获取所有结果,返回一个列表
查询单条
GET /todos/1
响应:{"id": 1, "title": "买牛奶", "done": false}
@app.get("/todos/{todo_id}")
def get_todo(todo_id: int):
with Session(engine) as session:
todo = session.get(Todo, todo_id)
if not todo:
raise HTTPException(status_code=404, detail="待办事项不存在")
return todo
讲解:
todo = session.get(Todo, todo_id)
session.get(Todo, todo_id)→ 根据主键(id)查找一条数据- 如果找到了,返回 Todo 对象;如果没找到,返回
None
if not todo:
raise HTTPException(status_code=404, detail="待办事项不存在")
- 如果没找到,主动抛出 404 错误
测试
- 先用
POST /todos添加几条数据 GET /todos→ 应该返回所有数据的列表GET /todos/1→ 返回 id 为 1 的数据GET /todos/999→ 返回 404 错误
5.5 Update —— 修改待办事项
接口设计
PUT /todos/1
请求体:{"done": true} ← 只传要改的字段
响应: {"id": 1, "title": "买牛奶", "done": true}
代码实现
@app.put("/todos/{todo_id}")
def update_todo(todo_id: int, todo_update: TodoUpdate):
with Session(engine) as session:
todo = session.get(Todo, todo_id)
if not todo:
raise HTTPException(status_code=404, detail="待办事项不存在")
todo_data = todo_update.model_dump(exclude_unset=True)
todo.sqlmodel_update(todo_data)
session.add(todo)
session.commit()
session.refresh(todo)
return todo
逐行讲解
def update_todo(todo_id: int, todo_update: TodoUpdate):
- 接收两个参数:路径参数
todo_id和请求体todo_update
todo = session.get(Todo, todo_id)
if not todo:
raise HTTPException(status_code=404, detail="待办事项不存在")
- 先查数据库,确认这条数据存在
todo_data = todo_update.model_dump(exclude_unset=True)
model_dump()把模型转成字典exclude_unset=True只保留用户传了的字段(没传的不改)- 比如用户只传了
{"done": true},那 title 不会被改动
todo.sqlmodel_update(todo_data)
- 用传入的数据更新 todo 对象的字段
session.add(todo)
session.commit()
session.refresh(todo)
return todo
- 保存到数据库,返回更新后的数据
测试
- 先确认有一条数据(
GET /todos/1) PUT /todos/1,请求体:{"done": true}- 再
GET /todos/1,检查 done 是否变成了 true
5.6 Delete —— 删除待办事项
接口设计
DELETE /todos/1
响应:{"message": "删除成功"}
代码实现
@app.delete("/todos/{todo_id}")
def delete_todo(todo_id: int):
with Session(engine) as session:
todo = session.get(Todo, todo_id)
if not todo:
raise HTTPException(status_code=404, detail="待办事项不存在")
session.delete(todo)
session.commit()
return {"message": "删除成功"}
讲解:
session.delete(todo)
session.commit()
session.delete(todo)→ 标记要删除这条数据session.commit()→ 提交,真正从数据库中删除
测试
- 先确认有数据(
GET /todos) DELETE /todos/1→ 返回{"message": "删除成功"}- 再
GET /todos→ 列表中应该少了一条 DELETE /todos/999→ 返回 404 错误
5.7 完整 main.py 代码
把所有接口放在一起,这就是完整的后台代码:
from contextlib import asynccontextmanager
from fastapi import FastAPI, HTTPException
from sqlmodel import Session, select
from database import create_db_and_tables, engine
from models import Todo, TodoCreate, TodoUpdate
@asynccontextmanager
async def lifespan(app: FastAPI):
create_db_and_tables()
yield
app = FastAPI(lifespan=lifespan)
@app.get("/")
def index():
return {"message": "Todo 后台服务已启动"}
# ==================== 增删改查接口 ====================
@app.post("/todos", status_code=201)
def create_todo(todo: TodoCreate):
"""新增一条待办事项"""
with Session(engine) as session:
db_todo = Todo.model_validate(todo)
session.add(db_todo)
session.commit()
session.refresh(db_todo)
return db_todo
@app.get("/todos")
def get_todos():
"""查询所有待办事项"""
with Session(engine) as session:
todos = session.exec(select(Todo)).all()
return todos
@app.get("/todos/{todo_id}")
def get_todo(todo_id: int):
"""查询单条待办事项"""
with Session(engine) as session:
todo = session.get(Todo, todo_id)
if not todo:
raise HTTPException(status_code=404, detail="待办事项不存在")
return todo
@app.put("/todos/{todo_id}")
def update_todo(todo_id: int, todo_update: TodoUpdate):
"""修改一条待办事项"""
with Session(engine) as session:
todo = session.get(Todo, todo_id)
if not todo:
raise HTTPException(status_code=404, detail="待办事项不存在")
todo_data = todo_update.model_dump(exclude_unset=True)
todo.sqlmodel_update(todo_data)
session.add(todo)
session.commit()
session.refresh(todo)
return todo
@app.delete("/todos/{todo_id}")
def delete_todo(todo_id: int):
"""删除一条待办事项"""
with Session(engine) as session:
todo = session.get(Todo, todo_id)
if not todo:
raise HTTPException(status_code=404, detail="待办事项不存在")
session.delete(todo)
session.commit()
return {"message": "删除成功"}
5.8 操作对照表
把所有操作对照一下,帮助记忆:
| 操作 | HTTP 方法 | URL | 函数 | 数据库操作 |
|---|---|---|---|---|
| 新增 | POST | /todos | create_todo | session.add() + commit() |
| 查全部 | GET | /todos | get_todos | session.exec(select(Todo)).all() |
| 查单条 | GET | /todos/{id} | get_todo | session.get(Todo, id) |
| 修改 | PUT | /todos/{id} | update_todo | 修改字段 + commit() |
| 删除 | DELETE | /todos/{id} | delete_todo | session.delete() + commit() |
数据库操作三步曲
不管是增、改、删,都遵循同样的套路:
1. 操作(add / 修改字段 / delete)
2. 提交(commit)
3. 刷新(refresh,如果需要返回最新数据)
本章小结
恭喜你!到这里你已经实现了一个完整的后台 CRUD 服务。
✅ Create → POST /todos → 新增
✅ Read → GET /todos → 查询全部
✅ Read → GET /todos/{id} → 查询单条
✅ Update → PUT /todos/{id} → 修改
✅ Delete → DELETE /todos/{id} → 删除
这就是后台开发最核心的技能。市面上所有的后台管理系统、电商后台、内容管理系统……本质上都是更复杂的 CRUD。
下一章,我们将把前端和后端连起来——用 Vue 调用这些接口,实现完整的前后端联调。