前言你是不是也经常懵刚开始学FastAPI的时候我每次写接口都要纠结半天查数据用GET还是POST更新数据用PUT还是POST它们有啥区别删除资源到底用DELETE还是POST相信很多同学都有同样的困惑。网上很多教程一上来就贴代码讲完语法却不告诉你什么场景该用什么。这篇文章换个思路——先讲场景再上代码最后一张表总结看完你就彻底清楚了。一、核心概念四个接口到底什么关系GET、POST、PUT、DELETE是四种HTTP方法对应数据的增删改查CRUD操作。打个比方——把你的服务器想象成一个仓库管理员HTTP方法类比回答的问题对应CRUDGET去仓库查货这个货架上有多少件货Read查POST往仓库存新货给我新增一个货架放这批货Create增PUT把仓库的货整个换掉把3号货架上的货全部清空换成这批新货Update改DELETE从仓库销毁货物把3号货架上的货扔掉Delete删一句话总结GET只读不改POST新增创建PUT整体替换DELETE删除资源。下面逐个拆解:二、GET接口只管查不管改什么时候用GET获取资源列表或详情搜索、筛选、分页查询不修改服务器上的任何数据GET请求的核心特征是安全且幂等——安全意味着不产生副作用幂等意味着调用1次和调用100次结果一样。完整代码示例from fastapi import FastAPI, Query, HTTPException from pydantic import BaseModel app FastAPI() # 模拟数据库 fake_users { 1: {id: 1, name: 张三, age: 25, email: zhangsantest.com}, 2: {id: 2, name: 李四, age: 30, email: lisitest.com}, } class UserOut(BaseModel): id: int name: str age: int email: str # 场景1获取所有用户支持分页查询 app.get(/users/, response_modellist[UserOut]) async def get_users(skip: int 0, limit: int Query(10, le100)): 查询用户列表skip和limit通过URL查询参数传递 users list(fake_users.values()) return users[skip : skip limit] # 场景2获取单个用户详情 app.get(/users/{user_id}, response_modelUserOut) async def get_user(user_id: int): 通过路径参数获取指定用户 if user_id not in fake_users: raise HTTPException(status_code404, detail用户不存在) return fake_users[user_id] # 场景3按关键词搜索用户 app.get(/users/search/, response_modellist[UserOut]) async def search_users( keyword: str Query(..., min_length1, description搜索关键词), max_age: int Query(default100, le150, description年龄上限), ): 通过查询参数搜索用户 results [ u for u in fake_users.values() if keyword in u[name] and u[age] max_age ] return results关键点特征说明参数位置路径参数/users/{id}或查询参数?skip0limit10请求体不能有请求体GET请求不携带body幂等性幂等——多次调用结果相同安全性安全——不修改服务器数据缓存浏览器/CDN可以缓存GET响应使用场景查列表、查详情、搜索、筛选注意路由顺序/users/search/必须定义在/users/{user_id}之前否则FastAPI会把search当成user_id来匹配。三、POST接口创建新东西什么时候用POST创建新资源注册用户、新增文章、提交订单提交表单数据执行一个非幂等的操作同样的请求提交两次会创建两条记录POST的核心特征是不幂等——提交两次相同的数据会创建两个资源。完整代码示例from fastapi import FastAPI, HTTPException from pydantic import BaseModel, Field app FastAPI() fake_users { 1: {id: 1, name: 张三, age: 25, email: zhangsantest.com}, } next_id 2 # 请求模型——客户端提交的数据 class UserCreate(BaseModel): name: str Field(..., min_length1, max_length50, description用户名) age: int Field(..., ge0, le150, description年龄) email: str Field(..., description邮箱地址) # 响应模型——返回给客户端的数据 class UserOut(BaseModel): id: int name: str age: int email: str app.post(/users/, response_modelUserOut, status_code201) async def create_user(user: UserCreate): 创建新用户数据通过请求体(body)传递 global next_id # 检查邮箱是否重复 for existing in fake_users.values(): if existing[email] user.email: raise HTTPException(status_code400, detail邮箱已被注册) # 存入数据库 new_user { id: next_id, name: user.name, age: user.age, email: user.email, } fake_users[next_id] new_user next_id 1 return new_user关键点特征说明参数位置请求体body用Pydantic模型接收幂等性不幂等——重复提交会创建多个资源请求体必须有请求体通常JSON格式安全性不安全——会修改服务器数据使用场景创建资源、提交表单、上传文件四、PUT接口整体替换什么时候用PUT完整更新一个资源把旧数据整体替换成新数据创建一个已知ID的资源如果不存在就创建存在就覆盖PUT的核心特征是幂等——对同一个资源用相同的数据调用1次和100次最终状态完全一样。因为PUT是整体替换替换成同样的内容结果不变。PUT vs POST最容易搞混的这对对比维度POSTPUT语义创建新资源替换/更新已有资源幂等性不幂等调两次创建两条幂等调两次结果一样谁决定ID服务器决定服务器分配新ID客户端决定URL中指定ID典型URLPOST /users/PUT /users/{id}完整代码示例from fastapi import FastAPI, HTTPException from pydantic import BaseModel, Field app FastAPI() fake_users { 1: {id: 1, name: 张三, age: 25, email: zhangsantest.com}, 2: {id: 2, name: 李四, age: 30, email: lisitest.com}, } class UserUpdate(BaseModel): PUT请求模型——所有字段必须提供因为是整体替换 name: str Field(..., min_length1, max_length50, description用户名) age: int Field(..., ge0, le150, description年龄) email: str Field(..., description邮箱地址) class UserOut(BaseModel): id: int name: str age: int email: str app.put(/users/{user_id}, response_modelUserOut) async def update_user(user_id: int, user: UserUpdate): PUT整体替换用户信息 客户端必须提供所有字段。 如果某个字段没传PUT会把它覆盖为None或报错。 if user_id not in fake_users: raise HTTPException(status_code404, detail用户不存在) # 整体替换——所有字段都用新值覆盖 fake_users[user_id] { id: user_id, name: user.name, age: user.age, email: user.email, } return fake_users[user_id]PUT的整体替换到底是什么意思假设数据库中有个用户{id: 1, name: 张三, age: 25, email: zhangsantest.com}你只想改名字用PUT发送{name: 张三丰}结果age和email会被覆盖掉因为PUT的语义是整体替换你没提供的字段会被清空。如果你只想改名字应该提供所有字段{name: 张三丰, age: 25, email: zhangsantest.com}补充如果你只想改一个字段、不想传所有字段那应该用PATCH方法局部更新。FastAPI中用app.patch()请求模型中所有字段设为Optional。本文主要讲四种核心方法PATCH道理类似。关键点特征说明参数位置路径参数指定资源 请求体提供新数据幂等性幂等——相同数据多次调用结果一致请求体必须有请求体完整的资源数据核心语义整体替换客户端必须提供所有字段使用场景完整更新资源、Upsert存在则更新不存在则创建五、DELETE接口删东西什么时候用DELETE删除指定资源删除用户、删除文章、删除订单取消订阅、注销账号DELETE的核心特征是幂等——删除同一个资源不管调1次还是100次最终状态都是已删除。完整代码示例from fastapi import FastAPI, HTTPException from pydantic import BaseModel app FastAPI() fake_users { 1: {id: 1, name: 张三, age: 25, email: zhangsantest.com}, 2: {id: 2, name: 李四, age: 30, email: lisitest.com}, } class DeleteResult(BaseModel): success: bool message: str deleted_id: int app.delete(/users/{user_id}, response_modelDeleteResult) async def delete_user(user_id: int): 通过路径参数指定要删除的用户 if user_id not in fake_users: raise HTTPException(status_code404, detail用户不存在) deleted_user fake_users.pop(user_id) return DeleteResult( successTrue, messagef用户 {deleted_user[name]} 已删除, deleted_iduser_id, )关键点特征说明参数位置通常用路径参数/users/{id}指定要删除的资源请求体一般不需要请求体但FastAPI允许DELETE带body幂等性幂等——删除已删除的资源结果还是不存在安全性不安全——会修改服务器数据使用场景删除资源、注销、取消常见疑问删除操作要不要返回数据两种做法都可以返回被删除的资源信息或者只返回一个状态信息如上面的DeleteResult。RESTful规范没有强制要求团队统一即可。六、一张表总结什么时候用什么维度GETPOSTPUTDELETE核心用途查询数据创建数据整体更新数据删除数据对应CRUDRead查Create增Update改Delete删参数位置路径查询参数请求体(body)路径参数请求体路径参数请求体不能有必须有必须有(完整数据)通常不需要幂等性幂等不幂等幂等幂等安全性安全(无副作用)不安全不安全不安全会修改数据不会会会会谁决定ID不涉及服务器决定客户端指定(URL中)客户端指定(URL中)典型URLGET /users/{id}POST /users/PUT /users/{id}DELETE /users/{id}典型状态码200201 Created200200或204 No Content速记口诀GET查不改POST建不幂等PUT全替换DELETE删了算。七、完整实战四个接口串起来下面是一个完整的用户管理接口四种方法全部用到from fastapi import FastAPI, HTTPException, Query from pydantic import BaseModel, Field from typing import Optional app FastAPI(title用户管理API) # 模拟数据库 db { 1: {id: 1, name: 张三, age: 25, email: zhangsantest.com}, } next_id 2 # 数据模型 class UserCreate(BaseModel): POST创建用户的请求模型 name: str Field(..., min_length1, max_length50) age: int Field(..., ge0, le150) email: str Field(..., description邮箱地址) class UserUpdate(BaseModel): PUT更新用户的请求模型——所有字段必须提供 name: str Field(..., min_length1, max_length50) age: int Field(..., ge0, le150) email: str Field(..., description邮箱地址) class UserOut(BaseModel): 对外响应模型 id: int name: str age: int email: str class DeleteResult(BaseModel): 删除操作响应模型 success: bool message: str deleted_id: int # 接口实现 # 1. GET查询用户列表 app.get(/users/, response_modellist[UserOut], summary获取用户列表) async def list_users(skip: int 0, limit: int Query(10, le100)): users list(db.values()) return users[skip : skip limit] # 2. GET查询单个用户 app.get(/users/{user_id}, response_modelUserOut, summary获取用户详情) async def get_user(user_id: int): if user_id not in db: raise HTTPException(status_code404, detail用户不存在) return db[user_id] # 3. POST创建新用户 app.post(/users/, response_modelUserOut, status_code201, summary创建用户) async def create_user(user: UserCreate): global next_id for u in db.values(): if u[email] user.email: raise HTTPException(status_code400, detail邮箱已被注册) new_user { id: next_id, name: user.name, age: user.age, email: user.email, } db[next_id] new_user next_id 1 return new_user # 4. PUT整体更新用户 app.put(/users/{user_id}, response_modelUserOut, summary更新用户) async def update_user(user_id: int, user: UserUpdate): if user_id not in db: raise HTTPException(status_code404, detail用户不存在) db[user_id] { id: user_id, name: user.name, age: user.age, email: user.email, } return db[user_id] # 5. DELETE删除用户 app.delete(/users/{user_id}, response_modelDeleteResult, summary删除用户) async def delete_user(user_id: int): if user_id not in db: raise HTTPException(status_code404, detail用户不存在) deleted db.pop(user_id) return DeleteResult( successTrue, messagef用户 {deleted[name]} 已删除, deleted_iduser_id, )运行方式pip install fastapi uvicorn uvicorn main:app --reload # 打开 http://127.0.0.1:8000/docs 即可在Swagger UI中测试 #或者下载Apifox中调式八、常见踩坑总结坑1用POST做查询有些同学习惯全部用POST觉得方便。但这破坏了HTTP语义导致浏览器无法缓存结果、CDN无法缓存、API语义混乱。查询就用GET创建就用POST别偷懒。坑2搞混PUT和POST最经典的混淆你想更新一个用户却用了POST。POST /users/1 → 语义上是在/users/1下创建新资源不是更新 PUT /users/1 → 语义上是替换/users/1这个资源这才是更新记住URL中有具体ID 要修改数据 → 用PUTURL中无ID 要创建数据 → 用POST。坑3PUT只传了部分字段# 数据库中{id: 1, name: 张三, age: 25, email: zhangsantest.com} # 你只想改名字用PUT只传了name PUT /users/1 {name: 张三丰} # 结果age和email被覆盖为None因为PUT是整体替换没传的字段会被清掉正确做法PUT请求必须携带所有字段。如果只想改一个字段用PATCHapp.patch()。坑4路由顺序写反了# 错误顺序/users/{user_id} 会先匹配到 /users/search app.get(/users/{user_id}) async def get_user(user_id: int): ... app.get(/users/search) async def search_users(): ... # 永远到不了这里 # 正确顺序固定路径放前面 app.get(/users/search) async def search_users(): ... app.get(/users/{user_id}) async def get_user(user_id: int): ...坑5把PUT当成局部更新PUT的语义是整体替换不是局部更新。只想改部分字段应该用PATCHclass UserPatch(BaseModel): PATCH请求模型——所有字段可选 name: Optional[str] None age: Optional[int] None email: Optional[str] None app.patch(/users/{user_id}, response_modelUserOut) async def patch_user(user_id: int, user: UserPatch): PATCH局部更新只改传了的字段 if user_id not in db: raise HTTPException(status_code404, detail用户不存在) stored db[user_id] update_data user.model_dump(exclude_unsetTrue) stored.update(update_data) return stored九、总结回到开头的问题——什么时候用什么接口查数据→ GET参数放URL不修改数据建数据→ POST参数放body服务器分配ID改数据→ PUT参数放URLbody整体替换删数据→ DELETE参数放URL删完就没了记住这个对应关系90%的场景都能覆盖。剩下的特殊情况PATCH局部更新原理类似举一反三即可。FastAPI的设计理念就是用类型注解把一切自动化——你声明好模型验证、过滤、文档全部自动生成。把GET/POST/PUT/DELETE用好API设计就是一件很享受的事。如果这篇文章对你有帮助欢迎点赞收藏有问题可以在评论区交流我会一一回复。