Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

第一个接口

方法论: 从 Hello World 到用户注册,一步步写出真正可用的 API。


📖 本节目标

学完本节,你将能够:

  • ✅ 写出第一个 Hello World 接口
  • ✅ 理解路由、参数、响应的概念
  • ✅ 使用 Pydantic 自动验证数据
  • ✅ 实现用户注册接口
  • ✅ 用 /docs 自动文档测试接口

预计用时: 40 分钟


0. 环境准备(必看!)

0.1 安装必要的库

在写代码之前,我们需要先安装一些工具(就像装修前先买工具)。

打开终端(黑框框),输入以下命令:

pip install fastapi uvicorn[standard] pydantic[email]

这行命令做了什么?

  • fastapi:后端框架,用来写 API
  • uvicorn:服务器,用来运行你的 API
  • pydantic[email]:数据验证库,带邮箱格式验证功能

预期结果: 看到类似 “Successfully installed fastapi-0.xx.x uvicorn-0.xx.x…” 的提示就成功了。

遇到报错?不要慌!

  • 如果提示 “pip 不是内部或外部命令”,说明 Python 没装好,把错误信息复制给 AI 问怎么解决
  • 如果提示权限错误,在命令前加 sudo(Mac/Linux)或用管理员身份运行(Windows)

1. Hello World 接口

1.1 最简单的接口

创建 main.py

from fastapi import FastAPI

# 创建应用实例
app = FastAPI()

# 定义根路由
@app.get("/")
def root():
    return {"message": "Hello World"}

# 启动应用(开发环境)
if __name__ == "__main__":
    import uvicorn
    uvicorn.run("main:app", host="0.0.0.0", port=8000, reload=True)

运行:

python main.py

访问: http://localhost:8000

会看到:

{"message": "Hello World"}

1.2 理解代码

@app.get("/")  # ← 装饰器:告诉 FastAPI 这是一个 GET 请求,路径是 "/"
def root():    # ← 函数名随便起,但要见名知意
    return {"message": "Hello World"}  # ← 返回 JSON 数据

类比理解:

@app.get("/")  = 在餐厅门口挂牌子:"这里是大门"
def root()     = 定义:客人进门后要做什么
return {...}   = 给客人回复:"你好,欢迎光临"

1.3 自动文档

FastAPI 最强大的功能:自动生成 API 文档

访问:http://localhost:8000/docs

你会看到一个漂亮的交互式文档(Swagger UI),可以直接测试接口!


2. 理解路由和参数

2.1 路径参数(Path Parameters)

场景: 获取指定 ID 的用户信息

@app.get("/users/{user_id}")
def get_user(user_id: int):
    return {"user_id": user_id, "username": f"user_{user_id}"}

访问: http://localhost:8000/users/123

返回:

{"user_id": 123, "username": "user_123"}

理解:

  • {user_id} 是占位符,可以匹配任意内容
  • user_id: int 自动把字符串转成整数
  • 如果访问 /users/abc,FastAPI 会自动返回 400 错误(类型不匹配)

小知识:f-string 是什么?

代码中的 f"user_{user_id}" 是 Python 的 f-string(格式化字符串),作用是把变量填进字符串。

# 普通拼接(繁琐)
username = "user_" + str(user_id)

# f-string(简洁)
username = f"user_{user_id}"  # 花括号里的变量会自动替换

类比理解:就像填空题,f"user_{user_id}" 表示“user_“ + 这里填 user_id 的值。

你只需要记住:看到 f"...{变量}..." 就是在做字符串填空,让 AI 帮你写就行!

2.2 查询参数(Query Parameters)

场景: 搜索功能,带分页

@app.get("/search")
def search(q: str, skip: int = 0, limit: int = 10):
    return {
        "query": q,
        "skip": skip,
        "limit": limit,
        "results": [f"结果 {i}" for i in range(1, limit + 1)]
    }

访问: http://localhost:8000/search?q=Python&skip=0&limit=5

返回:

{
  "query": "Python",
  "skip": 0,
  "limit": 5,
  "results": ["结果 1", "结果 2", "结果 3", "结果 4", "结果 5"]
}

