码到成功
Fastapi——Dependency、SwagUI、自定义状态码

为什么这三个点值得放一起讲
很多项目刚开头时,接口都很轻,怎么写都像能跑。可一旦路由多起来,问题就开始冒出来:
- token 校验每个接口写一遍
- 数据库会话创建和释放到处复制
- 文档页默认能用,但团队调试时总觉得差点意思
- 明明是“创建成功”,结果前端永远只看到
200
FastAPI 这套能力正好能把这些地方收一下。
说得更直接一点:
Dependency解决“重复逻辑到处飘”Swagger UI解决“文档页只是存在,但不好用”- 状态码设计解决“接口返回值会说话,但还不够准”
Dependency:别再把公共逻辑塞进每个路由里
FastAPI 里最常用也最顺手的一个特性,就是 Depends()。
它的味道不是“魔法”,而更像“把公共逻辑注册成可组合组件”。路由只关心自己要什么,不关心这份东西是怎么准备出来的。
最基础的写法
先看一个轻量例子:
from fastapi import Depends, FastAPI
app = FastAPI()
def common_query(q: str | None = None, page: int = 1, size: int = 20):
return {"q": q, "page": page, "size": size}
@app.get("/articles")
def list_articles(params: dict = Depends(common_query)):
return {
"message": "query accepted",
"params": params,
}
这个例子看着不复杂,但已经把一个重要思路定下来了:
- 公共查询参数可以抽成 dependency
- 路由函数不用再自己解析一遍
- 后面要改默认分页,只改一处就行
这比每个接口自己收 q/page/size 要干净得多。
更常见的用法:数据库会话
在接口项目里,数据库会话是很典型的 dependency 场景。
from collections.abc import Generator
from fastapi import Depends, FastAPI
from sqlalchemy.orm import Session
app = FastAPI()
def get_db() -> Generator[Session, None, None]:
db = SessionLocal()
try:
yield db
finally:
db.close()
@app.get("/users/{user_id}")
def get_user(user_id: int, db: Session = Depends(get_db)):
user = db.get(User, user_id)
return {"user_id": user_id, "user": user}
这里最舒服的一点是:yield 把“拿资源”和“释放资源”一起包好了。
路由函数只拿到 db,不用再关心:
- 什么时候创建会话
- 什么时候关闭会话
- 如果中间出异常该怎么收尾
这就是 dependency 很值钱的地方。它不只是少写几行,而是把资源生命周期也一起整理了。
权限校验也很适合 dependency
再往前走一步,权限校验也特别适合塞进 Depends()。
from fastapi import Depends, FastAPI, Header, HTTPException, status
app = FastAPI()
def get_current_user(x_token: str = Header(...)):
if x_token != "debug-token":
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="invalid token",
)
return {"username": "codex-admin", "role": "editor"}
@app.get("/profile")
def read_profile(current_user: dict = Depends(get_current_user)):
return {"current_user": current_user}
这个结构的好处很明显:
- 校验逻辑集中
- 路由签名很直观
- 哪些接口依赖登录,一眼能看出来
后面如果你要把 token 校验换成 JWT,或者继续补角色判断,也不用把路由函数翻一遍。
dependency 还能一层套一层
这点很容易被低估。FastAPI 的 dependency 不是平铺的,它可以继续依赖别的 dependency。
比如:
get_current_user()依赖parse_token()get_admin_user()再依赖get_current_user()
这样权限链就能写得很自然,而不是所有东西挤进一个巨型函数里。
Swagger UI:别让文档页只是“默认开着”
很多人第一次用 FastAPI,都会被 /docs 惊一下:这文档页也太省心了。
确实,开箱即用是它的一大优点。但如果只是停在默认页面,其实还有不少可玩的空间。
最基础的配置
先把文档地址和标题收一下:
from fastapi import FastAPI
app = FastAPI(
title="Content Center API",
description="后台内容服务接口",
version="0.1.0",
docs_url="/swagger",
redoc_url="/redoc",
openapi_url="/openapi.json",
)
这段配置做了几件很实用的小事:
- 给项目一个像样的标题
- 文档地址不一定非得叫
/docs - OpenAPI 描述文件也有了固定出口
对团队协作来说,这种小收口很重要。接口不只是“能调”,还得“容易说明白”。
调 Swagger UI 的展示参数
FastAPI 还支持直接给 Swagger UI 丢参数。
from fastapi import FastAPI
app = FastAPI(
swagger_ui_parameters={
"defaultModelsExpandDepth": -1,
"docExpansion": "list",
"displayRequestDuration": True,
"filter": True,
"syntaxHighlight.theme": "monokai",
}
)
这些配置很适合把文档页收得更利落一点:
defaultModelsExpandDepth=-1可以把右侧 schema 面板先收起来docExpansion="list"让接口折叠得更整齐displayRequestDuration=True调试时能顺手看看耗时filter=True路由一多以后特别好用
如果你的接口列表很长,这几个参数能让 Swagger UI 的可读性一下子上来。
想更定制一点,可以自己接文档页
有时候默认的 Swagger UI 还不够,你想替换页面标题、引入自定义 JS 或 CSS,这时候可以直接用 get_swagger_ui_html()。
from fastapi import FastAPI
from fastapi.openapi.docs import get_swagger_ui_html
from fastapi.responses import HTMLResponse
app = FastAPI(docs_url=None)
@app.get("/swagger", include_in_schema=False)
def custom_swagger() -> HTMLResponse:
return get_swagger_ui_html(
openapi_url=app.openapi_url,
title="Content Center Swagger UI",
swagger_js_url="https://cdn.jsdelivr.net/npm/swagger-ui-dist/swagger-ui-bundle.js",
swagger_css_url="https://cdn.jsdelivr.net/npm/swagger-ui-dist/swagger-ui.css",
)
这种写法在下面这些场景很有用:
- 想把默认文档页入口隐藏掉
- 想统一公司内部 API 门户风格
- 想给文档页加额外说明或导航
它不是必须,但一旦你的项目不再是“只有自己调”,就会很顺手。
自定义状态码:别把所有成功都塞进 200
如果一个接口创建了新资源,返回 201 会比 200 更清楚。
如果一个接口删除成功却没内容,204 往往比“返回一个空 JSON”更干净。
状态码这件事,说到底是在帮接口表达语义。它不是装饰,而是协议的一部分。
直接在路由上声明状态码
最简单的方式,就是在装饰器里写:
from fastapi import FastAPI, status
app = FastAPI()
@app.post("/posts", status_code=status.HTTP_201_CREATED)
def create_post():
return {"message": "post created"}
这种写法的优点很明显:
- 文档里会直接显示这个状态码
- 前端读接口定义时更清楚
- 代码意图更直
用 JSONResponse 精准控制返回
如果你想按业务分支返回不同状态码,可以用 JSONResponse。
from fastapi import FastAPI
from fastapi.responses import JSONResponse
app = FastAPI()
@app.put("/posts/{post_id}")
def upsert_post(post_id: int, payload: dict):
existed = post_id % 2 == 0
if existed:
return JSONResponse(
status_code=200,
content={"message": "post updated", "post_id": post_id},
)
return JSONResponse(
status_code=201,
content={"message": "post created", "post_id": post_id},
)
这个模式在“更新或创建”“命中缓存或重新生成”“同步成功但结果不同”这些场景里很好用。
业务异常别只会抛 500
很多项目一开始没想太多,接口一出错就让异常自然冒泡,最后统一变成 500。这当然能工作,但信息含量太低。
FastAPI 的 HTTPException 很适合把这件事说清楚。
from fastapi import FastAPI, HTTPException, status
app = FastAPI()
@app.get("/orders/{order_id}")
def get_order(order_id: int):
if order_id <= 0:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="order_id must be positive",
)
if order_id == 404:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="order not found",
)
return {"order_id": order_id}
这类写法的好处在于:
- 客户端知道自己是参数错了,还是资源不存在
- Swagger UI 里能直接看到异常语义
- 日后做错误码映射时也更自然
再往前走一步:统一异常格式
如果你不想每个异常都长得不一样,可以自己加异常处理器。
from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse
app = FastAPI()
class BizError(Exception):
def __init__(self, code: str, message: str, status_code: int = 400):
self.code = code
self.message = message
self.status_code = status_code
@app.exception_handler(BizError)
async def biz_error_handler(request: Request, exc: BizError):
return JSONResponse(
status_code=exc.status_code,
content={
"ok": False,
"error_code": exc.code,
"message": exc.message,
"path": str(request.url.path),
},
)
这样你后面的接口就能保持统一语气:
- 状态码表达 HTTP 层语义
error_code表达业务层语义message给人看
这会比一堆随手拼出来的报错 JSON 干净很多。
把三件事揉进一个小接口
如果把 Dependency、Swagger UI 和状态码放进同一个迷你项目,它大概会长这样:
from fastapi import Depends, FastAPI, Header, HTTPException, status
from fastapi.responses import JSONResponse
app = FastAPI(
title="Article Service",
docs_url="/swagger",
swagger_ui_parameters={
"docExpansion": "list",
"filter": True,
},
)
def get_token(x_token: str = Header(...)):
if x_token != "debug-token":
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="invalid token",
)
return x_token
def get_pagination(page: int = 1, size: int = 10):
return {"page": page, "size": size}
@app.get("/articles")
def list_articles(
token: str = Depends(get_token),
pagination: dict = Depends(get_pagination),
):
return {"token": token, "pagination": pagination, "items": []}
@app.post("/articles")
def create_article(payload: dict, token: str = Depends(get_token)):
return JSONResponse(
status_code=status.HTTP_201_CREATED,
content={
"message": "article created",
"token": token,
"payload": payload,
},
)
这段小代码里其实已经把几件关键事串起来了:
- 认证逻辑抽成 dependency
- 分页参数抽成 dependency
- 文档页地址和展示参数收口
- 创建接口明确返回
201
这种结构很适合做项目的第一版骨架,因为它不是“先跑起来再说”,而是一开始就尽量别散。
用 FastAPI 写接口时,一个很实用的习惯
如果你想让接口项目从一开始就更稳,我会建议你把下面三个动作当成默认习惯:
- 只要某段逻辑会在多个路由出现,就优先考虑 dependency
- 只要接口会给团队或前端调,就别放任 Swagger UI 维持默认样子
- 只要接口有明确业务动作,就给它匹配合适的状态码
这三个习惯单独看都不大,但放在一起,会让项目明显更整洁。
如果你已经在写 FastAPI,这三块值得尽早养成习惯。等路由一多,它们给你的回报会非常直接。