fastapi-patterns
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseFastAPI Patterns
FastAPI 模式
Production-oriented patterns for FastAPI services.
面向生产环境的FastAPI服务模式。
When to Use
使用场景
- Building or reviewing a FastAPI app.
- Splitting routers, schemas, dependencies, and database access.
- Writing async endpoints that call a database or external service.
- Adding authentication, authorization, OpenAPI docs, tests, or deployment settings.
- Checking a FastAPI PR for copy-pasteable examples and production risks.
- 构建或评审FastAPI应用。
- 拆分路由、Schema、依赖项和数据库访问逻辑。
- 编写调用数据库或外部服务的异步端点。
- 添加认证、授权、OpenAPI文档、测试或部署配置。
- 检查FastAPI的PR,获取可复制的示例并排查生产风险。
How It Works
工作原理
Treat the FastAPI app as a thin HTTP layer over explicit dependencies and service code:
- owns app construction, middleware, exception handlers, and router registration.
main.py - owns Pydantic request and response models.
schemas/ - owns database, auth, pagination, and request-scoped dependencies.
dependencies.py - or
services/owns business and persistence operations.crud/ - overrides dependencies instead of opening production resources.
tests/
Prefer small routers and explicit declarations. Keep raw ORM objects, secrets, and framework globals out of response schemas.
response_model将FastAPI应用视为显式依赖项与业务代码之上的轻量HTTP层:
- 负责应用构建、中间件、异常处理器和路由注册。
main.py - 目录存放Pydantic请求与响应模型。
schemas/ - 负责数据库、认证、分页和请求作用域的依赖项。
dependencies.py - 或
services/目录存放业务逻辑与持久化操作。crud/ - 通过覆盖依赖项而非直接访问生产资源来实现测试。
tests/
优先使用小型路由和显式的声明。避免在响应Schema中暴露原始ORM对象、密钥和框架全局变量。
response_modelProject Layout
项目结构
text
app/
|-- main.py
|-- config.py
|-- dependencies.py
|-- exceptions.py
|-- api/
| `-- routes/
| |-- users.py
| `-- health.py
|-- core/
| |-- security.py
| `-- middleware.py
|-- db/
| |-- session.py
| `-- crud.py
|-- models/
|-- schemas/
`-- tests/text
app/
|-- main.py
|-- config.py
|-- dependencies.py
|-- exceptions.py
|-- api/
| `-- routes/
| |-- users.py
| `-- health.py
|-- core/
| |-- security.py
| `-- middleware.py
|-- db/
| |-- session.py
| `-- crud.py
|-- models/
|-- schemas/
`-- tests/Application Factory
应用工厂
Use a factory so tests and workers can build the app with controlled settings.
python
from contextlib import asynccontextmanager
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from app.api.routes import health, users
from app.config import settings
from app.db.session import close_db, init_db
from app.exceptions import register_exception_handlers
@asynccontextmanager
async def lifespan(app: FastAPI):
await init_db()
yield
await close_db()
def create_app() -> FastAPI:
app = FastAPI(
title=settings.api_title,
version=settings.api_version,
lifespan=lifespan,
)
app.add_middleware(
CORSMiddleware,
allow_origins=settings.cors_origins,
allow_credentials=bool(settings.cors_origins),
allow_methods=["GET", "POST", "PUT", "PATCH", "DELETE"],
allow_headers=["Authorization", "Content-Type"],
)
register_exception_handlers(app)
app.include_router(health.router, prefix="/health", tags=["health"])
app.include_router(users.router, prefix="/api/v1/users", tags=["users"])
return app
app = create_app()Do not use with ; browsers reject that combination and Starlette disallows it for credentialed requests.
allow_origins=["*"]allow_credentials=True使用工厂模式,以便测试和工作进程可以通过受控配置构建应用。
python
from contextlib import asynccontextmanager
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from app.api.routes import health, users
from app.config import settings
from app.db.session import close_db, init_db
from app.exceptions import register_exception_handlers
@asynccontextmanager
async def lifespan(app: FastAPI):
await init_db()
yield
await close_db()
def create_app() -> FastAPI:
app = FastAPI(
title=settings.api_title,
version=settings.api_version,
lifespan=lifespan,
)
app.add_middleware(
CORSMiddleware,
allow_origins=settings.cors_origins,
allow_credentials=bool(settings.cors_origins),
allow_methods=["GET", "POST", "PUT", "PATCH", "DELETE"],
allow_headers=["Authorization", "Content-Type"],
)
register_exception_handlers(app)
app.include_router(health.router, prefix="/health", tags=["health"])
app.include_router(users.router, prefix="/api/v1/users", tags=["users"])
return app
app = create_app()请勿在时使用;浏览器会拒绝这种组合,Starlette也不允许针对带凭证的请求使用该配置。
allow_credentials=Trueallow_origins=["*"]Pydantic Schemas
Pydantic Schema
Keep request, update, and response models separate.
python
from datetime import datetime
from typing import Annotated
from uuid import UUID
from pydantic import BaseModel, ConfigDict, EmailStr, Field
class UserBase(BaseModel):
email: EmailStr
full_name: Annotated[str, Field(min_length=1, max_length=100)]
class UserCreate(UserBase):
password: Annotated[str, Field(min_length=12, max_length=128)]
class UserUpdate(BaseModel):
email: EmailStr | None = None
full_name: Annotated[str | None, Field(min_length=1, max_length=100)] = None
class UserResponse(UserBase):
model_config = ConfigDict(from_attributes=True)
id: UUID
created_at: datetime
updated_at: datetimeResponse models must never include password hashes, access tokens, refresh tokens, or internal authorization state.
将请求、更新和响应模型分开存放。
python
from datetime import datetime
from typing import Annotated
from uuid import UUID
from pydantic import BaseModel, ConfigDict, EmailStr, Field
class UserBase(BaseModel):
email: EmailStr
full_name: Annotated[str, Field(min_length=1, max_length=100)]
class UserCreate(UserBase):
password: Annotated[str, Field(min_length=12, max_length=128)]
class UserUpdate(BaseModel):
email: EmailStr | None = None
full_name: Annotated[str | None, Field(min_length=1, max_length=100)] = None
class UserResponse(UserBase):
model_config = ConfigDict(from_attributes=True)
id: UUID
created_at: datetime
updated_at: datetime响应模型绝不能包含密码哈希、访问令牌、刷新令牌或内部授权状态。
Dependencies
依赖项
Use dependency injection for request-scoped resources.
python
from collections.abc import AsyncIterator
from uuid import UUID
from fastapi import Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer
from sqlalchemy.ext.asyncio import AsyncSession
from app.core.security import decode_token
from app.db.session import session_factory
from app.models.user import User
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/v1/auth/login")
async def get_db() -> AsyncIterator[AsyncSession]:
async with session_factory() as session:
try:
yield session
await session.commit()
except Exception:
await session.rollback()
raise
async def get_current_user(
token: str = Depends(oauth2_scheme),
db: AsyncSession = Depends(get_db),
) -> User:
payload = decode_token(token)
user_id = UUID(payload["sub"])
user = await db.get(User, user_id)
if user is None:
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid token")
return userAvoid creating sessions, clients, or credentials inline inside route handlers.
使用依赖注入管理请求作用域的资源。
python
from collections.abc import AsyncIterator
from uuid import UUID
from fastapi import Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer
from sqlalchemy.ext.asyncio import AsyncSession
from app.core.security import decode_token
from app.db.session import session_factory
from app.models.user import User
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/v1/auth/login")
async def get_db() -> AsyncIterator[AsyncSession]:
async with session_factory() as session:
try:
yield session
await session.commit()
except Exception:
await session.rollback()
raise
async def get_current_user(
token: str = Depends(oauth2_scheme),
db: AsyncSession = Depends(get_db),
) -> User:
payload = decode_token(token)
user_id = UUID(payload["sub"])
user = await db.get(User, user_id)
if user is None:
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid token")
return user避免在路由处理器内联创建会话、客户端或凭证。
Async Endpoints
异步端点
Keep route handlers async when they perform I/O, and use async libraries inside them.
python
from fastapi import APIRouter, Depends, Query
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from app.dependencies import get_current_user, get_db
from app.models.user import User
from app.schemas.user import UserResponse
router = APIRouter()
@router.get("/", response_model=list[UserResponse])
async def list_users(
limit: int = Query(default=50, ge=1, le=100),
offset: int = Query(default=0, ge=0),
db: AsyncSession = Depends(get_db),
current_user: User = Depends(get_current_user),
):
result = await db.execute(
select(User).order_by(User.created_at.desc()).limit(limit).offset(offset)
)
return result.scalars().all()Use for external HTTP calls from async handlers. Do not call in an async route.
httpx.AsyncClientrequests当端点执行I/O操作时,保持路由处理器为异步模式,并在其中使用异步库。
python
from fastapi import APIRouter, Depends, Query
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from app.dependencies import get_current_user, get_db
from app.models.user import User
from app.schemas.user import UserResponse
router = APIRouter()
@router.get("/", response_model=list[UserResponse])
async def list_users(
limit: int = Query(default=50, ge=1, le=100),
offset: int = Query(default=0, ge=0),
db: AsyncSession = Depends(get_db),
current_user: User = Depends(get_current_user),
):
result = await db.execute(
select(User).order_by(User.created_at.desc()).limit(limit).offset(offset)
)
return result.scalars().all()在异步处理器中使用发起外部HTTP请求。请勿在异步路由中调用库。
httpx.AsyncClientrequestsError Handling
错误处理
Centralize domain exceptions and keep response shapes stable.
python
from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse
class ApiError(Exception):
def __init__(self, status_code: int, code: str, message: str):
self.status_code = status_code
self.code = code
self.message = message
def register_exception_handlers(app: FastAPI) -> None:
@app.exception_handler(ApiError)
async def api_error_handler(request: Request, exc: ApiError):
return JSONResponse(
status_code=exc.status_code,
content={"error": {"code": exc.code, "message": exc.message}},
)集中管理领域异常,保持响应格式稳定。
python
from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse
class ApiError(Exception):
def __init__(self, status_code: int, code: str, message: str):
self.status_code = status_code
self.code = code
self.message = message
def register_exception_handlers(app: FastAPI) -> None:
@app.exception_handler(ApiError)
async def api_error_handler(request: Request, exc: ApiError):
return JSONResponse(
status_code=exc.status_code,
content={"error": {"code": exc.code, "message": exc.message}},
)OpenAPI Customization
OpenAPI 自定义
Assign the custom OpenAPI callable to ; do not just call the function once.
app.openapipython
from fastapi import FastAPI
from fastapi.openapi.utils import get_openapi
def install_openapi(app: FastAPI) -> None:
def custom_openapi():
if app.openapi_schema:
return app.openapi_schema
app.openapi_schema = get_openapi(
title="Service API",
version="1.0.0",
routes=app.routes,
)
return app.openapi_schema
app.openapi = custom_openapi将自定义OpenAPI可调用对象赋值给;请勿仅调用该函数一次。
app.openapipython
from fastapi import FastAPI
from fastapi.openapi.utils import get_openapi
def install_openapi(app: FastAPI) -> None:
def custom_openapi():
if app.openapi_schema:
return app.openapi_schema
app.openapi_schema = get_openapi(
title="Service API",
version="1.0.0",
routes=app.routes,
)
return app.openapi_schema
app.openapi = custom_openapiTesting
测试
Override the dependency used by , not an internal helper that route handlers never reference.
Dependspython
import pytest
from httpx import ASGITransport, AsyncClient
from sqlalchemy.ext.asyncio import AsyncSession
from app.dependencies import get_db
from app.main import create_app
@pytest.fixture
async def client(test_session: AsyncSession):
app = create_app()
async def override_get_db():
yield test_session
app.dependency_overrides[get_db] = override_get_db
async with AsyncClient(
transport=ASGITransport(app=app),
base_url="http://test",
) as test_client:
yield test_client
app.dependency_overrides.clear()覆盖使用的依赖项,而非路由处理器从未引用的内部辅助函数。
Dependspython
import pytest
from httpx import ASGITransport, AsyncClient
from sqlalchemy.ext.asyncio import AsyncSession
from app.dependencies import get_db
from app.main import create_app
@pytest.fixture
async def client(test_session: AsyncSession):
app = create_app()
async def override_get_db():
yield test_session
app.dependency_overrides[get_db] = override_get_db
async with AsyncClient(
transport=ASGITransport(app=app),
base_url="http://test",
) as test_client:
yield test_client
app.dependency_overrides.clear()Security Checklist
安全检查清单
- Hash passwords with ,
argon2-cffi, or a current passlib-compatible hasher.bcrypt - Validate JWT issuer, audience, expiry, and signing algorithm.
- Keep CORS origins environment-specific.
- Put rate limits on auth and write-heavy endpoints.
- Use Pydantic models for all request bodies.
- Use ORM parameter binding or SQLAlchemy Core expressions; never build SQL with f-strings.
- Redact tokens, authorization headers, cookies, and passwords from logs.
- Run dependency audit tooling in CI.
- 使用、
argon2-cffi或兼容passlib的当前哈希算法对密码进行哈希处理。bcrypt - 验证JWT的签发者、受众、过期时间和签名算法。
- 保持CORS源与环境相关。
- 对认证和写密集型端点设置速率限制。
- 对所有请求体使用Pydantic模型。
- 使用ORM参数绑定或SQLAlchemy Core表达式;绝不要使用f-string构建SQL语句。
- 在日志中屏蔽令牌、授权头、Cookie和密码。
- 在CI中运行依赖项审计工具。
Performance Checklist
性能检查清单
- Configure database connection pooling explicitly.
- Add pagination to list endpoints.
- Watch for N+1 queries and use eager loading intentionally.
- Use async HTTP/database clients in async paths.
- Add compression only after checking payload size and CPU tradeoffs.
- Cache stable expensive reads behind explicit invalidation.
- 显式配置数据库连接池。
- 为列表端点添加分页功能。
- 注意N+1查询问题,有意使用预加载。
- 在异步路径中使用异步HTTP/数据库客户端。
- 仅在检查有效负载大小和CPU权衡后添加压缩。
- 在显式失效策略后缓存稳定的高开销读取操作。
Examples
示例
Use these examples as patterns, not as project-wide templates:
- Application factory: configure middleware and routers once in .
create_app - Schema split: ,
UserCreate, andUserUpdatehave different responsibilities.UserResponse - Dependency override: tests override directly.
get_db - OpenAPI customization: assign .
app.openapi = custom_openapi
将这些示例作为模式使用,而非全项目模板:
- 应用工厂:在中一次性配置中间件和路由。
create_app - Schema拆分:、
UserCreate和UserUpdate具有不同职责。UserResponse - 依赖项覆盖:测试直接覆盖。
get_db - OpenAPI自定义:赋值。
app.openapi = custom_openapi
See Also
另请参阅
- Agent:
fastapi-reviewer - Command:
/fastapi-review - Skill:
python-patterns - Skill:
python-testing - Skill:
api-design
- Agent:
fastapi-reviewer - Command:
/fastapi-review - Skill:
python-patterns - Skill:
python-testing - Skill:
api-design