理解:

  • 查询参数在 URL 后面,用 ? 开头,& 连接
  • skip: int = 0 表示有默认值,可以不传
  • q: str 没有默认值,必须传

2.3 请求体(Request Body)

场景: 用户提交表单数据

from pydantic import BaseModel

# 定义数据模型
class Item(BaseModel):
    name: str
    price: float
    description: str = None  # 可选字段

@app.post("/items")
def create_item(item: Item):
    return {
        "message": "创建成功",
        "item": {
            "name": item.name,
            "price": item.price,
            "description": item.description
        }
    }

测试(用 /docs 或 Postman):

请求:

POST /items
{
  "name": "苹果",
  "price": 5.5,
  "description": "新鲜的苹果"
}

返回:

{
  "message": "创建成功",
  "item": {
    "name": "苹果",
    "price": 5.5,
    "description": "新鲜的苹果"
  }
}

3. Pydantic 数据验证

3.1 为什么需要数据验证?

不验证的后果:

@app.post("/users")
def create_user(username, password):
    # ❌ 用户可能传入任何数据
    # username = ""  (空字符串)
    # password = 123  (不是字符串)
    # 你需要手动写一堆 if 判断
    ...

用 Pydantic 自动验证:

from pydantic import BaseModel, Field, EmailStr

class UserRegisterRequest(BaseModel):
    username: str = Field(..., min_length=3, max_length=50)
    password: str = Field(..., min_length=6)
    email: EmailStr  # 自动验证邮箱格式

@app.post("/users/register")
def register(user: UserRegisterRequest):
    # ✅ 到这里,数据一定是合法的
    # username 长度 3-50
    # password 长度 >= 6
    # email 格式正确
    return {"message": "注册成功"}

3.2 Field 验证规则

from pydantic import BaseModel, Field

class UserModel(BaseModel):
    # 必填,长度 3-50
    username: str = Field(..., min_length=3, max_length=50)

    # 必填,最少 6 个字符
    password: str = Field(..., min_length=6)

    # 可选,1-120 之间
    age: int = Field(None, ge=1, le=120)

    # 可选,默认值是 False
    is_active: bool = Field(default=True)

常用验证规则:

规则说明示例
...必填(没有默认值)Field(...)
default=xxx默认值Field(default=0)
min_length最小长度Field(..., min_length=3)
max_length最大长度Field(..., max_length=50)
ge大于等于(greater or equal)Field(None, ge=1)
le小于等于(less or equal)Field(None, le=120)
regex正则表达式Field(..., regex=r'^[a-z0-9]+$')

3.3 自动错误提示

如果用户传入不合法的数据,FastAPI 会自动返回详细错误:

请求:

POST /users/register
{
  "username": "ab",  ← 太短(最少 3 个字符)
  "password": "123",  ← 太短(最少 6 个字符)
  "email": "not-an-email"  ← 格式错误
}

返回:

{
  "detail": [
    {
      "loc": ["body", "username"],
      "msg": "ensure this value has at least 3 characters",
      "type": "value_error.any_str.min_length"
    },
    {
      "loc": ["body", "password"],
      "msg": "ensure this value has at least 6 characters",
      "type": "value_error.any_str.min_length"
    },
    {
      "loc": ["body", "email"],
      "msg": "value is not a valid email address",
      "type": "value_error.email"
    }
  ]
}

用户看到清晰的错误提示! 你不用写一行验证代码!


4. 实战:用户注册接口

4.1 需求分析

接口路径: POST /api/users/register

请求参数:

  • username: 用户名(3-50 字符)
  • password: 密码(最少 6 字符)
  • email: 邮箱(可选,格式验证)

返回格式:

{
  "code": 200,
  "msg": "注册成功",
  "data": {
    "user_id": 123
  }
}

4.2 完整代码

第一步:定义数据模型

from pydantic import BaseModel, Field, EmailStr
from typing import Optional

class UserRegisterRequest(BaseModel):
    """用户注册请求模型"""
    username: str = Field(..., min_length=3, max_length=50, description="用户名")
    password: str = Field(..., min_length=6, description="密码")
    email: Optional[EmailStr] = Field(None, description="邮箱")

    class Config:
        # 在 /docs 文档中显示的示例(方便测试)
        schema_extra = {
            "example": {
                "username": "zhangsan",
                "password": "123456",
                "email": "zhangsan@example.com"
            }
        }

