FastAPI 零基础入门教程(一)- 核心概念 + 第一个接口,半天跑通第一个项目

FastAPI 零基础入门教程(一)- 核心概念 + 第一个接口

前言

前面我们完整学完了 Pydantic v2 和 SQLModel 两套核心工具,分别解决了「数据校验序列化」和「数据库操作」两大后端核心问题。但零散的工具无法直接对外提供服务,我们需要一个 Web 框架把它们串起来,这就是 FastAPI。

FastAPI 由 Sebastián Ramírez(tiangolo)开发,是目前 Python 生态最火的现代 Web 框架,底层基于 Starlette(高性能异步)+ Pydantic v2(Rust 数据校验),性能比肩 Node.js/Go,开发效率却远超传统框架。更重要的是,你前面学的 Pydantic、SQLModel 知识可以 100% 直接复用,无缝衔接。

本文为 FastAPI 系列第一阶段,半天时间从零搭建第一个 FastAPI 项目,掌握核心概念、路径参数、查询参数、自动接口文档,代码全部可直接运行。

一、阶段学习目标

  1. 理解 FastAPI 三大核心优势,知道为什么选它而不是 Flask/Django

  2. 学会安装 FastAPI + Uvicorn,启动第一个 Web 服务

  3. 掌握路径参数、查询参数、默认值、可选参数四种传参方式

  4. 理解 HTTP 请求方法:GET / POST / PUT / DELETE 的语义区别

  5. 熟练使用 Swagger UI 交互式文档(/docs)和 ReDoc 文档

  6. 完成第一个实战:用户查询接口 + 列表分页接口

二、FastAPI 核心优势(为什么学它)

2.1 三大核心优势

优势一:自动生成交互式API文档

写代码即生成文档,不需要额外写注释、配置 Swagger。启动项目后访问 /docs 直接打开 Swagger UI,可以在线调试接口、查看参数说明、测试请求响应,前后端联调效率直接翻倍。

优势二:类型驱动开发,自动数据校验

基于 Python 原生类型注解 + Pydantic v2,所有请求参数、响应体自动校验,非法参数直接返回标准化错误,不用手写一堆 if 判断。同时自动序列化返回值,字典、ORM 对象直接转 JSON。

优势三:高性能异步支持

底层基于 Starlette 异步框架 + Uvicorn ASGI 服务器,原生支持 async/await 异步编程,IO 密集型场景性能比肩 Node.js 和 Go,远超同步 WSGI 框架(Flask、Django)。

2.2 FastAPI vs Flask vs Django 对比

维度 FastAPI Flask Django
性能 极高(异步+Rust校验) 中等(同步WSGI) 中等(同步WSGI)
学习曲线 中等(需懂类型注解) 简单 陡峭
自动文档 ✅ 原生支持 ❌ 需第三方插件 ❌ 需第三方插件
数据校验 ✅ Pydantic 原生 ❌ 手动/第三方 ✅ Form/Serializer
异步支持 ✅ 原生 async ❌ 同步为主 ⚠️ 部分支持
适用场景 API 服务、微服务、前后端分离 小型项目、快速原型 全栈、Admin 后台

结论:如果你做的是 前后端分离项目、API 服务、微服务,FastAPI 是目前 Python 生态的最优选择。

三、环境安装与第一个接口

3.1 安装依赖

FastAPI 本身只是框架,还需要一个 ASGI 服务器来运行,官方推荐 Uvicorn。

bash 复制代码
pip install fastapi
pip install "uvicorn[standard]"

说明:

  • fastapi:FastAPI 框架核心

  • uvicorn[standard]:ASGI 服务器,带标准库(支持 WebSocket、HTTP/2 等)

3.2 第一个 FastAPI 应用

创建 main.py 文件,写入最简单的接口:

python 复制代码
from fastapi import FastAPI

# 创建 FastAPI 应用实例
app = FastAPI(title="我的第一个FastAPI项目", version="1.0")

# 定义 GET 接口,路径为 /
@app.get("/")
def root():
    return {"message": "Hello FastAPI!"}

