第一次写 FastAPI:从启动服务到看懂一次 HTTP 请求
摘要:不从大段 HTTP 概念讲起,而是先运行两个真实接口,再通过浏览器、curl 和 pytest 看清请求、路由、状态码与 JSON 响应之间的关系。

图:浏览器发出请求,FastAPI 匹配路由并把处理结果包装成响应。
不少 FastAPI 入门资料一上来就解释 ASGI、异步模型和 OpenAPI,读者还没看到页面,已经被术语淹没。第一次接触 Web API,更有效的路径是先让一次请求真的跑通。
这篇文章只做一件事:从空目录启动一个 FastAPI 服务,写出根接口和健康检查,然后从实际响应反推 HTTP 请求经历了什么。完成后,你会得到一份可以直接修改的最小项目,而不是一段只能阅读的示例代码。
本系列说明:本系列所有操作默认在
04-fastapi-beginner/目录下执行。如果你是从公众号单独阅读某一篇,请先进入该目录再执行命令。
先把服务跑起来,看一眼最终效果
启动应用后,请求根路径:
curl -i http://127.0.0.1:8000/
响应中的日期、服务器信息可能不同,关键结果应该类似:
HTTP/1.1 200 OK
content-type: application/json
{"message":"Hello FastAPI"}
再打开 http://127.0.0.1:8000/docs,你会看到 FastAPI 根据代码自动生成的交互式 API 文档,其中包含 / 和 /health 两个接口。
先把 Python 环境隔离出来
确认 Python 版本
进入项目目录后执行:
cd 04-fastapi-beginner
python3 --version
本系列建议使用 Python 3.11 或更高版本。预期输出类似:
Python 3.12.10
具体补丁版本不需要完全相同。若系统提示找不到 python3,请先从 https://www.python.org/downloads/ 安装 Python。
创建虚拟环境
python3 -m venv .venv
source .venv/bin/activate
Windows PowerShell 使用:
python -m venv .venv
.venv\Scripts\Activate.ps1
激活成功后,终端提示符通常会出现 (.venv)。继续确认当前解释器:
python -c "import sys; print(sys.executable)"
预期路径中包含 04-fastapi-beginner/.venv。虚拟环境的作用是把本系列依赖与系统 Python、其他项目依赖隔离开。
安装依赖
python -m pip install --upgrade pip
python -m pip install -r requirements.txt
验证安装结果:
python -c "import fastapi; print(fastapi.__version__)"
预期输出:
0.139.2
项目固定关键依赖版本,是为了避免同一份代码在不同时间安装后表现不同。以后升级版本时,应先运行全部测试。
一个 FastAPI 应用其实只需要十几行
本文代码位于 examples/ch01_first_api/main.py。先完整阅读,再自己重新敲一遍:
from fastapi import FastAPI
app = FastAPI(
title="任务管理 API",
description="FastAPI 入门教程的第一个可运行应用",
version="0.1.0",
)
@app.get("/")
def read_root() -> dict[str, str]:
return {"message": "Hello FastAPI"}
@app.get("/health")
def read_health() -> dict[str, str]:
return {"status": "ok"}
创建应用对象
app = FastAPI(
title="任务管理 API",
description="FastAPI 入门教程的第一个可运行应用",
version="0.1.0",
)
app 是整个 API 应用的入口。标题、描述和版本会进入 OpenAPI 文档,并显示在 Swagger UI 页面。
注册根接口
@app.get("/")
def read_root() -> dict[str, str]:
return {"message": "Hello FastAPI"}
这里有三个关键部分:
get表示只处理 HTTP GET 请求。/表示 URL 的根路径。read_root是请求匹配后由 FastAPI 调用的 Python 函数。
函数返回 Python 字典,FastAPI 会把它转换为 JSON 响应。
注册健康检查
@app.get("/health")
def read_health() -> dict[str, str]:
return {"status": "ok"}
健康检查接口用于回答"服务现在能否接收请求"。本文只返回固定结果;后续接入数据库后,它还可以检查关键依赖是否可用。
浏览器能打开还不够,再用 curl 看 HTTP
启动开发服务器
确认终端位于 04-fastapi-beginner,并且虚拟环境已经激活:
fastapi dev examples/ch01_first_api/main.py
看到服务器启动并监听 http://127.0.0.1:8000 即表示成功。这个命令会监视代码文件,保存修改后自动重启,适合本地开发。
当前终端需要保持运行。另开一个终端,进入相同目录并激活虚拟环境,再继续下面的验证。
使用浏览器验证
依次打开:
前两个地址应分别显示:
{"message":"Hello FastAPI"}
{"status":"ok"}
在 /docs 页面展开 GET /health,点击 Try it out,再点击 Execute。确认响应状态码为 200。
使用 curl 验证
浏览器隐藏了很多 HTTP 细节。增加 -i 可以同时查看响应头和响应体:
curl -i http://127.0.0.1:8000/health
重点观察:
- 第一行状态码是不是
200 OK。 content-type是否包含application/json。- 最后一行是否为
{"status":"ok"}。
再故意访问不存在的地址:
curl -i http://127.0.0.1:8000/not-found
预期状态码是 404 Not Found,响应体类似:
{"detail":"Not Found"}
这说明请求确实到达了服务,只是没有找到匹配的路径。
使用自动化测试验证
停止开发服务器不是必须的。直接在另一个已激活虚拟环境的终端执行:
python -m pytest tests/test_ch01.py -q
预期结果:
... [100%]
3 passed
测试不仅检查两个接口,还检查生成的 OpenAPI 文档是否包含这两个路径。以后修改代码时,只需再次运行命令,就能知道原有行为有没有被意外破坏。

