FastAPI 零基础入门教程(一)- 核心概念 + 第一个接口
前言
前面我们完整学完了 Pydantic v2 和 SQLModel 两套核心工具,分别解决了「数据校验序列化」和「数据库操作」两大后端核心问题。但零散的工具无法直接对外提供服务,我们需要一个 Web 框架把它们串起来,这就是 FastAPI。
FastAPI 由 Sebastián Ramírez(tiangolo)开发,是目前 Python 生态最火的现代 Web 框架,底层基于 Starlette(高性能异步)+ Pydantic v2(Rust 数据校验),性能比肩 Node.js/Go,开发效率却远超传统框架。更重要的是,你前面学的 Pydantic、SQLModel 知识可以 100% 直接复用,无缝衔接。
本文为 FastAPI 系列第一阶段,半天时间从零搭建第一个 FastAPI 项目,掌握核心概念、路径参数、查询参数、自动接口文档,代码全部可直接运行。
一、阶段学习目标
-
理解 FastAPI 三大核心优势,知道为什么选它而不是 Flask/Django
-
学会安装 FastAPI + Uvicorn,启动第一个 Web 服务
-
掌握路径参数、查询参数、默认值、可选参数四种传参方式
-
理解 HTTP 请求方法:GET / POST / PUT / DELETE 的语义区别
-
熟练使用 Swagger UI 交互式文档(/docs)和 ReDoc 文档
-
完成第一个实战:用户查询接口 + 列表分页接口
二、FastAPI 核心优势(为什么学它)
2.1 三大核心优势
优势一:自动生成交互式API文档
写代码即生成文档,不需要额外写注释、配置 Swagger。启动项目后访问 /docs 直接打开 Swagger UI,可以在线调试接口、查看参数说明、测试请求响应,前后端联调效率直接翻倍。
优势二:类型驱动开发,自动数据校验
基于 Python 原生类型注解 + Pydantic v2,所有请求参数、响应体自动校验,非法参数直接返回标准化错误,不用手写一堆 if 判断。同时自动序列化返回值,字典、ORM 对象直接转 JSON。
优势三:高性能异步支持
底层基于 Starlette 异步框架 + Uvicorn ASGI 服务器,原生支持 async/await 异步编程,IO 密集型场景性能比肩 Node.js 和 Go,远超同步 WSGI 框架(Flask、Django)。
2.2 FastAPI vs Flask vs Django 对比
| 维度 | FastAPI | Flask | Django |
|---|---|---|---|
| 性能 | 极高(异步+Rust校验) | 中等(同步WSGI) | 中等(同步WSGI) |
| 学习曲线 | 中等(需懂类型注解) | 简单 | 陡峭 |
| 自动文档 | ✅ 原生支持 | ❌ 需第三方插件 | ❌ 需第三方插件 |
| 数据校验 | ✅ Pydantic 原生 | ❌ 手动/第三方 | ✅ Form/Serializer |
| 异步支持 | ✅ 原生 async | ❌ 同步为主 | ⚠️ 部分支持 |
| 适用场景 | API 服务、微服务、前后端分离 | 小型项目、快速原型 | 全栈、Admin 后台 |
结论:如果你做的是 前后端分离项目、API 服务、微服务,FastAPI 是目前 Python 生态的最优选择。
三、环境安装与第一个接口
3.1 安装依赖
FastAPI 本身只是框架,还需要一个 ASGI 服务器来运行,官方推荐 Uvicorn。
bash
pip install fastapi
pip install "uvicorn[standard]"
说明:
-
fastapi:FastAPI 框架核心 -
uvicorn[standard]:ASGI 服务器,带标准库(支持 WebSocket、HTTP/2 等)
3.2 第一个 FastAPI 应用
创建 main.py 文件,写入最简单的接口:
python
from fastapi import FastAPI
# 创建 FastAPI 应用实例
app = FastAPI(title="我的第一个FastAPI项目", version="1.0")
# 定义 GET 接口,路径为 /
@app.get("/")
def root():
return {"message": "Hello FastAPI!"}
代码解析:
-
FastAPI()创建应用实例,可传 title、description、version 等参数用于文档展示 -
@app.get("/")装饰器定义 GET 请求,路径为根路径/ -
函数返回值会自动序列化为 JSON 响应
3.3 启动服务
在终端执行启动命令:
bash
uvicorn main:app --reload
参数说明:
-
main:app:文件名:应用实例名,对应 main.py 中的 app 变量 -
--reload:开发模式,代码修改后自动重启服务器(生产环境不要用)
或者使用如下代码:
python
# 脚本直接运行入口,方便在本地开发时快速启动服务
if __name__ == "__main__":
import uvicorn
# 使用 Uvicorn (ASGI 服务器) 启动应用
# reload=True: 开启热重载,修改代码后自动重启服务,仅限开发环境使用
# host: 监听本地回环地址
# port: 监听 8000 端口
uvicorn.run("main:app", reload=True, host="127.0.0.1", port=8000)
启动成功后,终端会显示:
text
INFO: Will watch for changes in these directories: ['D:\\ProjectCode\\fastapi']
INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
INFO: Started reloader process [28740] using WatchFiles
INFO: Started server process [9460]
INFO: Waiting for application startup.
INFO: Application startup complete.
打开浏览器访问 http://127.0.0.1:8000,就能看到返回的 JSON:
json
{"message": "Hello FastAPI!"}
3.4 常用启动参数
| 参数 | 说明 |
|---|---|
--host 0.0.0.0 |
监听所有网卡,允许外部访问 |
--port 8080 |
指定端口,默认 8000 |
--reload |
开发热重载,代码变更自动重启 |
--workers 4 |
多进程 worker 数量,生产环境使用 |
四、路径参数详解
4.1 什么是路径参数
路径参数就是写在 URL 路径中的参数,用 {参数名} 包裹,常用于传递资源 ID、标识等。
例如:/users/123 中的 123 就是用户 ID 路径参数。
4.2 基础用法
python
from fastapi import FastAPI
app = FastAPI()
# 路径参数 user_id 写在 { } 中
@app.get("/users/{user_id}")
def get_user(user_id: int):
return {"user_id": user_id, "message": "查询用户成功"}
访问 http://127.0.0.1:8000/users/123,返回:
json
{"user_id": 123, "message": "查询用户成功"}
关键特性:自动类型转换 + 校验
因为我们声明了 user_id: int,FastAPI 会自动:
-
把 URL 中的字符串 "123" 转换为整数 123
-
校验类型,如果传的是
/users/abc,自动返回 422 错误
非法参数返回示例:
json
{
"detail": [
{
"type": "int_parsing",
"loc": ["path", "user_id"],
"msg": "Input should be a valid integer, unable to parse string as an integer",
"input": "abc"
}
]
}
4.3 路径参数顺序问题
如果有固定路径和动态路径冲突,固定路径必须写在前面,否则会被动态路径匹配到。
python
# ✅ 正确:固定路径写在前面
@app.get("/users/me")
def get_current_user():
return {"username": "当前登录用户"}
# 动态路径写在后面
@app.get("/users/{user_id}")
def get_user(user_id: int):
return {"user_id": user_id}
python
# ❌ 错误:/users/me 会被匹配到 user_id=me
@app.get("/users/{user_id}")
def get_user(user_id: int):
return {"user_id": user_id}
@app.get("/users/me") # 永远不会执行到
def get_current_user():
return {"username": "当前登录用户"}
4.4 预设值枚举(Enum)
如果路径参数只能是固定几个值,可以用枚举类限制,文档中也会显示可选值。
python
from enum import Enum
class UserRole(str, Enum):
ADMIN = "admin"
USER = "user"
GUEST = "guest"
@app.get("/users/role/{role}")
def get_users_by_role(role: UserRole):
return {"role": role.value, "message": f"查询{role.value}角色用户"}
传入不在枚举中的值会自动返回 422 错误。
4.5 路径参数包含路径(斜杠)
如果参数值本身包含 /(比如文件路径),需要用 :path 修饰:
python
@app.get("/files/{file_path:path}")
def read_file(file_path: str):
return {"file_path": file_path}
访问 /files/home/user/docs/readme.txt,file_path 会完整匹配到 home/user/docs/readme.txt。
五、查询参数详解
5.1 什么是查询参数
查询参数是 URL 中 ? 后面的键值对,用 & 分隔,常用于筛选、分页、搜索等场景。
例如:/items?page=1&page_size=10 中的 page 和 page_size 就是查询参数。
5.2 基础用法
FastAPI 中,函数参数中没有在路径中声明的,自动识别为查询参数。
python
@app.get("/items/")
def list_items(skip: int = 0, limit: int = 10):
return {"skip": skip, "limit": limit}
访问 http://127.0.0.1:8000/items/?skip=20&limit=5,返回:
json
{"skip": 20, "limit": 5}
特点:
-
自动类型转换:字符串转 int/float/bool 等
-
自动校验:类型错误返回 422
-
有默认值的参数是可选的,不传就用默认值
5.3 必填查询参数
如果不给默认值,参数就是必填的,不传会报错。
python
@app.get("/search/")
def search(keyword: str): # 没有默认值,必填
return {"keyword": keyword}
访问 /search/ 不传 keyword,返回 422 错误:
json
{
"detail": [
{
"type": "missing",
"loc": ["query", "keyword"],
"msg": "Field required",
"input": null
}
]
}
5.4 可选查询参数(Optional)
使用 Optional 或 | None 声明可选参数,不传就是 None。
python
from typing import Optional
@app.get("/users/")
def list_users(
page: int = 1,
page_size: int = 10,
keyword: Optional[str] = None # 可选,不传为None
):
result = {"page": page, "page_size": page_size}
if keyword:
result["keyword"] = keyword
return result
Python 3.10+ 可以用更简洁的写法:
python
@app.get("/users/")
def list_users(keyword: str | None = None):
return {"keyword": keyword}
5.5 布尔类型查询参数
布尔参数支持多种写法:1/0、true/false、on/off、yes/no,自动转换。
python
@app.get("/items/")
def list_items(is_active: bool = False):
return {"is_active": is_active}
以下访问方式都等价:
-
/items/?is_active=true -
/items/?is_active=1 -
/items/?is_active=on -
/items/?is_active=yes
5.6 多路径参数 + 多查询参数混合
路径参数和查询参数可以混合使用,FastAPI 自动识别哪个是哪个。
python
@app.get("/users/{user_id}/items/")
def get_user_items(
user_id: int, # 路径参数
page: int = 1, # 查询参数
page_size: int = 10, # 查询参数
keyword: str | None = None # 可选查询参数
):
return {
"user_id": user_id,
"page": page,
"page_size": page_size,
"keyword": keyword
}
访问 /users/123/items/?page=2&keyword=phone,正确解析所有参数。
text
{
"user_id": 123,
"page": 2,
"page_size": 10,
"keyword": "phone"
}
六、请求方法与 RESTful 规范
6.1 常用 HTTP 请求方法
FastAPI 支持所有标准 HTTP 方法,每个方法对应不同的业务语义:
| 方法 | FastAPI 装饰器 | 语义 |
|---|---|---|
| GET | @app.get() |
查询数据,幂等,无请求体 |
| POST | @app.post() |
新增数据,非幂等 |
| PUT | @app.put() |
全量更新,幂等 |
| PATCH | @app.patch() |
局部更新,非幂等 |
| DELETE | @app.delete() |
删除数据,幂等 |
| HEAD | @app.head() |
只获取响应头,无响应体 |
| OPTIONS | @app.options() |
获取资源支持的方法 |
6.2 RESTful 设计规范
RESTful 是一种 API 设计风格,核心原则是「资源为中心」,用 HTTP 方法表示操作。
以用户资源为例的标准设计:
| 操作 | 方法 + 路径 | 说明 |
|---|---|---|
| 查询列表 | GET /users |
获取用户列表,支持分页筛选 |
| 查询单个 | GET /users/{id} |
根据 ID 查询单个用户 |
| 新增 | POST /users |
创建新用户 |
| 全量更新 | PUT /users/{id} |
完整更新用户信息 |
| 局部更新 | PATCH /users/{id} |
只更新部分字段 |
| 删除 | DELETE /users/{id} |
删除指定用户 |
6.3 常见状态码
| 状态码 | 含义 | 使用场景 |
|---|---|---|
| 200 | OK | 查询、更新成功 |
| 201 | Created | 创建成功 |
| 204 | No Content | 删除成功,无响应体 |
| 400 | Bad Request | 参数错误 |
| 401 | Unauthorized | 未登录/Token无效 |
| 403 | Forbidden | 无权限 |
| 404 | Not Found | 资源不存在 |
| 422 | Unprocessable Entity | 参数校验失败(FastAPI默认) |
| 500 | Internal Server Error | 服务器内部错误 |
6.4 指定响应状态码
可以在装饰器中用 status_code 指定成功时的状态码:
python
from fastapi import FastAPI, status
app = FastAPI()
# 创建成功返回 201
@app.post("/users/", status_code=status.HTTP_201_CREATED)
def create_user(username: str):
return {"username": username}
# 删除成功返回 204
@app.delete("/users/{user_id}", status_code=status.HTTP_204_NO_CONTENT)
def delete_user(user_id: int):
# 删除逻辑...
return None
推荐使用 status 模块的常量,而不是直接写数字,可读性更好。
七、自动接口文档(前后端联调神器)
7.1 两套自动文档
FastAPI 原生提供两套自动生成的接口文档,不需要任何额外配置:
-
Swagger UI :交互式文档,可在线调试,地址
/docs -
ReDoc :简洁文档,适合打印和阅读,地址
/redoc
启动项目后直接访问即可:
-
Swagger UI:
http://127.0.0.1:8000/docs -
ReDoc:
http://127.0.0.1:8000/redoc
7.2 Swagger UI 使用教程
Swagger UI 是开发和联调最常用的工具,功能非常强大:
-
接口列表:按标签分组展示所有接口,点击展开查看详情
-
参数说明:自动显示参数名、类型、是否必填、默认值、描述
-
Try it out:点击按钮后可以直接在浏览器中发送请求,测试接口
-
响应示例:展示响应状态码、响应体结构、示例数据
使用步骤:
-
打开
/docs,找到要测试的接口 -
点击右上角
Try it out按钮 -
填写参数(路径参数、查询参数、请求体)
-
点击
Execute发送请求 -
查看
Responses中的返回结果

