首页 / FastAPI 入门教程 / 错误处理

FastAPI 入门教程

错误处理

本教程共 50 篇 · 第 16 篇 · 更新于 2026-08-12 · 约 8 分钟阅读

FastAPIFastAPI 入门教程HTTPException异常处理RequestValidationError统一错误格式

本节目标:学会用 HTTPException 返回标准错误,并用自定义异常处理器接管业务异常与数据校验错误,让全站错误格式统一。

接口不可能永远顺利。客户端可能查一个不存在的订单,可能没登录就访问受限资源,也可能发来格式不对的数据。这些情况下,你不能返回一堆正常数据,而应该返回一个清晰的错误响应,让调用方知道出了什么问题。

16-1 用 HTTPException 抛出错误

FastAPI 提供了 HTTPException,专门用来返回 HTTP 错误。它本质上就是一个普通的 Python 异常,只不过额外带了状态码和错误详情。

关键点:它是异常,用 raise,不是 return

from fastapi import FastAPI, HTTPException

app = FastAPI()

items = {"foo": "Foo 摔跤手"}


@app.get("/items/{item_id}")
async def read_item(item_id: str) -> dict:
    if item_id not in items:
        raise HTTPException(status_code=404, detail="找不到这个商品")
    return {"item": items[item_id]}

请求 /items/foo,返回 200{"item": "Foo 摔跤手"}。请求 /items/bar(不存在),则抛出 404,返回:

{
    "detail": "找不到这个商品"
}

raise 而不是 return,有一个重要好处:它立刻中断当前请求。哪怕你在某个工具函数里 raise HTTPException,外层的路径操作函数也会马上停下,不会继续执行后面的代码。这在依赖注入和安全校验里非常有用。

Tip

detail 不限于字符串,也可以传 dictlist 等任何能转成 JSON 的值,FastAPI 会自动序列化。

16-2 给错误加自定义响应头

某些安全场景(比如限流、OAuth)需要在错误响应里带一个头。直接在 HTTPException 里传 headers 即可:

from fastapi import FastAPI, HTTPException

app = FastAPI()

items = {"foo": "Foo 摔跤手"}


@app.get("/items-header/{item_id}")
async def read_item_header(item_id: str) -> dict:
    if item_id not in items:
        raise HTTPException(
            status_code=404,
            detail="找不到这个商品",
            headers={"X-Error": "商品 ID 无效"},
        )
    return {"item": items[item_id]}

平时你很少需要它,但知道有这个能力,遇到特殊需求就不慌。

16-3 自定义业务异常处理器

除了 FastAPI 自带的错误,你自己的业务也可能定义专属异常。比如你有个 UnicornException,希望在全局统一处理它。

@app.exception_handler(异常类) 装饰一个函数,这个函数接收 Request 和异常实例,返回你想给客户端的响应。

from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse

app = FastAPI()


class UnicornException(Exception):
    def __init__(self, name: str) -> None:
        self.name = name


@app.exception_handler(UnicornException)
async def unicorn_exception_handler(request: Request, exc: UnicornException) -> JSONResponse:
    return JSONResponse(
        status_code=418,
        content={"message": f"哎呀,{exc.name} 闯祸了,彩虹飞走了……"},
    )


@app.get("/unicorns/{name}")
async def read_unicorn(name: str) -> dict:
    if name == "yolo":
        raise UnicornException(name=name)
    return {"unicorn_name": name}

请求 /unicorns/yolo,会触发 UnicornException,随即被 unicorn_exception_handler 接管,返回 418 和一段友好提示。

Note

RequestJSONResponse 也可以从 Starlette 直接导入(starlette.requestsstarlette.responses)。FastAPI 只是把它们重新导出,方便你一个包搞定。

16-4 覆盖默认请求校验错误处理

当客户端发来的数据不符合模型要求时,FastAPI 内部会抛出 RequestValidationError,并返回默认的 JSON 错误。如果你想换成自己的格式(比如纯文本、或统一的业务结构),就自己注册一个处理器。

from fastapi import FastAPI, HTTPException
from fastapi.exceptions import RequestValidationError
from fastapi.responses import PlainTextResponse
from starlette.exceptions import HTTPException as StarletteHTTPException

app = FastAPI()


@app.exception_handler(StarletteHTTPException)
async def http_exception_handler(request, exc) -> PlainTextResponse:
    return PlainTextResponse(str(exc.detail), status_code=exc.status_code)


@app.exception_handler(RequestValidationError)
async def validation_exception_handler(request, exc) -> PlainTextResponse:
    return PlainTextResponse(str(exc), status_code=400)


@app.get("/items/{item_id}")
async def read_item(item_id: int) -> dict:
    if item_id == 3:
        raise HTTPException(status_code=418, detail="我就不喜欢 3。")
    return {"item_id": item_id}

现在访问 /items/foo(传了非整数),你拿到的就不再是默认 JSON,而是一段纯文本校验说明。

RequestValidationError 里还带着出错的请求体,开发时可以用来打日志、调试:

from fastapi import FastAPI, Request, status
from fastapi.encoders import jsonable_encoder
from fastapi.exceptions import RequestValidationError
from fastapi.responses import JSONResponse
from pydantic import BaseModel

app = FastAPI()


@app.exception_handler(RequestValidationError)
async def validation_exception_handler(
    request: Request, exc: RequestValidationError
) -> JSONResponse:
    return JSONResponse(
        status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
        content=jsonable_encoder({"detail": exc.errors(), "body": exc.body}),
    )