图:同一个接口要从浏览器结果、HTTP 细节和自动化契约三个角度验证。
一次 GET 请求究竟走了哪些步骤
一次请求发生了什么
访问 http://127.0.0.1:8000/health 时,可以把过程简化为:
浏览器或 curl
-> 发送 GET /health
-> FastAPI 查找匹配的路由
-> 调用 read_health()
-> 把 Python 字典转换成 JSON
-> 返回 200 OK
现在先记住四个词:
| 名称 | 本文示例 | 含义 |
|---|---|---|
| HTTP 方法 | GET | 想对资源做什么 |
| 路径 | /health | 想访问哪个接口 |
| 状态码 | 200 | 服务器处理结果 |
| 响应体 | {"status":"ok"} | 服务器返回的数据 |
为什么返回字典却收到 JSON
Python 函数返回的是:
{"status": "ok"}
网络响应体是:
{"status":"ok"}
FastAPI 负责在二者之间转换,并把响应类型设置为 application/json。客户端不需要理解 Python 字典,只需要理解通用的 JSON 格式。
装饰器做了什么
@app.get("/health") 把"GET 方法 + /health 路径"和下面的函数关联起来。如果删除装饰器,函数仍然是普通 Python 函数,但它不再是可以通过网络访问的接口。
为什么这里使用 def
FastAPI 同时支持普通 def 和 async def。本文函数没有等待数据库、外部 HTTP 请求等异步操作,因此先使用更熟悉的普通函数。后续遇到真实 I/O 场景时再专门比较二者;不要为了"看起来异步"而机械地给所有函数加 async。
/docs 从哪里来
FastAPI 会根据路由、类型注解和应用元数据生成 OpenAPI 文档,再由 Swagger UI 展示。本文只写了两个路由,但已经同时得到机器可读的 /openapi.json 和可交互的 /docs。

图:客户端发出请求,FastAPI 匹配路由并把返回值组织成结构化响应。
第一次启动最常见的五个阻塞
ModuleNotFoundError: No module named 'fastapi'
常见原因是虚拟环境没有激活,或依赖被安装到了另一个 Python。依次执行:
python -c "import sys; print(sys.executable)"
python -m pip show fastapi
第一条路径应位于 .venv,第二条应能显示 FastAPI 信息。否则重新激活环境并安装依赖。
fastapi: command not found
先确认虚拟环境已激活,再执行:
python -m pip install -r requirements.txt
也可以检查命令位置:
which fastapi
Windows PowerShell 使用 Get-Command fastapi。
Address already in use
默认的 8000 端口已经被其他程序占用。开发时可以临时换端口:
fastapi dev examples/ch01_first_api/main.py --port 8001
此时访问地址也要改成 http://127.0.0.1:8001。
修改代码后结果没有变化
检查保存的是否为 examples/ch01_first_api/main.py,观察运行服务器的终端有没有重新加载日志。如果通过其他命令启动,先按 Ctrl+C 停止,再使用本文命令重新启动。
/docs 能打开,但自定义地址返回 404
对照 /docs 中显示的路径,检查斜杠和拼写。例如 /health 与 /heath 是两个不同的路径,/ 也不能遗漏。
跑通之后,顺手改出自己的第一个接口
先改一个小地方:修改欢迎语
把根接口响应改成:
{"message":"我的任务 API 已启动"}
保存文件后刷新浏览器,确认开发服务器自动加载了修改。注意:现有测试会失败,你也需要同步修改 test_read_root 的预期值,再让测试恢复通过。
再加一个接口:增加关于接口
新增 GET /about,返回:
{
"name": "任务管理 API",
"version": "0.1.0"
}
完成后进行三项检查:
- 浏览器能访问
/about。 /docs中出现/about。- 为它新增一个 pytest 测试。
故意制造一次错误:制造 404
把健康检查装饰器中的路径临时改为 /healthz,但仍然访问 /health。回答:
- 返回了什么状态码?
- 服务器是否已经崩溃?
/docs对定位问题有什么帮助?
完成观察后把路径恢复。
三个值得亲手验证的细节
- 为什么接口返回 JSON,而不是直接返回 Python 字典的字符串?
200和404分别说明什么?- 如果只修改函数名
read_health,请求地址会不会变化?请先预测,再实验。
最后,用测试固定这次改动
浏览器和 curl 适合观察接口,自动化测试负责在后续修改中守住这些行为。执行:
python -m pytest tests/test_ch01.py -q
python tests/validate_course.py
预期看到 3 passed,以及:
OK: all 16 articles satisfy the shareable tutorial contract
如果你完成了 /about 扩展并增加了测试,测试数量会大于 3,这是正常的。
写在最后
你已经完成了一个最小但真实的 HTTP API 闭环:客户端发出带方法和路径的请求,FastAPI 找到对应函数,把函数返回的 Python 数据转换成 JSON,并通过状态码说明处理结果。
下一篇将让路径变得可变化:用 /tasks/{task_id} 查询指定任务,并使用查询参数完成筛选和分页。届时我们也会主动输入错误参数,观察 FastAPI 如何利用 Python 类型注解保护接口。