关于 class Config 的说明(可跳过)

这是 Pydantic 的配置类,用来给 FastAPI 的 /docs 文档提供测试示例数据。

为什么要写这个?

  • 不写也能跑,但写了之后在 /docs 测试时会自动填好示例数据,省得你每次都手动输入。

类比理解:就像给表单预填了一个“张三“的示例,方便你快速测试,不需要每次都想用户名。

Vibe Coding 建议:先把代码跑通,这个 Config 可以以后再加。如果你觉得它复杂,可以直接删掉,不影响功能!

第二步:定义统一响应格式

Vibe Coding 提示:这部分代码比较复杂(涉及 class@staticmethod 等语法),但好消息是:你只需要理解它的作用,具体语法让 AI 写!

from typing import Optional, Any

class Response(BaseModel):
    """统一响应格式"""
    code: int          # 状态码(200=成功,400/500=失败)
    msg: str           # 提示信息(给用户看的文字)
    data: Optional[Any] = None  # 返回数据(可以是任意类型)

class R:
    """响应工具类(快速生成响应的工具箱)"""

    @staticmethod
    def success(data: Any = None, msg: str = "操作成功"):
        return Response(code=200, msg=msg, data=data)

    @staticmethod
    def error(msg: str = "操作失败", code: int = 500):
        return Response(code=code, msg=msg, data=None)

你只需要记住

  • R.success(data={...}) = 返回成功消息
  • R.error(msg="失败原因") = 返回错误消息

关于语法细节(可以跳过)

  • Optional[Any]:表示这个字段可以是任意类型,也可以为空(让 AI 帮你写)
  • @staticmethod:表示这是一个工具函数,可以直接用 R.success() 调用(让 AI 帮你写)
  • 如果你看到这些语法觉得头晕,完全可以跳过! 先把功能跑通,以后再慢慢理解。

类比理解class R 就像一个万能回复模板生成器。你只需要告诉它“成功了“或“失败了“,它就自动生成标准格式的回复。

第三步:实现注册接口

from fastapi import FastAPI, HTTPException
import hashlib

app = FastAPI(title="我的后端 API")

# 模拟数据库(真实项目会用 Supabase)
users_db = []

**重要提示:关于"模拟数据库"**

这里用列表 `[]` 来模拟数据库,**数据只存在内存里**。这意味着:
- ✅ 程序运行时,注册的用户会保存在 `users_db` 里
- ❌ 关闭程序后再重启,所有用户数据会消失

**为什么不直接用真数据库?**
因为我们要先学会"怎么写接口逻辑",数据库是下一章的内容。就像学开车要先学方向盘,再学倒车入库。

**类比理解**:`users_db = []` 就像在草稿纸上记笔记,擦掉就没了。真正的数据库(Supabase)才是记在本子上,永久保存。

---

@app.post("/api/users/register", response_model=Response)
def register(user: UserRegisterRequest):
    """
    用户注册接口

    - **username**: 用户名(3-50 字符)
    - **password**: 密码(最少 6 字符)
    - **email**: 邮箱(可选)
    """

    # 1. 检查用户名是否已存在
    for existing_user in users_db:
        if existing_user["username"] == user.username:
            return R.error(msg="用户名已存在", code=400)

    # 2. 密码加密(实际项目用 bcrypt,这里简化演示)
    password_hash = hashlib.sha256(user.password.encode()).hexdigest()

    # 👆 这行代码看起来很吓人?别慌!你只需要知道它做了什么:
    # - 把密码(如 "123456")变成一串乱码(如 "8d969eef6...")
    # - 这样即使数据库被黑客偷走,黑客也看不懂原始密码
    # - 具体怎么写?直接问 AI:"帮我写密码加密代码"

    # 3. 存入"数据库"
    new_user = {
        "user_id": len(users_db) + 1,
        "username": user.username,
        "password_hash": password_hash,
        "email": user.email
    }
    users_db.append(new_user)

    # 4. 返回成功响应
    return R.success(
        msg="注册成功",
        data={"user_id": new_user["user_id"]}
    )