代码解析:

  1. FastAPI() 创建应用实例,可传 title、description、version 等参数用于文档展示

  2. @app.get("/") 装饰器定义 GET 请求,路径为根路径 /

  3. 函数返回值会自动序列化为 JSON 响应

3.3 启动服务

在终端执行启动命令:

bash 复制代码
uvicorn main:app --reload

参数说明:

  • main:app文件名:应用实例名,对应 main.py 中的 app 变量

  • --reload:开发模式,代码修改后自动重启服务器(生产环境不要用)

或者使用如下代码:

python 复制代码
# 脚本直接运行入口,方便在本地开发时快速启动服务
if __name__ == "__main__":
    import uvicorn

    # 使用 Uvicorn (ASGI 服务器) 启动应用
    # reload=True: 开启热重载,修改代码后自动重启服务,仅限开发环境使用
    # host: 监听本地回环地址
    # port: 监听 8000 端口
    uvicorn.run("main:app", reload=True, host="127.0.0.1", port=8000)

启动成功后,终端会显示:

text 复制代码
INFO:     Will watch for changes in these directories: ['D:\\ProjectCode\\fastapi']
INFO:     Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
INFO:     Started reloader process [28740] using WatchFiles
INFO:     Started server process [9460]
INFO:     Waiting for application startup.
INFO:     Application startup complete.

打开浏览器访问 http://127.0.0.1:8000,就能看到返回的 JSON:

json 复制代码
{"message": "Hello FastAPI!"}

3.4 常用启动参数

参数 说明
--host 0.0.0.0 监听所有网卡,允许外部访问
--port 8080 指定端口,默认 8000
--reload 开发热重载,代码变更自动重启
--workers 4 多进程 worker 数量,生产环境使用

四、路径参数详解

4.1 什么是路径参数

路径参数就是写在 URL 路径中的参数,用 {参数名} 包裹,常用于传递资源 ID、标识等。

例如:/users/123 中的 123 就是用户 ID 路径参数。

4.2 基础用法

python 复制代码
from fastapi import FastAPI

app = FastAPI()

# 路径参数 user_id 写在 { } 中
@app.get("/users/{user_id}")
def get_user(user_id: int):
    return {"user_id": user_id, "message": "查询用户成功"}

访问 http://127.0.0.1:8000/users/123,返回:

json 复制代码
{"user_id": 123, "message": "查询用户成功"}

关键特性:自动类型转换 + 校验

因为我们声明了 user_id: int,FastAPI 会自动:

  1. 把 URL 中的字符串 "123" 转换为整数 123

  2. 校验类型,如果传的是 /users/abc,自动返回 422 错误

非法参数返回示例:

json 复制代码
{
  "detail": [
    {
      "type": "int_parsing",
      "loc": ["path", "user_id"],
      "msg": "Input should be a valid integer, unable to parse string as an integer",
      "input": "abc"
    }
  ]
}

4.3 路径参数顺序问题

如果有固定路径和动态路径冲突,固定路径必须写在前面,否则会被动态路径匹配到。

python 复制代码
# ✅ 正确:固定路径写在前面
@app.get("/users/me")
def get_current_user():
    return {"username": "当前登录用户"}

# 动态路径写在后面
@app.get("/users/{user_id}")
def get_user(user_id: int):
    return {"user_id": user_id}
python 复制代码
# ❌ 错误:/users/me 会被匹配到 user_id=me
@app.get("/users/{user_id}")
def get_user(user_id: int):
    return {"user_id": user_id}

@app.get("/users/me")  # 永远不会执行到
def get_current_user():
    return {"username": "当前登录用户"}

4.4 预设值枚举(Enum)

如果路径参数只能是固定几个值,可以用枚举类限制,文档中也会显示可选值。

python 复制代码
from enum import Enum

class UserRole(str, Enum):
    ADMIN = "admin"
    USER = "user"
    GUEST = "guest"

@app.get("/users/role/{role}")
def get_users_by_role(role: UserRole):
    return {"role": role.value, "message": f"查询{role.value}角色用户"}

