FastAPI 实战入门:用 Python 最快的框架 30 分钟搭建 REST API

2829 字
14 分钟
FastAPI 实战入门:用 Python 最快的框架 30 分钟搭建 REST API

为什么是 FastAPI?#

如果你是前端开发者,习惯了 Express、Koa 或 Hono 的开发体验,那么你在 Python 世界里要找的那个框架,就是 FastAPI

先看一组数据对比:

框架语言请求/秒 (JSON 序列化)类型安全自动文档
ExpressNode.js~15,000❌ (需 TS)
FlaskPython~1,200
Django RESTPython~800需插件
FastAPIPython~9,500✅ 原生✅ 原生

FastAPI 比 Flask 快 8 倍,而且自带类型校验和交互式 API 文档。这不是营销话术——它底层用了 Starlette(ASGI 框架)+ Pydantic(数据校验),性能接近 Go 和 Node.js。

💡 GitHub 81k+ stars,是 2026 年 Python Web 框架的事实标准。如果你只学一个 Python 后端框架,就是它了。

环境准备#

Terminal window
# 创建项目目录
mkdir fastapi-demo && cd fastapi-demo
# 建议用虚拟环境(别污染全局)
python -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
# 安装依赖
pip install "fastapi[standard]"

fastapi[standard] 会自动装上 uvicorn(ASGI 服务器)、pydanticpython-multipart 等常用依赖。一行搞定,不用像 Django 那样装一堆。

第一个 API:5 行代码#

创建 main.py

from fastapi import FastAPI
app = FastAPI()
@app.get("/")
async def root():
return {"message": "Hello, FastAPI!"}

启动:

Terminal window
uvicorn main:app --reload

打开 http://localhost:8000,你会看到 JSON 响应。再打开 http://localhost:8000/docs——惊喜来了,自动生成的 Swagger UI 交互文档,不用写一行配置。

这就是 FastAPI 的核心哲学:约定优于配置,类型即文档

路由与路径参数#

FastAPI 的路由设计非常直觉化,如果你写过 Express,几乎零学习成本:

from fastapi import FastAPI
app = FastAPI(title="书店 API", version="1.0.0")
# 路径参数 —— 类型自动校验
@app.get("/books/{book_id}")
async def get_book(book_id: int):
return {"book_id": book_id}
# 查询参数 —— 默认值 = 可选参数
@app.get("/books")
async def list_books(page: int = 1, size: int = 10, keyword: str | None = None):
return {"page": page, "size": size, "keyword": keyword}

注意 book_id: int 这个类型标注——它不是装饰,是真正的运行时校验。如果你请求 /books/abc,FastAPI 会自动返回 422 错误:

{
"detail": [
{
"type": "int_parsing",
"loc": ["path", "book_id"],
"msg": "Input should be a valid integer",
"input": "abc"
}
]
}

不用写一行校验代码。这就是”为什么用 FastAPI”的第一个答案。

Pydantic 模型:数据校验的核弹#

这是 FastAPI 最强大的武器。如果你用过 TypeScript 的 Zod 或 io-ts,Pydantic 就是 Python 版的运行时类型校验。

from pydantic import BaseModel, Field
from datetime import datetime
class BookCreate(BaseModel):
"""创建图书的请求体"""
title: str = Field(..., min_length=1, max_length=200, examples=["Python 编程导论"])
author: str = Field(..., min_length=1, max_length=100)
price: float = Field(..., gt=0, le=9999.99, description="价格,单位:元")
isbn: str = Field(..., pattern=r"^\d{13}$", description="13 位 ISBN")
tags: list[str] = Field(default_factory=list, max_length=10)
class BookResponse(BookCreate):
"""返回的图书数据"""
id: int
created_at: datetime
model_config = {"from_attributes": True} # 支持 ORM 对象转换

为什么要分 BookCreateBookResponse?这是 FastAPI 的最佳实践——请求模型和响应模型分离。就像前端的 DTO(Data Transfer Object)一样:

  • 创建时不需要 idcreated_at(服务端生成)
  • 返回时需要完整信息
  • 避免内部字段泄露(比如密码哈希)

CRUD 实战:完整的书店 API#

下面是一个功能完整的内存版书店 API。生产环境换成数据库就行,逻辑完全一样:

from fastapi import FastAPI, HTTPException, Query
from pydantic import BaseModel, Field
from datetime import datetime
app = FastAPI(title="书店 API", version="1.0.0")
# ---------- 模型 ----------
class BookCreate(BaseModel):
title: str = Field(..., min_length=1, max_length=200)
author: str = Field(..., min_length=1, max_length=100)
price: float = Field(..., gt=0, le=9999.99)
tags: list[str] = Field(default_factory=list)
class BookUpdate(BaseModel):
title: str | None = None
author: str | None = None
price: float | None = Field(None, gt=0, le=9999.99)
tags: list[str] | None = None
class BookResponse(BookCreate):
id: int
created_at: datetime
# ---------- 模拟数据库 ----------
db: dict[int, dict] = {}
counter = 0
# ---------- CRUD 路由 ----------
@app.post("/books", response_model=BookResponse, status_code=201)
async def create_book(book: BookCreate):
global counter
counter += 1
now = datetime.now()
record = {"id": counter, **book.model_dump(), "created_at": now}
db[counter] = record
return record
@app.get("/books", response_model=list[BookResponse])
async def list_books(
page: int = Query(1, ge=1),
size: int = Query(10, ge=1, le=100),
author: str | None = None,
):
items = list(db.values())
if author:
items = [b for b in items if author.lower() in b["author"].lower()]
start = (page - 1) * size
return items[start : start + size]
@app.get("/books/{book_id}", response_model=BookResponse)
async def get_book(book_id: int):
if book_id not in db:
raise HTTPException(status_code=404, detail=f"图书 {book_id} 不存在")
return db[book_id]
@app.put("/books/{book_id}", response_model=BookResponse)
async def update_book(book_id: int, book: BookUpdate):
if book_id not in db:
raise HTTPException(status_code=404, detail=f"图书 {book_id} 不存在")
# 只更新传入的字段(None 的跳过)
update_data = book.model_dump(exclude_unset=True)
db[book_id].update(update_data)
return db[book_id]
@app.delete("/books/{book_id}", status_code=204)
async def delete_book(book_id: int):
if book_id not in db:
raise HTTPException(status_code=404, detail=f"图书 {book_id} 不存在")
del db[book_id]

几个值得说的设计细节:

  1. response_model —— 自动过滤响应字段,只返回模型定义的内容,防止数据泄露
  2. status_code=201 —— 创建资源用 201,不是 200。RESTful 规范,别含糊
  3. exclude_unset=True —— 局部更新的关键。没传的字段不覆盖,避免 PUT 把所有字段重置为 None
  4. HTTPException —— FastAPI 内置的错误抛出方式,自动转成标准 JSON 错误响应

依赖注入:FastAPI 的杀手锏#

这是 FastAPI 区别于所有其他 Python 框架的核心特性。如果你用过 Angular 或 NestJS 的 DI,会觉得很亲切:

from fastapi import Depends, Header, HTTPException
# 模拟认证:从 Header 中提取 token
async def get_current_user(authorization: str = Header(...)):
if not authorization.startswith("Bearer "):
raise HTTPException(status_code=401, detail="无效的认证格式")
token = authorization.replace("Bearer ", "")
# 实际项目这里解析 JWT
if token != "secret-token-123":
raise HTTPException(status_code=401, detail="认证失败")
return {"user_id": 1, "username": "admin"}
# 权限检查:依赖可以嵌套
async def require_admin(user: dict = Depends(get_current_user)):
if user["username"] != "admin":
raise HTTPException(status_code=403, detail="需要管理员权限")
return user
# 使用依赖
@app.delete("/books/{book_id}", status_code=204)
async def delete_book(book_id: int, user: dict = Depends(require_admin)):
"""只有管理员才能删除图书"""
if book_id not in db:
raise HTTPException(status_code=404, detail=f"图书 {book_id} 不存在")
del db[book_id]

为什么依赖注入比中间件更好?

  • 中间件是全局的,依赖注入是路由级的——精确控制
  • 依赖可以有返回值(比如当前用户),中间件只能往 request 上挂属性
  • 依赖自动出现在 API 文档里,中间件不会
  • 依赖可以组合、嵌套、复用,像乐高积木

中间件与 CORS#

前端开发者最熟悉的痛点——跨域。FastAPI 一行搞定:

from fastapi.middleware.cors import CORSMiddleware
app.add_middleware(
CORSMiddleware,
allow_origins=["http://localhost:5173", "https://your-domain.com"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)

自定义中间件也很直观:

import time
from starlette.middleware.base import BaseHTTPMiddleware
from starlette.requests import Request
class TimingMiddleware(BaseHTTPMiddleware):
async def dispatch(self, request: Request, call_next):
start = time.perf_counter()
response = await call_next(request)
duration = time.perf_counter() - start
response.headers["X-Process-Time"] = f"{duration:.4f}"
if duration > 1.0:
print(f"⚠️ 慢请求: {request.method} {request.url.path} 耗时 {duration:.2f}s")
return response
app.add_middleware(TimingMiddleware)

错误处理:别让用户看到 500#

生产环境最怕的就是裸奔的 500 错误。FastAPI 提供了全局异常处理器:

from fastapi import Request
from fastapi.responses import JSONResponse
from pydantic import ValidationError
@app.exception_handler(Exception)
async def global_exception_handler(request: Request, exc: Exception):
"""兜底:所有未捕获的异常"""
# 生产环境应该接入日志系统(Sentry、ELK 等)
print(f"❌ 未处理异常: {type(exc).__name__}: {exc}")
return JSONResponse(
status_code=500,
content={
"detail": "服务器内部错误",
"type": type(exc).__name__,
},
)
@app.exception_handler(404)
async def not_found_handler(request: Request, exc: HTTPException):
return JSONResponse(
status_code=404,
content={"detail": "资源不存在", "path": str(request.url.path)},
)

常见坑:很多人在 FastAPI 里用 try/except 包裹每个路由。别这样——用全局异常处理器统一兜底,路由里只处理业务逻辑级别的异常(比如”用户不存在”),系统级异常交给全局处理器。

项目结构:从玩具到生产#

当你的 main.py 超过 200 行,就该拆分了。推荐这种结构:

fastapi-demo/
├── app/
│ ├── __init__.py
│ ├── main.py # 入口:创建 app、注册路由
│ ├── config.py # 配置管理
│ ├── dependencies.py # 公共依赖(认证、数据库连接等)
│ ├── routers/
│ │ ├── __init__.py
│ │ ├── books.py # 图书相关路由
│ │ └── users.py # 用户相关路由
│ ├── models/
│ │ ├── __init__.py
│ │ └── book.py # Pydantic 模型
│ └── services/
│ ├── __init__.py
│ └── book_service.py # 业务逻辑
├── tests/
│ ├── test_books.py
│ └── conftest.py
├── requirements.txt
└── Dockerfile

路由拆分用 APIRouter

app/routers/books.py
from fastapi import APIRouter
router = APIRouter(prefix="/books", tags=["图书管理"])
@router.get("/")
async def list_books():
...
# app/main.py
from app.routers import books, users
app = FastAPI()
app.include_router(books.router)
app.include_router(users.router)

和 Express 的 Router 几乎一模一样,前端同学上手零成本。

配置管理:别硬编码#

app/config.py
from pydantic_settings import BaseSettings
class Settings(BaseSettings):
app_name: str = "书店 API"
debug: bool = False
database_url: str = "sqlite:///./app.db"
secret_key: str = "change-me-in-production"
allowed_origins: list[str] = ["http://localhost:5173"]
model_config = {"env_file": ".env", "env_file_encoding": "utf-8"}
settings = Settings()

Pydantic Settings 会自动从环境变量.env 文件读取配置,类型自动转换。比如 DEBUG=true 会被解析为 Python 的 True。这比 os.getenv() 然后手动转类型优雅 10 倍。

测试:用 httpx 替代 requests#

FastAPI 官方推荐用 httpx 做测试,支持异步:

tests/test_books.py
import pytest
from httpx import AsyncClient, ASGITransport
from app.main import app
@pytest.fixture
async def client():
transport = ASGITransport(app=app)
async with AsyncClient(transport=transport, base_url="http://test") as ac:
yield ac
@pytest.mark.anyio
async def test_create_book(client: AsyncClient):
response = await client.post("/books", json={
"title": "Python 编程导论",
"author": "John Guttag",
"price": 89.00,
"tags": ["Python", "入门"]
})
assert response.status_code == 201
data = response.json()
assert data["title"] == "Python 编程导论"
assert "id" in data
assert "created_at" in data
@pytest.mark.anyio
async def test_get_nonexistent_book(client: AsyncClient):
response = await client.get("/books/99999")
assert response.status_code == 404

测试不需要启动真实服务器——ASGITransport 直接在内存中模拟请求,速度飞快。

性能调优:几个立竿见影的技巧#

1. 用 async def 还是 def#

# ✅ I/O 密集型(数据库查询、HTTP 请求)—— 用 async def
@app.get("/books")
async def list_books():
books = await db.fetch_all("SELECT * FROM books")
return books
# ✅ CPU 密集型(图片处理、计算)—— 用普通 def
# FastAPI 会自动放到线程池执行,不阻塞事件循环
@app.get("/compute")
def heavy_computation():
result = sum(i * i for i in range(10_000_000))
return {"result": result}

常见坑:在 async def 里调用同步阻塞函数(比如 requests.get())。这会阻塞整个事件循环!要么用 httpx(异步),要么用 run_in_executor 包装。

2. 响应模型优化#

# ❌ 每次都序列化所有字段
@app.get("/books", response_model=list[BookResponse])
async def list_books(): ...
# ✅ 列表接口返回精简模型
class BookSummary(BaseModel):
id: int
title: str
author: str
price: float
@app.get("/books", response_model=list[BookSummary])
async def list_books(): ...

列表接口不需要返回所有字段。减少序列化开销,减少网络传输,前端也不用处理一堆用不到的数据。

3. 生产部署命令#

Terminal window
# 开发
uvicorn app.main:app --reload --port 8000
# 生产(多 worker)
uvicorn app.main:app --host 0.0.0.0 --port 8000 --workers 4
# 更高性能:用 gunicorn + uvicorn worker
gunicorn app.main:app -w 4 -k uvicorn.workers.UvicornWorker --bind 0.0.0.0:8000

快速容器化#

FastAPI 项目天然适合 Docker 部署。一个精简的 Dockerfile:

FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY ./app ./app
EXPOSE 8000
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000", "--workers", "2"]
Terminal window
docker build -t bookstore-api .
docker run -d -p 8000:8000 --name bookstore bookstore-api

5 分钟就能跑起来。生产环境加上 --restart=always 和健康检查就行。

和 Flask 的真实对比#

最后说点主观判断。我同时用过 Flask 和 FastAPI 做项目,体感差异:

维度FlaskFastAPI
学习曲线极低(太简单以至于什么都要自己装)低(约定多,但省事)
类型安全无(靠自觉)原生支持(Pydantic)
API 文档需要 flask-restx/flasgger自动生成
异步支持Flask 2.0 后勉强支持原生 ASGI,一等公民
生态巨大(但很多库年久失修)快速增长(现代库为主)
适合场景简单页面、小工具API 服务、微服务、数据平台

一句话总结:2026 年新项目,选 FastAPI。Flask 不是不行,但 FastAPI 在类型安全、性能和开发体验上全面领先。

总结#

FastAPI 的核心理念可以浓缩成三个词:类型驱动、约定优先、性能至上

  • 类型标注不是装饰——它驱动了校验、文档、序列化整条链路
  • 依赖注入不是过度设计——它让认证、权限、数据库连接变得可测试、可组合
  • 异步不是可选项——在 I/O 密集的 API 场景下,async/await 带来的并发能力是质的飞跃

如果你是前端开发者想学后端,FastAPI 是最短路径。如果你是 Python 开发者还在用 Flask,是时候升级了。

下一篇我们聊聊如何用 SQLAlchemy + Alembic 给这个书店 API 接上真正的数据库。Stay tuned 🚀

文章分享

如果这篇文章对你有帮助,欢迎分享给更多人!

FastAPI 实战入门:用 Python 最快的框架 30 分钟搭建 REST API
https://boke.hackerdream.xyz/posts/fastapi-rest-api-practical-guide/
作者
晴天
发布于
2026-04-22
许可协议
CC BY-NC-SA 4.0
Profile Image of the Author
晴天
Hello, I'm 晴天.
公告
欢迎来到我的博客!这是一则示例公告。
音乐
封面

音乐

暂未播放

0:00 0:00
暂无歌词
分类
标签
站点统计
文章
125
分类
17
标签
287
总字数
257,955
运行时长
0
最后活动
0 天前

目录