4.3 测试接口

方式1: 使用 /docs

  1. 访问 http://localhost:8000/docs
  2. 找到 POST /api/users/register
  3. 点击 “Try it out”
  4. 输入数据:
    {
      "username": "zhangsan",
      "password": "123456",
      "email": "zhang@example.com"
    }
    
  5. 点击 “Execute”
  6. 看到返回结果:
    {
      "code": 200,
      "msg": "注册成功",
      "data": {
        "user_id": 1
      }
    }
    

方式2: 使用 Postman 或 Thunder Client

请求:

POST http://localhost:8000/api/users/register
Content-Type: application/json

{
  "username": "lisi",
  "password": "abc123",
  "email": "li@example.com"
}

返回:

{
  "code": 200,
  "msg": "注册成功",
  "data": {
    "user_id": 2
  }
}

4.4 测试边界情况

测试1: 用户名太短

{
  "username": "ab",
  "password": "123456"
}

返回:

{
  "detail": [
    {
      "loc": ["body", "username"],
      "msg": "ensure this value has at least 3 characters",
      "type": "value_error.any_str.min_length"
    }
  ]
}

测试2: 重复用户名

再次提交 username: "zhangsan"

返回:

{
  "code": 400,
  "msg": "用户名已存在",
  "data": null
}

测试3: 邮箱格式错误

{
  "username": "wangwu",
  "password": "123456",
  "email": "not-an-email"
}

返回:

{
  "detail": [
    {
      "loc": ["body", "email"],
      "msg": "value is not a valid email address",
      "type": "value_error.email"
    }
  ]
}

5. 完整代码(可直接运行)

from fastapi import FastAPI
from pydantic import BaseModel, Field, EmailStr
from typing import Optional, Any
import hashlib

# ===== 数据模型 =====

class UserRegisterRequest(BaseModel):
    """用户注册请求模型"""
    username: str = Field(..., min_length=3, max_length=50)
    password: str = Field(..., min_length=6)
    email: Optional[EmailStr] = None

    class Config:
        schema_extra = {
            "example": {
                "username": "zhangsan",
                "password": "123456",
                "email": "zhang@example.com"
            }
        }

class Response(BaseModel):
    """统一响应格式"""
    code: int
    msg: str
    data: Optional[Any] = None

# ===== 响应工具类 =====

class R:
    @staticmethod
    def success(data: Any = None, msg: str = "操作成功"):
        return Response(code=200, msg=msg, data=data)

    @staticmethod
    def error(msg: str = "操作失败", code: int = 500):
        return Response(code=code, msg=msg, data=None)

# ===== FastAPI 应用 =====

app = FastAPI(title="我的后端 API", version="1.0.0")

# 模拟数据库
users_db = []

@app.get("/")
def root():
    """根路径"""
    return {"message": "Hello World"}

@app.post("/api/users/register", response_model=Response)
def register(user: UserRegisterRequest):
    """用户注册接口"""

    # 检查用户名是否存在
    for existing_user in users_db:
        if existing_user["username"] == user.username:
            return R.error(msg="用户名已存在", code=400)

    # 密码加密
    password_hash = hashlib.sha256(user.password.encode()).hexdigest()

    # 存入数据库
    new_user = {
        "user_id": len(users_db) + 1,
        "username": user.username,
        "password_hash": password_hash,
        "email": user.email
    }
    users_db.append(new_user)

    return R.success(
        msg="注册成功",
        data={"user_id": new_user["user_id"]}
    )

@app.get("/api/users")
def list_users():
    """查看所有用户(测试用)"""
    return R.success(data=users_db)

# ===== 启动应用 =====

if __name__ == "__main__":
    import uvicorn
    uvicorn.run("main:app", host="0.0.0.0", port=8000, reload=True)

保存为 main.py,运行:

python main.py

访问: http://localhost:8000/docs

恭喜!你已经写出了第一个真正可用的 API! 🎉


6. 理解 GET vs POST

特性GETPOST
用途查询数据创建/提交数据
参数位置URL 查询参数请求体(Body)
是否安全❌ 参数可见✅ 参数隐藏
能否刷新✅ 可以反复请求❌ 可能重复提交
示例/search?q=Python/api/users/register

