FastAPI 高性能 Web 框架实战:从请求到数据库一条龙
很多朋友一提到 Python 做 Web 开发,脑子里冒出来的还是 Flask 和 Django。这俩确实经典,但如果你要写接口、做前后端分离,FastAPI 这几年已经成了新的默认选项。我最近把手头一个内部服务的接口层从 Flask 迁到了 FastAPI,踩了一些坑,也尝到了甜头。这篇把核心的东西理一理。
为什么是 FastAPI
先说几个实打实的好处,不带水分:
- 快。 底层是 Starlette + Pydantic,异步支持是一等公民,很多场景吞吐量能甩开同步框架一个量级。
- 类型即校验。 用 Python 类型注解声明请求和响应,Pydantic 自动帮你校验、序列化,顺便生成 OpenAPI 文档。
- 文档白送。 写完直接去
/docs就能看到可交互的 Swagger 界面,接前端的人自己就能对着文档调。
缺点也有:生态不像 Django 那么全家桶,很多东西(ORM 迁移、后台管理)得自己拼。不过对纯 API 服务来说,这反而清爽。
起步:一个最小的应用
先装包:
pip install fastapi uvicorn
写个 main.py:
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
def read_root():
return {"hello": "world"}
@app.get("/items/{item_id}")
def read_item(item_id: int, q: str | None = None):
return {"item_id": item_id, "q": q}
跑起来:
uvicorn main:app --reload
打开 http://127.0.0.1:8000/docs,你会看到自动生成的接口文档,参数、类型一目了然。注意上面那个 item_id: int——如果你访问 /items/abc,FastAPI 会直接返回 422 校验错误,而不是像 Flask 那样你得自己判断造轮子。
请求体与数据校验
这是 FastAPI 最舒服的地方。用 Pydantic 模型声明请求体,校验逻辑全自动:
from pydantic import BaseModel, Field
class Item(BaseModel):
name: str
price: float = Field(gt=0, description="价格必须大于 0")
tags: list[str] = []
@app.post("/items")
def create_item(item: Item):
return {"item_name": item.name, "price": item.price}
如果前端传了 price: -3,直接 422,错误信息还告诉你哪个字段出问题。以前写 Flask 那种手工 request.get_json() 再加一堆 if 判断的日子,真的回不去了。
异步数据库访问
FastAPI 的价值要配合异步数据库驱动才真正体现。以 SQLAlchemy 2.0 的 async 模式 + asyncpg 为例:
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession
from sqlalchemy.orm import sessionmaker
DATABASE_URL = "postgresql+asyncpg://user:pass@localhost/db"
engine = create_async_engine(DATABASE_URL, echo=True)
AsyncSessionLocal = sessionmaker(engine, class_=AsyncSession, expire_on_commit=False)
async def get_db():
async with AsyncSessionLocal() as session:
yield session
然后在路由里注入:
from fastapi import Depends
from sqlalchemy import text
@app.get("/users")
async def list_users(db: AsyncSession = Depends(get_db)):
result = await db.execute(text("SELECT id, name FROM users"))
return [dict(row) for row in result.mappings()]
Depends 是 FastAPI 的依赖注入,get_db 里 yield 出去的东西在请求结束后会自动清理。这个模式写熟了,代码会非常干净。
踩过的几个坑
坑一:同步代码会拖垮异步路由。 如果你在 async def 路由里调了一个同步的阻塞函数(比如 time.sleep 或者同步的 requests 请求),整个事件循环都会被卡住。这种情况要么用 def(不用 async 声明)让 FastAPI 丢到线程池,要么用 await asyncio.to_thread(...)。
坑二:Pydantic v2 的迁移。 FastAPI 现在默认用 Pydantic v2,老项目里的 Config 类写法要改成 model_config,orm_mode 也变成了 from_attributes=True。网上很多旧教程还是 v1 语法,照着抄会报错。
坑三:Depends 的作用域。 依赖默认是请求级缓存的,同一个请求里多次 Depends(get_db) 会复用同一个连接。想要全局单例得自己用 lru_cache 包一层,别想当然。
部署建议
生产环境别再用 --reload 了。一般用多 worker:
uvicorn main:app --host 0.0.0.0 --port 8000 --workers 4
更进阶的是用 Gunicorn 管 uvicorn worker,再前面套一层 Nginx 做反代和 TLS。FastAPI 本身不自带静态文件托管,遇到静态资源要么用 StaticFiles 挂载,要么干脆交给 Nginx。
小结
FastAPI 给我的感觉是:类型注解写起来确实比无类型的 Flask 多花几秒钟,但这几秒钟换回来的是自动校验、自动文档,还有异步带来的实打实的性能提升。如果你下一个项目是写 API,不妨从它开始。跟 Flask 比它更现代,跟 Django 比它更轻,定位卡得刚刚好。
整篇代码都能直接跑,建议照着敲一遍再改,比光看概念有用得多。