传入不在枚举中的值会自动返回 422 错误。

4.5 路径参数包含路径(斜杠)

如果参数值本身包含 /(比如文件路径),需要用 :path 修饰:

python 复制代码
@app.get("/files/{file_path:path}")
def read_file(file_path: str):
    return {"file_path": file_path}

访问 /files/home/user/docs/readme.txt,file_path 会完整匹配到 home/user/docs/readme.txt

五、查询参数详解

5.1 什么是查询参数

查询参数是 URL 中 ? 后面的键值对,用 & 分隔,常用于筛选、分页、搜索等场景。

例如:/items?page=1&page_size=10 中的 page 和 page_size 就是查询参数。

5.2 基础用法

FastAPI 中,函数参数中没有在路径中声明的,自动识别为查询参数

python 复制代码
@app.get("/items/")
def list_items(skip: int = 0, limit: int = 10):
    return {"skip": skip, "limit": limit}

访问 http://127.0.0.1:8000/items/?skip=20&limit=5,返回:

json 复制代码
{"skip": 20, "limit": 5}

特点:

  • 自动类型转换:字符串转 int/float/bool 等

  • 自动校验:类型错误返回 422

  • 有默认值的参数是可选的,不传就用默认值

5.3 必填查询参数

如果不给默认值,参数就是必填的,不传会报错。

python 复制代码
@app.get("/search/")
def search(keyword: str):  # 没有默认值,必填
    return {"keyword": keyword}

访问 /search/ 不传 keyword,返回 422 错误:

json 复制代码
{
  "detail": [
    {
      "type": "missing",
      "loc": ["query", "keyword"],
      "msg": "Field required",
      "input": null
    }
  ]
}

5.4 可选查询参数(Optional)

使用 Optional| None 声明可选参数,不传就是 None。

python 复制代码
from typing import Optional

@app.get("/users/")
def list_users(
    page: int = 1,
    page_size: int = 10,
    keyword: Optional[str] = None  # 可选,不传为None
):
    result = {"page": page, "page_size": page_size}
    if keyword:
        result["keyword"] = keyword
    return result

Python 3.10+ 可以用更简洁的写法:

python 复制代码
@app.get("/users/")
def list_users(keyword: str | None = None):
    return {"keyword": keyword}

5.5 布尔类型查询参数

布尔参数支持多种写法:1/0true/falseon/offyes/no,自动转换。

python 复制代码
@app.get("/items/")
def list_items(is_active: bool = False):
    return {"is_active": is_active}

以下访问方式都等价:

  • /items/?is_active=true

  • /items/?is_active=1

  • /items/?is_active=on

  • /items/?is_active=yes

5.6 多路径参数 + 多查询参数混合

路径参数和查询参数可以混合使用,FastAPI 自动识别哪个是哪个。

python 复制代码
@app.get("/users/{user_id}/items/")
def get_user_items(
    user_id: int,           # 路径参数
    page: int = 1,          # 查询参数
    page_size: int = 10,    # 查询参数
    keyword: str | None = None  # 可选查询参数
):
    return {
        "user_id": user_id,
        "page": page,
        "page_size": page_size,
        "keyword": keyword
    }

访问 /users/123/items/?page=2&keyword=phone,正确解析所有参数。

text 复制代码
{
  "user_id": 123,
  "page": 2,
  "page_size": 10,
  "keyword": "phone"
}

六、请求方法与 RESTful 规范

6.1 常用 HTTP 请求方法

FastAPI 支持所有标准 HTTP 方法,每个方法对应不同的业务语义:

方法 FastAPI 装饰器 语义
GET @app.get() 查询数据,幂等,无请求体
POST @app.post() 新增数据,非幂等
PUT @app.put() 全量更新,幂等
PATCH @app.patch() 局部更新,非幂等
DELETE @app.delete() 删除数据,幂等
HEAD @app.head() 只获取响应头,无响应体
OPTIONS @app.options() 获取资源支持的方法

6.2 RESTful 设计规范

