Chapter 01 · 第一个 API:从请求到响应
📚 学完本章,你将能够:
- 说清楚"后端"在 Web 流程中扮演什么角色
- 用 FastAPI 写出能运行的第一个 API 接口
- 区分 GET / POST / PUT / DELETE,知道什么时候该用谁
- 从请求中提取路径参数、查询参数和 JSON 请求体
- 返回结构化的 JSON 响应,并控制 HTTP 状态码
💡 场景引入
假设你要做一个"图书管理系统"。用户在网页上点击"查看所有书籍",前端(网页代码)需要向后端(服务器上的 Python 代码)要数据。后端查到数据后,把结果打包成 JSON 发给前端,前端再展示到页面上。
这个"前端向后端要数据"的通道,就叫做 API(应用程序接口)。本章就是教你用 FastAPI 搭建这个通道。
1.1 后端到底是什么?
很多初学者对"后端"的概念是模糊的。让我们先看一张图:
用户的每一次点击,背后都是浏览器发送了一个HTTP 请求到服务器。服务器上的 Python 代码(也就是你写的 FastAPI 应用)收到请求后,做三件事:
- 解析请求:看看用户要什么(URL 是什么?带了什么参数?)
- 执行业务:查数据库、做计算、调用其他服务等
- 构造响应:把结果打包成 JSON,附上状态码,发回给浏览器
你可以把 HTTP 理解为浏览器和服务器之间的"邮寄协议"。请求就像一封写有地址的信(URL),响应就像回信(包含状态和内容)。后面我们会反复遇到它。
1.2 环境准备与第一个接口
打开你的终端,创建一个项目文件夹:
mkdir my-first-api && cd my-first-api
python -m venv venv
# Windows:
venv\Scripts\activate
# macOS/Linux:
source venv/bin/activate
pip install fastapi uvicorn
然后创建 main.py,写入以下代码:
from fastapi import FastAPI
# 创建一个 FastAPI 应用实例
app = FastAPI()
# 定义一个路由:当用户访问根路径时,返回这句话
@app.get("/")
async def root():
return {"message": "Hello World"}
运行它:
uvicorn main:app --reload
打开浏览器,访问 http://127.0.0.1:8000。你应该看到 {"message":"Hello World"}。恭喜,你的第一个 API 已经跑起来了!
开发时加上 --reload,你每次保存代码文件,服务器会自动重启。生产环境绝对不能用这个参数。
1.3 GET:读取数据
GET 是最常用的 HTTP 方法,意思是"给我数据"。比如"给我所有书籍列表""给我编号为 5 的书籍详情"。
# 模拟数据库(后面会用真正的数据库)
books = [
{"id": 1, "title": "三体", "author": "刘慈欣"},
{"id": 2, "title": "流浪地球", "author": "刘慈欣"},
]
@app.get("/books")
async def get_all_books():
return books
@app.get("/books/{book_id}")
async def get_book(book_id: int):
for book in books:
if book["id"] == book_id:
return book
return {"error": "Book not found"}
访问 /books 返回全部,访问 /books/1 返回第一本。注意 {book_id} 这种写法——它叫路径参数,URL 里的那个数字会被自动提取成函数参数。
@app.get("/books/{book_id}")
async def get_book(book_id): # 没有类型注解
...
@app.get("/books/{book_id}")
async def get_book(book_id: int): # FastAPI 会自动校验类型
...
如果你写 book_id: int,用户访问 /books/abc 时,FastAPI 会自动返回一个清晰的错误,告诉你"必须是整数"。这就是类型注解的魔力——FastAPI 利用 Python 的类型系统来做数据校验。
1.4 查询参数:筛选与分页
路径参数适合定位唯一资源,但"搜索""筛选""分页"这类操作更适合用查询参数——也就是 URL 中 ? 后面的部分。
@app.get("/books/")
async def search_books(author: str | None = None, skip: int = 0, limit: int = 10):
result = books
if author:
result = [b for b in result if b["author"] == author]
return result[skip : skip + limit]
现在你可以这样访问:
/books/?author=刘慈欣→ 只返回刘慈欣的书/books/?skip=0&limit=1→ 只返回第 1 本/books/?author=刘慈欣&limit=1→ 组合使用
路径参数和查询参数的区别:路径参数是 URL 的一部分(/books/1),查询参数是附加条件(?author=xxx)。路径参数用于"找某一个",查询参数用于"筛选一批"。
1.5 POST:创建数据
POST 用于"提交数据"。比如用户填写表单新建一本书,前端把书名和作者打包成 JSON 发给后端。
from pydantic import BaseModel
class BookCreate(BaseModel):
title: str
author: str
@app.post("/books")
async def create_book(book: BookCreate):
new_id = max(b["id"] for b in books) + 1
new_book = {"id": new_id, "title": book.title, "author": book.author}
books.append(new_book)
return new_book
BaseModel 是 Pydantic 提供的工具类。它告诉 FastAPI:"这个接口期望收到一个 JSON,里面必须有 title(字符串)和 author(字符串)。"如果用户少传了字段,或者传了数字,FastAPI 会自动拦截并返回 422 错误。
当用户发送 POST /books 并附带 JSON 请求体时,FastAPI 内部做了这几步:
- 读取 HTTP 请求体中的原始 JSON 字符串
- 尝试解析成 Python 字典
- 用
BookCreate模型校验:字段是否齐全?类型对不对? - 如果校验失败,立即返回 422 Unprocessable Entity,不会执行你的视图函数
- 如果校验通过,构造一个
BookCreate实例传入你的函数
这意味着你写在函数里的代码,拿到的一定是"干净合法"的数据,不需要自己写一堆 if title is None。
1.6 PUT 与 DELETE:修改和删除
PUT 通常用于"完整替换"一个资源:
@app.put("/books/{book_id}")
async def update_book(book_id: int, book: BookCreate):
for b in books:
if b["id"] == book_id:
b["title"] = book.title
b["author"] = book.author
return b
return {"error": "Book not found"}
DELETE 用于删除资源:
@app.delete("/books/{book_id}")
async def delete_book(book_id: int):
global books
books = [b for b in books if b["id"] != book_id]
return {"message": "Deleted successfully"}
| 方法 | 用途 | 是否幂等* |
|---|---|---|
| GET | 读取数据 | 是 |
| POST | 创建数据 | 否 |
| PUT | 完整更新 | 是 |
| PATCH | 部分更新 | 否 |
| DELETE | 删除数据 | 是 |
*幂等:执行一次和执行多次,结果相同。GET 和 PUT 应该是幂等的。
1.7 响应模型与状态码
前面的例子直接返回字典。更好的做法是用 response_model 明确声明返回什么结构,这有两个好处:
- 自动过滤敏感字段(比如不返回密码)
- 自动生成 API 文档
class BookOut(BaseModel):
id: int
title: str
author: str
@app.post("/books", response_model=BookOut, status_code=201)
async def create_book(book: BookCreate):
new_id = max(b["id"] for b in books) + 1
new_book = {"id": new_id, "title": book.title, "author": book.author}
books.append(new_book)
return new_book
status_code=201 告诉客户端"创建成功"。常用的状态码:
200 OK:一切正常(默认)201 Created:创建成功204 No Content:删除成功,无需返回内容400 Bad Request:请求格式错误404 Not Found:资源不存在422 Unprocessable Entity:数据校验失败(FastAPI 自动返回)
🎯 知识检测
/users/42?active=true,其中 42 和 active=true 分别是什么?/users/{user_id}),查询参数写在 ? 后面(?key=value)。路径参数用于定位资源,查询参数用于附加条件。
@app.post("/items") 和 @app.get("/items") 可以共存吗?GET /items 是读取列表,POST /items 是创建新项,两者完全独立。
本章小结
后端 = 请求的加工厂
接收 HTTP 请求 → 解析参数 → 执行逻辑 → 返回 JSON 响应。
类型注解是 FastAPI 的灵魂
book_id: int 不只是提示,它真的会做校验。
五种 HTTP 方法各有分工
GET 读、POST 创、PUT 全量改、PATCH 部分改、DELETE 删。
响应模型保护数据安全
response_model 会过滤不想暴露的字段,同时自动生成文档。