class Item(BaseModel):
    title: str
    size: int


@app.post("/items/")
async def create_item(item: Item) -> Item:
    return item

16-5 统一全站错误格式

真实项目里,最推荐的做法是统一错误返回格式:无论哪种错误,客户端都收到同一种结构,比如都带 codemessage 字段。下面用 RequestValidationErrorHTTPException 做示范。

from fastapi import FastAPI, HTTPException, Request
from fastapi.exceptions import RequestValidationError
from fastapi.responses import JSONResponse
from starlette.exceptions import HTTPException as StarletteHTTPException

app = FastAPI()

ERROR_SHAPE = {"code": 0, "message": "", "data": None}


@app.exception_handler(StarletteHTTPException)
async def http_exception_handler(request: Request, exc: StarletteHTTPException) -> JSONResponse:
    return JSONResponse(
        status_code=exc.status_code,
        content={"code": exc.status_code, "message": str(exc.detail), "data": None},
    )


@app.exception_handler(RequestValidationError)
async def validation_exception_handler(request: Request, exc: RequestValidationError) -> JSONResponse:
    return JSONResponse(
        status_code=422,
        content={"code": 422, "message": "请求数据校验失败", "data": exc.errors()},
    )


@app.get("/users/{user_id}")
async def read_user(user_id: int) -> dict:
    if user_id == 0:
        raise HTTPException(status_code=404, detail="用户不存在")
    return {"user_id": user_id}

这样,无论是 404 还是校验失败,前端都收到 {code, message, data} 的统一结构,处理起来特别省事。

Warning

RequestValidationError 里包含出错的文件名和行号信息,方便你打日志。但如果你直接把它转成字符串返回给客户端,可能泄露系统内部细节。所以上面只提取了 exc.errors() 这类必要信息,敏感内容不要外泄。

16-6 FastAPI 与 Starlette 的 HTTPException

有个容易踩坑的细节:FastAPI 自己的 HTTPException 继承自 Starlette 的 HTTPException,区别仅在于 FastAPI 版允许 detail 传任意 JSON 值。

因此,业务代码里你照常 raise fastapi.HTTPException(...) 就行。但注册异常处理器时,应该注册到 Starlette 的 HTTPException

from starlette.exceptions import HTTPException as StarletteHTTPException

@app.exception_handler(StarletteHTTPException)
async def http_exception_handler(request, exc):
    ...

这样,万一 Starlette 内部或某个插件抛出了它自己的 HTTPException,你的处理器也能接住。

Tip

想先自己处理、再复用 FastAPI 默认处理器?从 fastapi.exception_handlers 导入 http_exception_handlerrequest_validation_exception_handler,处理完 return await http_exception_handler(request, exc) 即可。

16-7 综合示例:一个统一错误格式的迷你应用

把自定义业务异常、HTTP 错误、校验错误三种情况放一起,配合同一返回结构,端到端看一遍:

from fastapi import FastAPI, HTTPException, Request
from fastapi.exceptions import RequestValidationError
from fastapi.responses import JSONResponse
from starlette.exceptions import HTTPException as StarletteHTTPException

app = FastAPI()


class BizError(Exception):
    def __init__(self, msg: str) -> None:
        self.msg = msg


@app.exception_handler(BizError)
async def biz_handler(request: Request, exc: BizError) -> JSONResponse:
    return JSONResponse(
        status_code=400,
        content={"code": 400, "message": exc.msg, "data": None},
    )


@app.exception_handler(StarletteHTTPException)
async def http_handler(request: Request, exc: StarletteHTTPException) -> JSONResponse:
    return JSONResponse(
        status_code=exc.status_code,
        content={"code": exc.status_code, "message": str(exc.detail), "data": None},
    )


@app.exception_handler(RequestValidationError)
async def val_handler(request: Request, exc: RequestValidationError) -> JSONResponse:
    return JSONResponse(
        status_code=422,
        content={"code": 422, "message": "参数校验失败", "data": exc.errors()},
    )


@app.get("/orders/{order_id}")
async def read_order(order_id: int) -> dict:
    if order_id == 0:
        raise BizError("订单号不能为 0")
    if order_id == 999:
        raise HTTPException(status_code=404, detail="订单不存在")
    return {"order_id": order_id}

访问 /orders/0 触发业务异常,返回 400;访问 /orders/999 触发 404;传 /orders/abc 触发校验错误返回 422。三种错误都是 {code, message, data} 结构,前端一套逻辑全接住。

Tip

小项目直接 raise HTTPException 就够了;当异常种类变多、或想统一格式时,再上自定义处理器。别一上来就过度封装。

还有一点容易混淆:HTTPException 是你主动抛出的、关于”这次请求”的错误;而请求进来时参数校验失败触发的 RequestValidationError,是 FastAPI 在调用你的函数之前就拦下来的。两者的默认处理器不同,所以想完全自定义时,要分别注册两个处理器,正如上面的例子所示。理清这条边界,错误处理的代码就不会写乱。

16-8 小结

错误处理是接口健壮性的底线:

  • raise HTTPException(status_code, detail) 返回标准 HTTP 错误,记得是 raise 不是 return
  • 可用 headers 给错误加自定义响应头。
  • @app.exception_handler(异常类) 接管自定义异常和校验错误。
  • 覆盖 RequestValidationError 处理器,可换成纯文本或自己的格式。
  • 推荐统一全站错误结构(如 {code, message, data}),提升前端体验。
  • 注册处理器时用 Starlette 的 HTTPException,业务代码里才用 FastAPI 的。