RESTful 是一种 API 设计风格,核心原则是「资源为中心」,用 HTTP 方法表示操作。

以用户资源为例的标准设计:

操作 方法 + 路径 说明
查询列表 GET /users 获取用户列表,支持分页筛选
查询单个 GET /users/{id} 根据 ID 查询单个用户
新增 POST /users 创建新用户
全量更新 PUT /users/{id} 完整更新用户信息
局部更新 PATCH /users/{id} 只更新部分字段
删除 DELETE /users/{id} 删除指定用户

6.3 常见状态码

状态码 含义 使用场景
200 OK 查询、更新成功
201 Created 创建成功
204 No Content 删除成功,无响应体
400 Bad Request 参数错误
401 Unauthorized 未登录/Token无效
403 Forbidden 无权限
404 Not Found 资源不存在
422 Unprocessable Entity 参数校验失败(FastAPI默认)
500 Internal Server Error 服务器内部错误

6.4 指定响应状态码

可以在装饰器中用 status_code 指定成功时的状态码:

python 复制代码
from fastapi import FastAPI, status

app = FastAPI()

# 创建成功返回 201
@app.post("/users/", status_code=status.HTTP_201_CREATED)
def create_user(username: str):
    return {"username": username}

# 删除成功返回 204
@app.delete("/users/{user_id}", status_code=status.HTTP_204_NO_CONTENT)
def delete_user(user_id: int):
    # 删除逻辑...
    return None

推荐使用 status 模块的常量,而不是直接写数字,可读性更好。

七、自动接口文档(前后端联调神器)

7.1 两套自动文档

FastAPI 原生提供两套自动生成的接口文档,不需要任何额外配置:

  • Swagger UI :交互式文档,可在线调试,地址 /docs

  • ReDoc :简洁文档,适合打印和阅读,地址 /redoc

启动项目后直接访问即可:

  • Swagger UI:http://127.0.0.1:8000/docs

  • ReDoc:http://127.0.0.1:8000/redoc

7.2 Swagger UI 使用教程

Swagger UI 是开发和联调最常用的工具,功能非常强大:

  1. 接口列表:按标签分组展示所有接口,点击展开查看详情

  2. 参数说明:自动显示参数名、类型、是否必填、默认值、描述

  3. Try it out:点击按钮后可以直接在浏览器中发送请求,测试接口

  4. 响应示例:展示响应状态码、响应体结构、示例数据

使用步骤:

  1. 打开 /docs,找到要测试的接口

  2. 点击右上角 Try it out 按钮

  3. 填写参数(路径参数、查询参数、请求体)

  4. 点击 Execute 发送请求

  5. 查看 Responses 中的返回结果

7.3 文档信息配置

创建 FastAPI 应用时可以配置文档展示信息:

python 复制代码
from fastapi import FastAPI

app = FastAPI(
    title="用户管理系统 API",
    description="这是一个基于 FastAPI 的用户管理后台系统,提供完整的用户 CRUD 接口",
    version="1.0.0",
    terms_of_service="https://example.com/terms/",
    contact={
        "name": "技术支持",
        "url": "https://example.com/contact/",
        "email": "support@example.com",
    },
    license_info={
        "name": "Apache 2.0",
        "url": "https://www.apache.org/licenses/LICENSE-2.0.html",
    },
)

7.4 接口标签与描述

tags 给接口分组,用 summarydescription 添加说明:

python 复制代码
@app.get(
    "/users/{user_id}",
    tags=["用户管理"],
    summary="根据ID查询用户",
    response_description="用户详情"
)
def get_user(user_id: int):
    """
    根据用户ID查询用户详细信息

    - **user_id**: 用户ID,必填
    - 返回用户的完整信息
    """
    return {"user_id": user_id, "username": "test"}

说明:

  • tags:接口分组标签,文档中按标签分类展示

  • summary:接口简短摘要,显示在列表中

  • 函数 docstring:接口详细描述,支持 Markdown 格式

  • response_description:响应说明

7.5 OpenAPI Schema

