从零开始写 FastAPI 后端:一次真实项目中的技术选型与落地实践
开篇:为什么选择 FastAPI?

去年年初,我们团队需要开发一个轻量级的后台服务,用来支撑一个面向企业用户的 SaaS 应用。核心需求包括快速构建 RESTful API、良好的异步支持、高性能以及自动生成接口文档。当时在 Python 技术栈中,我们的可选项主要是 Flask 和 Django。
说实话,在权衡了很久之后,我最终选择了 FastAPI —— 一个相对较新的 Web 框架。这个决定在当时团队内部还引发了一些争议:“FastAPI 稳定吗?”“社区生态是否足够成熟?”这些问题我们都认真讨论过。但经过几个月的项目实战后,我可以负责任地说:这是一次非常正确的技术选型。
这篇文章我希望以第一人称的方式,和你分享我是如何从零开始搭建一个基于 FastAPI 的后端服务,过程中遇到了哪些坑,以及一些来自生产环境的实际经验。
问题描述:一个典型的后端服务需求场景

我们开发的是一个数据同步模块,主要负责从客户的 CRM 系统拉取数据,并通过 Webhook 或者定时任务将数据推送到客户自己的数据中心。整个系统需要满足以下几点关键要求:
- 高可用、低延迟
- 支持多租户架构
- 接口需有完善的鉴权机制(JWT)
- 自动生成文档,方便前端对接
- 要能轻松扩展业务逻辑,比如新增数据源类型
最开始的时候,我们尝试使用 Flask 实现了一个 MVP(最小可行产品),但很快发现两个问题:
- Flask 对异步请求的支持并不友好,我们很多地方需要用到协程来提高并发性能。
- 接口文档需要手动维护,前端同学每次都要反复确认参数格式。
这两个痛点让我们意识到必须换一个更适合现代 API 开发的框架。
解决方案:FastAPI 为何脱颖而出?

FastAPI 是基于 Starlette 构建的一个现代 Python Web 框架,内置了自动化的接口文档支持(OpenAPI + Swagger UI),并且天然支持 async/await 编写异步代码。它的几个关键特性让我最终拍板:
- 自动生成交互式文档(Swagger / ReDoc) ✅
- 异步编程支持 ✅
- 类型校验(Pydantic) ✅
- 性能媲美 Go 和 Node.js ✅
- 社区活跃且持续更新 ✅
另外值得一提的是,它对依赖注入和中间件的支持也非常灵活,这为我们在后续开发中统一处理权限验证、日志记录等打下了基础。
代码实践:快速上手示例

