响应模型和错误处理

接口不应该把后端对象原样返回给前端。

比如用户表里可能有 hashed_password,这个字段绝对不能返回。

FastAPI 用 response_model 控制响应结构。

一、最简单的响应模型

from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()


class UserOut(BaseModel):
    id: int
    username: str
    nickname: str | None = None


@app.get("/users/{user_id}", response_model=UserOut)
def get_user(user_id: int):
    return {
        "id": user_id,
        "username": "zhangsan",
        "nickname": "小张",
        "hashed_password": "secret",
    }

实际响应:

{
    "id": 1,
    "username": "zhangsan",
    "nickname": "小张"
}

hashed_password 不在 UserOut 里,所以不会返回给前端。

二、为什么要用响应模型

response_model 有几个重要作用:

作用说明
过滤字段不暴露密码、内部状态等敏感字段
校验返回值后端返回的数据不符合模型时会报错
生成文档/docs 会显示响应结构
统一格式前端看到的数据更稳定

三、列表响应

返回列表时,可以直接写 list[UserOut]

@app.get("/users/", response_model=list[UserOut])
def list_users():
    return [
        {"id": 1, "username": "zhangsan", "hashed_password": "secret"},
        {"id": 2, "username": "lisi", "hashed_password": "secret"},
    ]

实际响应里每个用户都会过滤掉 hashed_password

四、分页响应

实际项目里,列表接口通常返回总数和列表。

from pydantic import BaseModel


class UserPage(BaseModel):
    total: int
    items: list[UserOut]


@app.get("/users/", response_model=UserPage)
def list_users():
    return {
        "total": 2,
        "items": [
            {"id": 1, "username": "zhangsan"},
            {"id": 2, "username": "lisi"},
        ],
    }

响应:

{
    "total": 2,
    "items": [
        {"id": 1, "username": "zhangsan", "nickname": null},
        {"id": 2, "username": "lisi", "nickname": null}
    ]
}

五、设置成功状态码

创建资源常用 201 Created

from fastapi import FastAPI, status

app = FastAPI()


@app.post("/users/", status_code=status.HTTP_201_CREATED)
def create_user():
    return {"id": 1, "username": "zhangsan"}

status.HTTP_201_CREATED 比直接写 201 更可读。

六、HTTPException

当业务上找不到资源、权限不足、参数不合法时,可以抛 HTTPException

from fastapi import FastAPI, HTTPException

app = FastAPI()

fake_users = {
    1: {"id": 1, "username": "zhangsan"},
}


@app.get("/users/{user_id}")
def get_user(user_id: int):
    user = fake_users.get(user_id)
    if user is None:
        raise HTTPException(status_code=404, detail="用户不存在")
    return user

访问 /users/2

{
    "detail": "用户不存在"
}

七、常见状态码

状态码含义常见场景
200OK查询成功、更新成功
201Created创建成功
204No Content删除成功且不返回内容
400Bad Request业务参数不合法
401Unauthorized未登录或 Token 无效
403Forbidden已登录但没有权限
404Not Found资源不存在
409Conflict资源冲突,例如用户名重复
422Unprocessable EntityFastAPI / Pydantic 自动校验失败

八、删除接口返回 204

如果删除成功后不需要返回 JSON,可以用 204

from fastapi import Response, status


@app.delete("/users/{user_id}", status_code=status.HTTP_204_NO_CONTENT)
def delete_user(user_id: int):
    fake_users.pop(user_id, None)
    return Response(status_code=status.HTTP_204_NO_CONTENT)

也可以简单返回 {"detail": "删除成功"},这时用 200 更直观。

九、自定义错误格式

初学阶段优先使用 HTTPException,不用急着自定义全局异常处理。

如果项目需要统一错误格式,可以定义自己的异常和处理器:

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

app = FastAPI()


class BizError(Exception):
    def __init__(self, code: str, message: str):
        self.code = code
        self.message = message


@app.exception_handler(BizError)
async def biz_error_handler(request: Request, exc: BizError):
    return JSONResponse(
        status_code=400,
        content={"code": exc.code, "message": exc.message},
    )


@app.get("/demo")
def demo():
    raise BizError(code="USER_DISABLED", message="用户已被禁用")

返回:

{
    "code": "USER_DISABLED",
    "message": "用户已被禁用"
}

普通项目一开始不用过度设计,先把 HTTPException 用好。