FastAPI 自动生成符合 OpenAPI 3.0 规范的 JSON Schema,地址:

http://127.0.0.1:8000/openapi.json

这个 Schema 可以导入到 Postman、Apifox 等工具,也可以用来生成前端 SDK、Mock 数据等。

八、阶段综合实战:用户管理基础接口

8.1 实战目标

整合本阶段所有知识点,实现一套基础用户管理接口:

  1. 用户列表接口(分页 + 关键词搜索)

  2. 用户详情接口(根据ID查询)

  3. 新增用户接口

  4. 更新用户接口

  5. 删除用户接口

使用内存字典模拟数据库,不涉及真实数据库(下阶段再整合 SQLModel)。

8.2 完整实战代码

python 复制代码
from fastapi import FastAPI, HTTPException, status
from typing import Optional

app = FastAPI(
    title="用户管理系统",
    description="FastAPI第一阶段实战:基础用户CRUD接口",
    version="1.0"
)

# 模拟数据库:内存字典存储
fake_db = {
    1: {"id": 1, "username": "admin", "email": "admin@example.com", "age": 25},
    2: {"id": 2, "username": "zhangsan", "email": "zhangsan@qq.com", "age": 22},
    3: {"id": 3, "username": "lisi", "email": "lisi@qq.com", "age": 30},
}
next_id = 4

# ========== 1. 用户列表(分页 + 搜索) ==========
@app.get("/users/", tags=["用户管理"], summary="用户列表")
def list_users(
    page: int = 1,
    page_size: int = 10,
    keyword: Optional[str] = None
):
    """
    获取用户列表,支持分页和关键词搜索

    - **page**: 页码,默认1
    - **page_size**: 每页数量,默认10
    - **keyword**: 搜索关键词,可选,匹配用户名/邮箱
    """
    # 转换为列表
    user_list = list(fake_db.values())

    # 关键词过滤
    if keyword:
        keyword = keyword.lower()
        user_list = [
            u for u in user_list
            if keyword in u["username"].lower() or keyword in u["email"].lower()
        ]

    # 分页
    total = len(user_list)
    start = (page - 1) * page_size
    end = start + page_size
    items = user_list[start:end]

    return {
        "page": page,
        "page_size": page_size,
        "total": total,
        "items": items
    }

# ========== 2. 用户详情 ==========
@app.get("/users/{user_id}", tags=["用户管理"], summary="用户详情")
def get_user(user_id: int):
    """根据用户ID查询详情"""
    user = fake_db.get(user_id)
    if not user:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail="用户不存在"
        )
    return user

# ========== 3. 新增用户 ==========
@app.post("/users/", tags=["用户管理"], summary="新增用户", status_code=status.HTTP_201_CREATED)
def create_user(username: str, email: str, age: Optional[int] = None):
    """创建新用户"""
    global next_id
    # 检查用户名是否已存在
    for u in fake_db.values():
        if u["username"] == username:
            raise HTTPException(
                status_code=status.HTTP_400_BAD_REQUEST,
                detail="用户名已存在"
            )
    new_user = {
        "id": next_id,
        "username": username,
        "email": email,
        "age": age
    }
    fake_db[next_id] = new_user
    next_id += 1
    return new_user

# ========== 4. 更新用户 ==========
@app.put("/users/{user_id}", tags=["用户管理"], summary="更新用户")
def update_user(
    user_id: int,
    username: Optional[str] = None,
    email: Optional[str] = None,
    age: Optional[int] = None
):
    """更新用户信息,传什么更新什么"""
    user = fake_db.get(user_id)
    if not user:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail="用户不存在"
        )
    if username is not None:
        user["username"] = username
    if email is not None:
        user["email"] = email
    if age is not None:
        user["age"] = age
    return user

# ========== 5. 删除用户 ==========
@app.delete("/users/{user_id}", tags=["用户管理"], summary="删除用户", status_code=status.HTTP_204_NO_CONTENT)
def delete_user(user_id: int):
    """删除指定用户"""
    if user_id not in fake_db:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail="用户不存在"
        )
    del fake_db[user_id]
    return None