我们先从最简单的例子入手,看看 FastAPI 的基本用法:
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
price: float
is_offer: bool = None
@app.get("/")
def read_root():
return {"Hello": "World"}
@app.put("/items/{item_id}")
def update_item(item_id: int, item: Item):
return {
"item_id": item_id,
"name": item.name,
"price": item.price
}
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8000)
这段代码做了几件事:
- 定义了一个 Pydantic Model
Item来做请求体的数据结构校验; - 定义了两个路由:GET
/和 PUT/items/{item_id}; - 使用 Uvicorn 启动服务,默认监听 8000 端口;
运行起来以后,访问 http://localhost:8000/docs 就会看到自动生成的 OpenAPI 文档界面。
是不是比手动写 Postman 注释舒服多了?😉
项目架构设计:如何组织一个大型 FastAPI 工程?
随着业务功能越来越多,我们不能把所有代码都塞在一个文件里。为了保证项目的可维护性和扩展性,我们采用了下面这种标准结构:
project/
├── main.py # 启动文件
├── app/
│ ├── __init__.py
│ ├── api/
│ │ ├── __init__.py
│ │ ├── routes/
│ │ │ └── user.py # 用户相关路由
│ │ └── v1/
│ │ └── __init__.py
│ ├── core/
│ │ └── config.py # 配置管理
│ ├── models/
│ │ └── user.py # ORM 映射模型
│ ├── schemas/
│ │ └── user.py # Pydantic Schema
│ └── services/
│ └── user_service.py # 业务逻辑封装
└── requirements.txt
这套结构虽然略显复杂,但非常适合多人协作开发。每个模块职责清晰:
models层用于定义数据库表结构(结合 SQLAlchemy);schemas层处理 API 请求/响应的格式;services层包含核心业务逻辑,避免控制器膨胀;routes层只关注 HTTP 接口的映射。
数据库设计与接口设计考虑
我们使用 SQLAlchemy 做 ORM,配合 asyncpg 提供的异步数据库驱动,实现 PostgreSQL 的连接与操作。
这里举个简化版的 User 模型定义:
# app/models/user.py
from sqlalchemy import Column, Integer, String, DateTime
from database import Base
class User(Base):
__tablename__ = "users"
id = Column(Integer, primary_key=True)
email = Column(String, unique=True, index=True)
password_hash = Column(String)
created_at = Column(DateTime)
对应的 schema(返回给前端的数据格式)是这样的:
# app/schemas/user.py
from pydantic import BaseModel
class UserBase(BaseModel):
email: str
class UserCreate(UserBase):
password: str
class UserOut(UserBase):
id: int
created_at: datetime.datetime
这样设计的好处在于,我们可以根据不同上下文复用这些类。例如注册时用的是 UserCreate,返回结果用的是 UserOut,完全隔离了敏感字段。
踩坑经验:FastAPI 开发中遇到的真实问题
1. 异步 SQLAlchemy 不够优雅?
刚开始用 FastAPI 时,我发现如果想用异步 SQLAlchemy,要引入第三方包如 sqlalchemy.ext.asyncio,同时还要调整 session 的创建方式。
from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine
from sqlalchemy.orm import sessionmaker
engine = create_async_engine("postgresql+asyncpg://...", echo=True)
AsyncSessionLocal = sessionmaker(engine, class_=AsyncSession, expire_on_commit=False)
# 获取 db 的依赖
async def get_db():
async with AsyncSessionLocal() as session:
yield session
一开始不熟悉这些异步模式,导致有些函数没有正确 await,出现了奇怪的错误。后来逐步总结出一条原则:能异步就尽量异步,避免混用阻塞和非阻塞调用。
2. JWT 鉴权怎么集成到 FastAPI 中?
我们采用 OAuth2PasswordBearer 这种标准的方式来处理登录认证流程,并结合 JWT 做 token 验签。
# app/core/security.py
from fastapi.security import OAuth2PasswordBearer
from jose import jwt, JWTError
from datetime import datetime, timedelta
SECRET_KEY = "your-secret-key-here"
ALGORITHM = "HS256"
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/login")
def create_access_token(data: dict, expires_delta: timedelta = None):
to_encode = data.copy()
if expires_delta:
expire = datetime.utcnow() + expires_delta
else:
expire = datetime.utcnow() + timedelta(minutes=15)
to_encode.update({"exp": expire})
encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
return encoded_jwt
然后在接口中加一个 Depends(get_current_user) 这样的装饰器来确保认证通过:
# app/api/routes/user.py
from fastapi import Depends
@app.get("/user/me")
async def read_users_me(current_user: User = Depends(get_current_user)):
return current_user
3. 文档路径被 Nginx 反向代理遮挡怎么办?
部署上线后发现一个很尴尬的问题:生成的接口文档只能在本地访问,一旦放到线上服务器,/docs 页面就打不开。
排查后发现问题出在反向代理配置上。因为默认 FastAPI 文档资源路径是 /openapi.json、/docs、/redoc,而我们在 Nginx 中没有开放这些路径。
解决方法是在 Nginx 加入允许静态文件转发的规则:
location ~ ^/(docs|redoc|openapi\.json) {
proxy_pass http://your-fastapi-server;
}
这个问题其实挺常见的,尤其是在前后端部署路径不一致的情况下,记得提前检查文档地址是否被拦截。
效果总结:FastAPI 在项目中的实际收益
项目上线已经半年多了,整体运行非常稳定。我们从最初的单机部署升级到了 Docker 容器化 + K8s 编排,QPS 也从最初的几百提升到目前的万级峰值。
几个显著的收益点:
- 文档自动化节省沟通成本:前端同学再也不用追着后端问字段含义了,直接看
/docs; - 异步优势明显:部分耗时的 I/O 操作(如发送邮件、外部 API 调用)利用 async 减少了等待时间;
- 类型安全带来更少 bug:Pydantic 的强校验机制减少了大量接口报错;
- 架构清晰易于维护:各层分离让新同事接手更快,测试覆盖率更高。
经验分享:给新手的一些实用建议
如果你打算开始学习或使用 FastAPI,以下是我在实践中总结的一些小建议:
🌟 学好 Pydantic,越早越好
FastAPI 的灵魂就是 Pydantic,几乎所有的输入输出都是靠它来完成。理解它的原理和最佳实践,会让你事半功倍。
🧼 分离业务逻辑和服务层
不要把所有逻辑都塞进路由函数里面。抽出来一个 service 层,既方便单元测试,也能避免 controller 膨胀。
⚡ 多用异步,但也要合理使用
不是所有的操作都需要异步,有时候简单的同步函数反而更容易调试。根据 I/O 密集还是 CPU 密集来判断即可。
🔐 安全永远第一位
即使是内部接口也要注意鉴权、限流、输入校验。可以考虑用中间件统一处理异常和日志。
💡 结合现代运维工具一起使用
Docker + Gunicorn/Uvicorn + Nginx + Prometheus 是一个非常稳定的组合。配合 Kubernetes 做自动伸缩更是锦上添花。
写在最后:技术选型,从来不是一个简单命题
FastAPI 并不是银弹,但它确实解决了我们在实际项目中最头疼的几个问题:接口文档难维护、异步支持差、性能瓶颈明显。
作为一位后端开发人员,我觉得选择一款合适的框架不仅要看它有多酷炫,更要结合自己的业务特点、团队能力和未来的发展方向。FastAPI 正是因为在这三方面都能给出满意的答案,才让我愿意在多个项目中持续推广使用。
如果你也是 Python 后端开发者,还在纠结用 Flask 还是 Django,不妨试试 FastAPI,或许你会发现一种全新的开发体验。
希望这篇文章对你有所帮助,欢迎留言交流你的看法!

评论 0