什么时候用 GET?

  • 获取列表(文章列表、商品列表)
  • 查询详情(用户信息、订单详情)
  • 搜索功能

什么时候用 POST?

  • 创建数据(注册、发布文章)
  • 提交表单(登录、评论)
  • 上传文件

7. 常见问题

Q1: 为什么要用 Pydantic?

A: 自动验证数据,减少 90% 的验证代码。

不用 Pydantic:

@app.post("/users/register")
def register(username, password, email):
    if not username:
        return {"error": "用户名不能为空"}
    if len(username) < 3:
        return {"error": "用户名太短"}
    if len(password) < 6:
        return {"error": "密码太短"}
    if email and "@" not in email:
        return {"error": "邮箱格式错误"}
    # ... 50 行验证代码

用 Pydantic:

class UserRegisterRequest(BaseModel):
    username: str = Field(..., min_length=3)
    password: str = Field(..., min_length=6)
    email: EmailStr = None

@app.post("/users/register")
def register(user: UserRegisterRequest):
    # 到这里数据一定合法
    ...

Q2: 为什么要统一响应格式?

A: 前端可以统一处理响应。

// 前端代码
const response = await fetch('/api/users/register', {...});
const result = await response.json();

if (result.code === 200) {
    alert(result.msg);  // "注册成功"
} else {
    alert(result.msg);  // "用户名已存在"
}

Q3: 密码为什么要加密?

A: 数据库被黑客入侵时,用户的明文密码不会泄露。

# ❌ 明文存储
{"username": "zhangsan", "password": "123456"}  # 黑客直接看到密码

# ✅ 加密存储
{"username": "zhangsan", "password_hash": "8d969eef6..."}  # 黑客无法反推

Q4: 为什么 /docs 文档这么智能?

A: FastAPI 根据你的代码自动生成:

  • @app.post("/api/users/register") 知道路径和方法
  • UserRegisterRequest 知道参数格式
  • class Config: schema_extra 知道示例数据
  • response_model=Response 知道返回格式

💡 Vibe Coding 提示

到这里,你应该已经理解:

  1. FastAPI 怎么定义路由(@app.get, @app.post
  2. 怎么接收参数(路径参数、查询参数、请求体)
  3. Pydantic 自动验证数据
  4. 怎么返回统一格式的响应

还不太懂?没关系!这很正常!

实战建议(按顺序做)

  1. 先跑通:把完整代码(第 5 节)复制下来,运行看效果
  2. 再改动:改改参数名、返回内容,看有什么变化
  3. 多测试:在 /docs 里测试各种边界情况
  4. 遇到报错
    • ❌ 不要慌,不要放弃
    • ✅ 复制完整错误信息
    • ✅ 问 AI:“这个错误是什么意思?怎么解决?”
    • ✅ 记住:报错是正常的,所有程序员每天都在报错和解决报错

关于复杂语法(@staticmethod、Optional[Any] 等)

  • 你现在不需要完全理解它们
  • 先记住它们的作用(比如 R.success() 返回成功消息)
  • 具体怎么写?让 AI 帮你写
  • 等写了 5-10 个接口后,你自然就懂了

Vibe Coding 核心原则

  • 能跑通 > 能背下来
  • 知道工具能做什么 > 知道语法怎么写
  • 会问 AI > 死记硬背

记住: 你不是在学“代码“,你是在学“用 AI 解决问题“!

AI 协作示例(遇到问题就这样问)

场景 1:想加新字段
提问:"我想在用户注册时增加一个手机号字段,要求必填,11 位数字,帮我写 Pydantic 模型代码"

场景 2:遇到报错
提问:"我运行代码时遇到这个错误:[粘贴完整错误信息],怎么解决?"

场景 3:想改功能
提问:"我想把注册接口改成登录接口,需要改哪些地方?给我完整代码"

场景 4:不懂语法
提问:"f-string 是什么意思?用生活化的例子解释一下"

📚 下一步

👉 数据库操作

学习如何用 Supabase 存储和查询真实数据,告别模拟数据库。

返回 后端开发基础 查看完整目录。