8.3 运行测试

  1. 启动服务:uvicorn main:app --reload

  2. 打开文档:http://127.0.0.1:8000/docs

  3. 逐个点击 Try it out 测试每个接口

  4. 测试异常场景:查询不存在的用户、重复用户名等

8.4 实战覆盖的知识点

  • ✅ 路径参数:user_id

  • ✅ 查询参数:pagepage_sizekeyword

  • ✅ 默认值与可选参数

  • ✅ GET / POST / PUT / DELETE 四种请求方法

  • ✅ HTTPException 异常抛出

  • ✅ status 状态码常量

  • ✅ tags 接口分组

  • ✅ summary 接口摘要

  • ✅ docstring 接口详细描述

  • ✅ 自动文档与在线调试

九、阶段核心总结

9.1 核心知识点清单

本阶段需要掌握的核心内容:

  1. FastAPI 三大优势:自动文档、类型驱动、高性能异步

  2. 环境搭建:fastapi + uvicorn 安装与启动

  3. 路径参数{param} 语法、自动类型转换、枚举限制

  4. 查询参数:默认值、必填、可选(Optional)、布尔类型

  5. 请求方法:GET/POST/PUT/PATCH/DELETE 语义区别

  6. RESTful 规范:资源为中心的 URL 设计

  7. 状态码:200/201/204/400/401/403/404/422/500

  8. 自动文档/docs Swagger UI、/redoc ReDoc

  9. 异常处理:HTTPException 抛出业务异常

9.2 一天学习节奏建议

时间段 内容 目标
上午 0.5h 环境安装 + 第一个接口 跑通 Hello World,访问文档
上午 1h 路径参数 + 查询参数 掌握两种传参方式,写 5 个以上练习接口
下午 1h 请求方法 + RESTful + 状态码 理解 HTTP 方法语义,设计标准 REST 接口
下午 0.5h 自动文档 + 标签描述 熟练使用 Swagger UI 调试
下午 1h 综合实战:用户 CRUD 独立完成完整的用户管理接口

十、新手高频避坑指南

10.1 新手高频踩坑点

坑1:路径参数顺序错误

固定路径必须写在动态路径前面,否则固定路径会被动态参数匹配到。

python 复制代码
# ❌ 错误:/users/me 会被匹配成 user_id=me
@app.get("/users/{user_id}")
def get_user(user_id: int): ...

@app.get("/users/me")
def get_me(): ...

# ✅ 正确:固定路径在前
@app.get("/users/me")
def get_me(): ...

@app.get("/users/{user_id}")
def get_user(user_id: int): ...

坑2:查询参数忘记写默认值

没有默认值的参数就是必填的,不传就报 422 错误。可选参数必须给默认值 None。

python 复制代码
# ❌ 错误:keyword 必填,不传报错
@app.get("/search/")
def search(keyword: str): ...

# ✅ 正确:可选参数
@app.get("/search/")
def search(keyword: str | None = None): ...

坑3:路径末尾斜杠不一致

/items/items/ 是两个不同的路径,FastAPI 会自动重定向,但建议统一风格。

坑4:POST 请求用查询参数传数据

POST/PUT 应该用请求体(Body)传数据,而不是查询参数。查询参数只用于 GET 的筛选条件。本阶段先用查询参数演示,下阶段学请求体。

坑5:忘记 --reload 导致修改不生效

开发模式一定要加 --reload,否则代码修改后服务器不会自动重启。生产环境不要加。

坑6:状态码用数字而不是常量

推荐使用 from fastapi import status 的常量,如 status.HTTP_404_NOT_FOUND,可读性更好,不容易写错。

10.2 最佳实践

  • 所有接口都加 tags 分组,文档更清晰

  • 重要接口写 summary 和 docstring 描述

  • 统一使用 status 模块的状态码常量

  • GET 查询用查询参数,POST/PUT 用请求体(下阶段学)

  • 路径用名词复数形式:/users/orders

  • 开发模式开启 --reload,生产环境关闭