FastAPI 教程

Chapter 01 · 第一个 API:从请求到响应

📚 学完本章,你将能够:

  • 说清楚"后端"在 Web 流程中扮演什么角色
  • 用 FastAPI 写出能运行的第一个 API 接口
  • 区分 GET / POST / PUT / DELETE,知道什么时候该用谁
  • 从请求中提取路径参数、查询参数和 JSON 请求体
  • 返回结构化的 JSON 响应,并控制 HTTP 状态码

💡 场景引入

假设你要做一个"图书管理系统"。用户在网页上点击"查看所有书籍",前端(网页代码)需要向后端(服务器上的 Python 代码)要数据。后端查到数据后,把结果打包成 JSON 发给前端,前端再展示到页面上。

这个"前端向后端要数据"的通道,就叫做 API(应用程序接口)。本章就是教你用 FastAPI 搭建这个通道。

1.1 后端到底是什么?

很多初学者对"后端"的概念是模糊的。让我们先看一张图:

Web 请求的生命周期
用户
在浏览器操作
→
浏览器
发送 HTTP 请求
→
FastAPI 后端
处理请求,返回 JSON
→
浏览器
拿到 JSON,渲染页面
→
用户
看到结果

用户的每一次点击,背后都是浏览器发送了一个HTTP 请求到服务器。服务器上的 Python 代码(也就是你写的 FastAPI 应用)收到请求后,做三件事:

  1. 解析请求:看看用户要什么(URL 是什么?带了什么参数?)
  2. 执行业务:查数据库、做计算、调用其他服务等
  3. 构造响应:把结果打包成 JSON,附上状态码,发回给浏览器
HTTP 是什么?

你可以把 HTTP 理解为浏览器和服务器之间的"邮寄协议"。请求就像一封写有地址的信(URL),响应就像回信(包含状态和内容)。后面我们会反复遇到它。

1.2 环境准备与第一个接口

打开你的终端,创建一个项目文件夹:

bash
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,写入以下代码:

main.py
from fastapi import FastAPI

# 创建一个 FastAPI 应用实例
app = FastAPI()

# 定义一个路由:当用户访问根路径时,返回这句话
@app.get("/")
async def root():
    return {"message": "Hello World"}

运行它:

bash
uvicorn main:app --reload

打开浏览器,访问 http://127.0.0.1:8000。你应该看到 {"message":"Hello World"}。恭喜,你的第一个 API 已经跑起来了!

--reload 是什么意思?

开发时加上 --reload,你每次保存代码文件,服务器会自动重启。生产环境绝对不能用这个参数。

1.3 GET:读取数据

GET 是最常用的 HTTP 方法,意思是"给我数据"。比如"给我所有书籍列表""给我编号为 5 的书籍详情"。

GET 示例
# 模拟数据库(后面会用真正的数据库)
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]

现在你可以这样访问:

常见陷阱

路径参数和查询参数的区别:路径参数是 URL 的一部分(/books/1),查询参数是附加条件(?author=xxx)。路径参数用于"找某一个",查询参数用于"筛选一批"。

1.5 POST:创建数据

POST 用于"提交数据"。比如用户填写表单新建一本书,前端把书名和作者打包成 JSON 发给后端。

POST 示例
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 内部做了这几步:

  1. 读取 HTTP 请求体中的原始 JSON 字符串
  2. 尝试解析成 Python 字典
  3. 用 BookCreate 模型校验:字段是否齐全?类型对不对?
  4. 如果校验失败,立即返回 422 Unprocessable Entity,不会执行你的视图函数
  5. 如果校验通过,构造一个 BookCreate 实例传入你的函数

这意味着你写在函数里的代码,拿到的一定是"干净合法"的数据,不需要自己写一堆 if title is None。

1.6 PUT 与 DELETE:修改和删除

PUT 通常用于"完整替换"一个资源:

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 用于删除资源:

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 明确声明返回什么结构,这有两个好处:

  1. 自动过滤敏感字段(比如不返回密码)
  2. 自动生成 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 告诉客户端"创建成功"。常用的状态码:

🎯 知识检测

问题 1
用户访问 /users/42?active=true,其中 42 和 active=true 分别是什么?
都是查询参数
42 是路径参数,active=true 是查询参数
都是路径参数
42 是查询参数,active=true 是路径参数
解析:路径参数写在 URL 路径里(/users/{user_id}),查询参数写在 ? 后面(?key=value)。路径参数用于定位资源,查询参数用于附加条件。
问题 2
在 FastAPI 中,@app.post("/items") 和 @app.get("/items") 可以共存吗?
可以,HTTP 方法不同,路径相同是允许的
不可以,路径必须唯一
只有 GET 和 POST 可以共存,其他不行
解析:REST API 的设计原则是:同一个 URL,不同的 HTTP 方法代表不同的操作。GET /items 是读取列表,POST /items 是创建新项,两者完全独立。
问题 3
以下哪个状态码最适合"成功创建了一本新书"?
200 OK
201 Created
204 No Content
404 Not Found
解析:201 专门表示"创建成功",比通用的 200 更精确。204 用于删除成功(不需要返回内容)。404 表示资源找不到。

本章小结

✓

后端 = 请求的加工厂

接收 HTTP 请求 → 解析参数 → 执行逻辑 → 返回 JSON 响应。

✓

类型注解是 FastAPI 的灵魂

book_id: int 不只是提示,它真的会做校验。

✓

五种 HTTP 方法各有分工

GET 读、POST 创、PUT 全量改、PATCH 部分改、DELETE 删。

✓

响应模型保护数据安全

response_model 会过滤不想暴露的字段,同时自动生成文档。

🛠️ 动手实验

实现一个完整的"图书 API":GET /books(列表+查询参数筛选)、GET /books/{id}(详情)、POST /books(创建)、PUT /books/{id}(更新)、DELETE /books/{id}(删除)。
用 response_model 保护响应,并尝试让 POST 返回 201 状态码。