第一个接口
方法论: 从 Hello World 到用户注册,一步步写出真正可用的 API。
📖 本节目标
学完本节,你将能够:
- ✅ 写出第一个 Hello World 接口
- ✅ 理解路由、参数、响应的概念
- ✅ 使用 Pydantic 自动验证数据
- ✅ 实现用户注册接口
- ✅ 用 /docs 自动文档测试接口
预计用时: 40 分钟
0. 环境准备(必看!)
0.1 安装必要的库
在写代码之前,我们需要先安装一些工具(就像装修前先买工具)。
打开终端(黑框框),输入以下命令:
pip install fastapi uvicorn[standard] pydantic[email]
这行命令做了什么?
fastapi:后端框架,用来写 APIuvicorn:服务器,用来运行你的 APIpydantic[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
- 访问 http://localhost:8000/docs
- 找到
POST /api/users/register - 点击 “Try it out”
- 输入数据:
{ "username": "zhangsan", "password": "123456", "email": "zhang@example.com" } - 点击 “Execute”
- 看到返回结果:
{ "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
| 特性 | GET | POST |
|---|---|---|
| 用途 | 查询数据 | 创建/提交数据 |
| 参数位置 | 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 提示
到这里,你应该已经理解:
- FastAPI 怎么定义路由(
@app.get,@app.post) - 怎么接收参数(路径参数、查询参数、请求体)
- Pydantic 自动验证数据
- 怎么返回统一格式的响应
还不太懂?没关系!这很正常!
实战建议(按顺序做):
- 先跑通:把完整代码(第 5 节)复制下来,运行看效果
- 再改动:改改参数名、返回内容,看有什么变化
- 多测试:在
/docs里测试各种边界情况 - 遇到报错:
- ❌ 不要慌,不要放弃
- ✅ 复制完整错误信息
- ✅ 问 AI:“这个错误是什么意思?怎么解决?”
- ✅ 记住:报错是正常的,所有程序员每天都在报错和解决报错
关于复杂语法(@staticmethod、Optional[Any] 等):
- 你现在不需要完全理解它们
- 先记住它们的作用(比如
R.success()返回成功消息) - 具体怎么写?让 AI 帮你写
- 等写了 5-10 个接口后,你自然就懂了
Vibe Coding 核心原则:
- 能跑通 > 能背下来
- 知道工具能做什么 > 知道语法怎么写
- 会问 AI > 死记硬背
记住: 你不是在学“代码“,你是在学“用 AI 解决问题“!
AI 协作示例(遇到问题就这样问):
场景 1:想加新字段
提问:"我想在用户注册时增加一个手机号字段,要求必填,11 位数字,帮我写 Pydantic 模型代码"
场景 2:遇到报错
提问:"我运行代码时遇到这个错误:[粘贴完整错误信息],怎么解决?"
场景 3:想改功能
提问:"我想把注册接口改成登录接口,需要改哪些地方?给我完整代码"
场景 4:不懂语法
提问:"f-string 是什么意思?用生活化的例子解释一下"
📚 下一步
👉 数据库操作
学习如何用 Supabase 存储和查询真实数据,告别模拟数据库。
返回 后端开发基础 查看完整目录。