2030 字
10 分钟
FastAPI 完全指南 2026:从零构建高性能异步 Python API 到 Docker 生产部署
FastAPI 是 2019 年发布、目前最受欢迎的 Python Web API 框架之一,在 GitHub Star 数上已超越 Flask 和 Django REST Framework。它的核心竞争力:原生异步支持 + 自动 OpenAPI 文档 + Pydantic 数据验证 三位一体,让 Python API 开发效率大幅提升。
本文从零开始,完整覆盖 FastAPI 核心功能到生产部署的全链路。
一、安装与项目结构
1.1 安装
# 推荐使用 uv 管理项目uv init my-api --libcd my-api
# 添加依赖uv add fastapi uvicorn[standard] pydantic-settings
# 可选:数据库相关uv add sqlalchemy[asyncio] asyncpg alembic
# 可选:认证相关uv add python-jose[cryptography] passlib[bcrypt]
# 开发依赖uv add --dev pytest pytest-asyncio httpx ruff mypy1.2 推荐项目结构
my-api/├── pyproject.toml├── alembic/ # 数据库迁移│ └── versions/├── src/│ └── my_api/│ ├── __init__.py│ ├── main.py # FastAPI 应用入口│ ├── config.py # 配置管理(pydantic-settings)│ ├── database.py # 数据库连接│ ├── models/ # SQLAlchemy ORM 模型│ │ └── user.py│ ├── schemas/ # Pydantic 请求/响应模型│ │ └── user.py│ ├── routers/ # 路由(按资源拆分)│ │ ├── users.py│ │ └── auth.py│ ├── services/ # 业务逻辑层│ │ └── user.py│ └── deps.py # 依赖注入(认证、数据库 Session 等)└── tests/ └── test_users.py二、核心功能速览
2.1 最小应用
from fastapi import FastAPI
app = FastAPI( title="My API", description="API 文档说明", version="1.0.0", docs_url="/docs", # Swagger UI 路径 redoc_url="/redoc", # ReDoc 路径)
@app.get("/")async def root(): return {"message": "Hello, FastAPI!"}
@app.get("/health")async def health(): return {"status": "ok"}# 启动开发服务器(热重载)uvicorn src.my_api.main:app --reload --host 0.0.0.0 --port 8000
# 或用 uvuv run uvicorn src.my_api.main:app --reload
# 访问自动生成的 Swagger UIopen http://localhost:8000/docs2.2 路径参数 / 查询参数 / 请求体
from fastapi import FastAPI, Path, Query, Bodyfrom pydantic import BaseModel, Fieldfrom typing import Annotated
app = FastAPI()
# ── 路径参数 ──────────────────────────────────────────────────@app.get("/users/{user_id}")async def get_user( user_id: Annotated[int, Path(title="用户ID", ge=1)] # ge=1:必须 >= 1): return {"user_id": user_id}
# ── 查询参数 ──────────────────────────────────────────────────@app.get("/users")async def list_users( page: Annotated[int, Query(ge=1, description="页码")] = 1, per_page: Annotated[int, Query(ge=1, le=100)] = 20, search: str | None = None, # 可选查询参数 active: bool = True,): return { "page": page, "per_page": per_page, "search": search, "active": active, }
# ── 请求体(Pydantic 模型)──────────────────────────────────────class UserCreate(BaseModel): username: str = Field(..., min_length=3, max_length=50, pattern=r'^[a-zA-Z0-9_]+$') email: str = Field(..., description="用户邮箱") age: int = Field(..., ge=0, le=150) bio: str | None = Field(default=None, max_length=500)
class UserResponse(BaseModel): id: int username: str email: str
model_config = {"from_attributes": True} # 允许从 ORM 对象创建
@app.post("/users", response_model=UserResponse, status_code=201)async def create_user(user: UserCreate): # Pydantic 自动验证 user 数据,验证失败自动返回 422 return {"id": 1, **user.model_dump()}2.3 配置管理(pydantic-settings)
from pydantic_settings import BaseSettings, SettingsConfigDict
class Settings(BaseSettings): # 优先从环境变量读取,其次从 .env 文件 model_config = SettingsConfigDict( env_file=".env", env_file_encoding="utf-8", case_sensitive=False, )
# 数据库 database_url: str = "postgresql+asyncpg://user:pass@localhost/mydb"
# JWT 认证 secret_key: str = "change-this-in-production" algorithm: str = "HS256" access_token_expire_minutes: int = 30
# 应用 debug: bool = False app_name: str = "My API" api_prefix: str = "/api/v1"
# 全局单例settings = Settings()三、依赖注入(Depends)
FastAPI 的依赖注入系统是其最强大的特性之一:
from fastapi import Depends, HTTPException, statusfrom fastapi.security import OAuth2PasswordBearerfrom jose import JWTError, jwtfrom .config import settingsfrom .database import AsyncSessionLocal
# ── 数据库 Session 依赖 ───────────────────────────────────────async def get_db(): async with AsyncSessionLocal() as session: try: yield session await session.commit() except Exception: await session.rollback() raise
# ── JWT 认证依赖 ──────────────────────────────────────────────oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/v1/auth/login")
async def get_current_user(token: str = Depends(oauth2_scheme)): credentials_exception = HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="无效的认证凭证", headers={"WWW-Authenticate": "Bearer"}, ) try: payload = jwt.decode(token, settings.secret_key, algorithms=[settings.algorithm]) user_id: int | None = payload.get("sub") if user_id is None: raise credentials_exception except JWTError: raise credentials_exception return user_id
# ── 在路由中使用依赖 ──────────────────────────────────────────from fastapi import APIRouterfrom sqlalchemy.ext.asyncio import AsyncSession
router = APIRouter()
@router.get("/me")async def get_me( current_user_id: int = Depends(get_current_user), db: AsyncSession = Depends(get_db),): # current_user_id 和 db 自动注入 return {"user_id": current_user_id}四、数据库集成(SQLAlchemy 2.0 异步)
from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine, async_sessionmakerfrom sqlalchemy.orm import DeclarativeBasefrom .config import settings
engine = create_async_engine( settings.database_url, echo=settings.debug, # debug=True 时打印 SQL 语句 pool_size=10, max_overflow=20,)
AsyncSessionLocal = async_sessionmaker( engine, class_=AsyncSession, expire_on_commit=False,)
class Base(DeclarativeBase): passfrom datetime import datetimefrom sqlalchemy import String, Boolean, DateTime, funcfrom sqlalchemy.orm import Mapped, mapped_columnfrom ..database import Base
class User(Base): __tablename__ = "users"
id: Mapped[int] = mapped_column(primary_key=True, index=True) username: Mapped[str] = mapped_column(String(50), unique=True, index=True) email: Mapped[str] = mapped_column(String(255), unique=True, index=True) hashed_password: Mapped[str] = mapped_column(String(255)) is_active: Mapped[bool] = mapped_column(Boolean, default=True) created_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.now()) updated_at: Mapped[datetime] = mapped_column( DateTime, server_default=func.now(), onupdate=func.now() )from sqlalchemy import selectfrom sqlalchemy.ext.asyncio import AsyncSessionfrom ..models.user import Userfrom ..schemas.user import UserCreatefrom passlib.context import CryptContext
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
class UserService: def __init__(self, db: AsyncSession): self.db = db
async def get_by_id(self, user_id: int) -> User | None: result = await self.db.execute(select(User).where(User.id == user_id)) return result.scalar_one_or_none()
async def get_by_email(self, email: str) -> User | None: result = await self.db.execute(select(User).where(User.email == email)) return result.scalar_one_or_none()
async def create(self, data: UserCreate) -> User: user = User( username=data.username, email=data.email, hashed_password=pwd_context.hash(data.password), ) self.db.add(user) await self.db.flush() # 获取自动生成的 ID,不提交事务 await self.db.refresh(user) return user
def verify_password(self, plain: str, hashed: str) -> bool: return pwd_context.verify(plain, hashed)五、JWT 认证完整实现
from datetime import datetime, timedelta, timezonefrom fastapi import APIRouter, Depends, HTTPException, statusfrom fastapi.security import OAuth2PasswordRequestFormfrom jose import jwtfrom sqlalchemy.ext.asyncio import AsyncSessionfrom ..config import settingsfrom ..deps import get_dbfrom ..services.user import UserService
router = APIRouter(prefix="/auth", tags=["认证"])
def create_access_token(data: dict, expires_delta: timedelta | None = None) -> str: to_encode = data.copy() expire = datetime.now(timezone.utc) + (expires_delta or timedelta(minutes=30)) to_encode.update({"exp": expire}) return jwt.encode(to_encode, settings.secret_key, algorithm=settings.algorithm)
@router.post("/login")async def login( form_data: OAuth2PasswordRequestForm = Depends(), db: AsyncSession = Depends(get_db),): service = UserService(db) user = await service.get_by_email(form_data.username)
if not user or not service.verify_password(form_data.password, user.hashed_password): raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="邮箱或密码错误", )
access_token = create_access_token( data={"sub": str(user.id)}, expires_delta=timedelta(minutes=settings.access_token_expire_minutes), ) return { "access_token": access_token, "token_type": "bearer", }六、中间件与错误处理
import timeimport uuidfrom fastapi import FastAPI, Requestfrom fastapi.middleware.cors import CORSMiddlewarefrom fastapi.responses import JSONResponsefrom .config import settingsfrom .routers import users, auth
app = FastAPI(title=settings.app_name)
# ── CORS 跨域中间件 ───────────────────────────────────────────app.add_middleware( CORSMiddleware, allow_origins=["https://your-frontend.com"], # 生产环境写具体域名 allow_credentials=True, allow_methods=["*"], allow_headers=["*"],)
# ── 请求 ID 和耗时日志中间件 ──────────────────────────────────@app.middleware("http")async def add_request_context(request: Request, call_next): request_id = str(uuid.uuid4())[:8] start = time.perf_counter()
response = await call_next(request)
duration = (time.perf_counter() - start) * 1000 response.headers["X-Request-ID"] = request_id response.headers["X-Process-Time"] = f"{duration:.2f}ms" return response
# ── 全局异常处理 ──────────────────────────────────────────────@app.exception_handler(404)async def not_found_handler(request: Request, exc): return JSONResponse(status_code=404, content={"error": "资源不存在"})
@app.exception_handler(500)async def server_error_handler(request: Request, exc): return JSONResponse(status_code=500, content={"error": "服务器内部错误"})
# ── 注册路由 ──────────────────────────────────────────────────app.include_router(auth.router, prefix=settings.api_prefix)app.include_router(users.router, prefix=settings.api_prefix)七、后台任务与 WebSocket
from fastapi import BackgroundTasks
# ── 后台任务(轻量异步任务,如发送邮件)────────────────────────def send_welcome_email(email: str): """在后台线程中执行(不阻塞响应)""" import time time.sleep(2) # 模拟发邮件的延迟 print(f"邮件已发送到 {email}")
@app.post("/users", status_code=201)async def create_user(user: UserCreate, background_tasks: BackgroundTasks): # 创建用户... background_tasks.add_task(send_welcome_email, user.email) return {"message": "用户创建成功,欢迎邮件发送中"}
# ── WebSocket ─────────────────────────────────────────────────from fastapi import WebSocketfrom fastapi.websockets import WebSocketDisconnect
@app.websocket("/ws/{client_id}")async def websocket_endpoint(websocket: WebSocket, client_id: str): await websocket.accept() try: while True: data = await websocket.receive_text() await websocket.send_text(f"[{client_id}] 收到: {data}") except WebSocketDisconnect: print(f"客户端 {client_id} 已断开")八、测试(pytest + httpx)
import pytestimport pytest_asynciofrom httpx import AsyncClient, ASGITransportfrom sqlalchemy.ext.asyncio import create_async_engine, async_sessionmaker, AsyncSessionfrom src.my_api.main import appfrom src.my_api.database import Basefrom src.my_api.deps import get_db
# 使用内存 SQLite 作为测试数据库TEST_DATABASE_URL = "sqlite+aiosqlite:///:memory:"
@pytest_asyncio.fixture(scope="session")async def db_engine(): engine = create_async_engine(TEST_DATABASE_URL, echo=False) async with engine.begin() as conn: await conn.run_sync(Base.metadata.create_all) yield engine await engine.dispose()
@pytest_asyncio.fixtureasync def db_session(db_engine): session_factory = async_sessionmaker(db_engine, class_=AsyncSession) async with session_factory() as session: yield session await session.rollback() # 每个测试后回滚,保持隔离
@pytest_asyncio.fixtureasync def client(db_session): # 覆盖依赖注入,使用测试数据库 app.dependency_overrides[get_db] = lambda: db_session
async with AsyncClient( transport=ASGITransport(app=app), base_url="http://test", ) as c: yield c
app.dependency_overrides.clear()import pytest
@pytest.mark.asyncioasync def test_create_user(client): response = await client.post("/api/v1/users", json={ "username": "testuser", "email": "test@example.com", "password": "SecurePass123!", "age": 25, }) assert response.status_code == 201 data = response.json() assert data["username"] == "testuser" assert "password" not in data # 确保密码不在响应中
@pytest.mark.asyncioasync def test_login(client): # 先创建用户 await client.post("/api/v1/users", json={ "username": "loginuser", "email": "login@example.com", "password": "TestPass123!", "age": 30, })
# 测试登录 response = await client.post( "/api/v1/auth/login", data={"username": "login@example.com", "password": "TestPass123!"}, ) assert response.status_code == 200 assert "access_token" in response.json()
@pytest.mark.asyncioasync def test_get_me_unauthorized(client): response = await client.get("/api/v1/me") assert response.status_code == 401# 运行测试uv run pytest tests/ -v --asyncio-mode=auto九、Docker 生产部署
9.1 Dockerfile(多阶段构建)
FROM python:3.12-slim AS builderWORKDIR /appRUN pip install uvCOPY pyproject.toml uv.lock ./RUN uv venv /opt/venv && uv sync --frozen --no-dev --python /opt/venv/bin/pythonCOPY src/ ./src/
FROM python:3.12-slim AS finalRUN useradd --no-create-home --shell /bin/false appuserWORKDIR /appCOPY --from=builder /opt/venv /opt/venvCOPY --from=builder /app/src ./srcENV PATH="/opt/venv/bin:$PATH"USER appuserEXPOSE 8000CMD ["uvicorn", "src.my_api.main:app", "--host", "0.0.0.0", "--port", "8000", "--workers", "4"]9.2 compose.yml(生产配置)
services: api: build: . restart: unless-stopped environment: DATABASE_URL: postgresql+asyncpg://appuser:${DB_PASSWORD}@db:5432/myapp SECRET_KEY: ${SECRET_KEY} DEBUG: "false" depends_on: db: condition: service_healthy networks: [backend, frontend]
db: image: postgres:16-alpine restart: unless-stopped environment: POSTGRES_DB: myapp POSTGRES_USER: appuser POSTGRES_PASSWORD: ${DB_PASSWORD} volumes: - db-data:/var/lib/postgresql/data networks: [backend] healthcheck: test: ["CMD-SHELL", "pg_isready -U appuser -d myapp"] interval: 10s timeout: 5s retries: 5
nginx: image: nginx:1.27-alpine restart: unless-stopped ports: ["80:80", "443:443"] volumes: - ./nginx.conf:/etc/nginx/conf.d/default.conf:ro depends_on: [api] networks: [frontend]
volumes: db-data:networks: frontend: backend: internal: true相关文章:
- Python 项目工程化实战(一):pyproject.toml + uv + Ruff
- Python 类型注解完全指南:从基础标注到 Pydantic 数据验证
- Python 数据处理实战:Pandas 核心 API + 大文件分块读取
- Docker 完全指南 2026:Compose 多服务编排与生产部署
- Linux 服务器初始化安全配置:买完 VPS 必做的 12 件事
本文基于 FastAPI 0.111+ / Pydantic v2 / SQLAlchemy 2.0 编写,接口以官方文档为准:fastapi.tiangolo.com
FastAPI 完全指南 2026:从零构建高性能异步 Python API 到 Docker 生产部署
https://971918.xyz/posts/python-guide/python-fastapi-complete-guide/