7.3 文档信息配置
创建 FastAPI 应用时可以配置文档展示信息:
python
from fastapi import FastAPI
app = FastAPI(
title="用户管理系统 API",
description="这是一个基于 FastAPI 的用户管理后台系统,提供完整的用户 CRUD 接口",
version="1.0.0",
terms_of_service="https://example.com/terms/",
contact={
"name": "技术支持",
"url": "https://example.com/contact/",
"email": "support@example.com",
},
license_info={
"name": "Apache 2.0",
"url": "https://www.apache.org/licenses/LICENSE-2.0.html",
},
)

7.4 接口标签与描述
用 tags 给接口分组,用 summary 和 description 添加说明:
python
@app.get(
"/users/{user_id}",
tags=["用户管理"],
summary="根据ID查询用户",
response_description="用户详情"
)
def get_user(user_id: int):
"""
根据用户ID查询用户详细信息
- **user_id**: 用户ID,必填
- 返回用户的完整信息
"""
return {"user_id": user_id, "username": "test"}
说明:
-
tags:接口分组标签,文档中按标签分类展示 -
summary:接口简短摘要,显示在列表中 -
函数 docstring:接口详细描述,支持 Markdown 格式
-
response_description:响应说明

7.5 OpenAPI Schema
FastAPI 自动生成符合 OpenAPI 3.0 规范的 JSON Schema,地址:
http://127.0.0.1:8000/openapi.json
这个 Schema 可以导入到 Postman、Apifox 等工具,也可以用来生成前端 SDK、Mock 数据等。
八、阶段综合实战:用户管理基础接口
8.1 实战目标
整合本阶段所有知识点,实现一套基础用户管理接口:
-
用户列表接口(分页 + 关键词搜索)
-
用户详情接口(根据ID查询)
-
新增用户接口
-
更新用户接口
-
删除用户接口
使用内存字典模拟数据库,不涉及真实数据库(下阶段再整合 SQLModel)。
8.2 完整实战代码
python
from fastapi import FastAPI, HTTPException, status
from typing import Optional
app = FastAPI(
title="用户管理系统",
description="FastAPI第一阶段实战:基础用户CRUD接口",
version="1.0"
)
# 模拟数据库:内存字典存储
fake_db = {
1: {"id": 1, "username": "admin", "email": "admin@example.com", "age": 25},
2: {"id": 2, "username": "zhangsan", "email": "zhangsan@qq.com", "age": 22},
3: {"id": 3, "username": "lisi", "email": "lisi@qq.com", "age": 30},
}
next_id = 4
# ========== 1. 用户列表(分页 + 搜索) ==========
@app.get("/users/", tags=["用户管理"], summary="用户列表")
def list_users(
page: int = 1,
page_size: int = 10,
keyword: Optional[str] = None
):
"""
获取用户列表,支持分页和关键词搜索
- **page**: 页码,默认1
- **page_size**: 每页数量,默认10
- **keyword**: 搜索关键词,可选,匹配用户名/邮箱
"""
# 转换为列表
user_list = list(fake_db.values())
# 关键词过滤
if keyword:
keyword = keyword.lower()
user_list = [
u for u in user_list
if keyword in u["username"].lower() or keyword in u["email"].lower()
]
# 分页
total = len(user_list)
start = (page - 1) * page_size
end = start + page_size
items = user_list[start:end]
return {
"page": page,
"page_size": page_size,
"total": total,
"items": items
}
# ========== 2. 用户详情 ==========
@app.get("/users/{user_id}", tags=["用户管理"], summary="用户详情")
def get_user(user_id: int):
"""根据用户ID查询详情"""
user = fake_db.get(user_id)
if not user:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="用户不存在"
)
return user
# ========== 3. 新增用户 ==========
@app.post("/users/", tags=["用户管理"], summary="新增用户", status_code=status.HTTP_201_CREATED)
def create_user(username: str, email: str, age: Optional[int] = None):
"""创建新用户"""
global next_id
# 检查用户名是否已存在
for u in fake_db.values():
if u["username"] == username:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="用户名已存在"
)
new_user = {
"id": next_id,
"username": username,
"email": email,
"age": age
}
fake_db[next_id] = new_user
next_id += 1
return new_user
# ========== 4. 更新用户 ==========
@app.put("/users/{user_id}", tags=["用户管理"], summary="更新用户")
def update_user(
user_id: int,
username: Optional[str] = None,
email: Optional[str] = None,
age: Optional[int] = None
):
"""更新用户信息,传什么更新什么"""
user = fake_db.get(user_id)
if not user:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="用户不存在"
)
if username is not None:
user["username"] = username
if email is not None:
user["email"] = email
if age is not None:
user["age"] = age
return user
# ========== 5. 删除用户 ==========
@app.delete("/users/{user_id}", tags=["用户管理"], summary="删除用户", status_code=status.HTTP_204_NO_CONTENT)
def delete_user(user_id: int):
"""删除指定用户"""
if user_id not in fake_db:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="用户不存在"
)
del fake_db[user_id]
return None
8.3 运行测试
-
启动服务:
uvicorn main:app --reload -
打开文档:
http://127.0.0.1:8000/docs -
逐个点击
Try it out测试每个接口 -
测试异常场景:查询不存在的用户、重复用户名等
8.4 实战覆盖的知识点
-
✅ 路径参数:
user_id -
✅ 查询参数:
page、page_size、keyword -
✅ 默认值与可选参数
-
✅ GET / POST / PUT / DELETE 四种请求方法
-
✅ HTTPException 异常抛出
-
✅ status 状态码常量
-
✅ tags 接口分组
-
✅ summary 接口摘要
-
✅ docstring 接口详细描述
-
✅ 自动文档与在线调试
九、阶段核心总结
9.1 核心知识点清单
本阶段需要掌握的核心内容:
-
FastAPI 三大优势:自动文档、类型驱动、高性能异步
-
环境搭建:fastapi + uvicorn 安装与启动
-
路径参数 :
{param}语法、自动类型转换、枚举限制 -
查询参数:默认值、必填、可选(Optional)、布尔类型
-
请求方法:GET/POST/PUT/PATCH/DELETE 语义区别
-
RESTful 规范:资源为中心的 URL 设计
-
状态码:200/201/204/400/401/403/404/422/500
-
自动文档 :
/docsSwagger UI、/redocReDoc -
异常处理:HTTPException 抛出业务异常
9.2 一天学习节奏建议
| 时间段 | 内容 | 目标 |
|---|---|---|
| 上午 0.5h | 环境安装 + 第一个接口 | 跑通 Hello World,访问文档 |
| 上午 1h | 路径参数 + 查询参数 | 掌握两种传参方式,写 5 个以上练习接口 |
| 下午 1h | 请求方法 + RESTful + 状态码 | 理解 HTTP 方法语义,设计标准 REST 接口 |
| 下午 0.5h | 自动文档 + 标签描述 | 熟练使用 Swagger UI 调试 |
| 下午 1h | 综合实战:用户 CRUD | 独立完成完整的用户管理接口 |
十、新手高频避坑指南
10.1 新手高频踩坑点
坑1:路径参数顺序错误
固定路径必须写在动态路径前面,否则固定路径会被动态参数匹配到。
python
# ❌ 错误:/users/me 会被匹配成 user_id=me
@app.get("/users/{user_id}")
def get_user(user_id: int): ...
@app.get("/users/me")
def get_me(): ...
# ✅ 正确:固定路径在前
@app.get("/users/me")
def get_me(): ...
@app.get("/users/{user_id}")
def get_user(user_id: int): ...
坑2:查询参数忘记写默认值
没有默认值的参数就是必填的,不传就报 422 错误。可选参数必须给默认值 None。
python
# ❌ 错误:keyword 必填,不传报错
@app.get("/search/")
def search(keyword: str): ...
# ✅ 正确:可选参数
@app.get("/search/")
def search(keyword: str | None = None): ...
坑3:路径末尾斜杠不一致
/items 和 /items/ 是两个不同的路径,FastAPI 会自动重定向,但建议统一风格。
坑4:POST 请求用查询参数传数据
POST/PUT 应该用请求体(Body)传数据,而不是查询参数。查询参数只用于 GET 的筛选条件。本阶段先用查询参数演示,下阶段学请求体。
坑5:忘记 --reload 导致修改不生效
开发模式一定要加 --reload,否则代码修改后服务器不会自动重启。生产环境不要加。
坑6:状态码用数字而不是常量
推荐使用 from fastapi import status 的常量,如 status.HTTP_404_NOT_FOUND,可读性更好,不容易写错。
10.2 最佳实践
-
所有接口都加
tags分组,文档更清晰 -
重要接口写
summary和 docstring 描述 -
统一使用
status模块的状态码常量 -
GET 查询用查询参数,POST/PUT 用请求体(下阶段学)
-
路径用名词复数形式:
/users、/orders -
开发模式开启
--reload,生产环境关闭