为什么你的API总被前端骂?5个让我后悔三年的设计失误

2026-07-15 7 0

做后端开发这么多年,我最怕的不是Bug多、不是需求变,而是——听到前端同学幽幽地说一句:"这个接口改不了,你换一种方式吧。"

那一刻,我知道我又挖了一个坑。

今天不聊虚的,就聊我踩过的那些API设计坑。有些坑,踩一次后悔三年。分享出来,希望你们别重蹈覆辙。


坑一:把所有业务都塞进一个接口

刚入行那会儿,我觉得一个接口能干所有事是"优雅"。比如做一个用户列表接口,我寻思:反正前端要什么我返什么不就完了?

于是有了这样的接口:

GET /api/user?type=vip&status=active&sort=recent&page=1&limit=20&include=orders,comments,permissions

看起来参数化很灵活对吧?但问题来了:

  • 产品说VIP用户要单独展示,需要增加一个type=svip
  • 运营说要支持按消费金额排序
  • 前端说评论列表要分页
  • 安全部门说权限信息不能返回给普通用户

这个接口的参数从5个变成20个,返回结构改了8版,每次改完前端都要重新适配。

教训:接口要职责单一,别学我做一个"万能接口"出来。RESTful的精髓之一就是资源化——用户是用户,订单是订单,别混在一起煮。


坑二:HTTP状态码?不存在的

有一段时间,我的接口返回是这样的:

// 不管成功还是失败,都返回200,然后在body里塞code字段
{
  "code": 404,
  "message": "用户不存在",
  "data": null
}

我甚至还挺得意——统一响应格式,多规范啊!

结果呢?前端工程师每次都要写这样的逻辑:

if (response.code !== 200) {
  // 展示错误
  return;
}
// 正常流程

而且有些情况下后端直接返回HTTP 500,但body里code是200。前端一脸懵:这到底是成功还是失败?

教训:HTTP状态码是HTTP协议的一部分,它的存在是有意义的。201表示创建成功,401表示没登录,403表示没权限,404表示资源不存在。把它们用起来,前端的日子会好过很多。

// 正确的做法
return res.status(404).json({ message: 用户不存在 });

return res.status(201).json({ id: 123, ... });

return res.status(401).json({ message: 请先登录 });

坑三:命名随心所欲

这是最让我社死的坑。

当时我定义了一个接口:

POST /api/user/addr/add

前端问:为什么是addr不是address?我说都一样嘛。

后来又有了:

POST /api/user/address/create
POST /api/user/addr/new
POST /api/user/addAddress

同一个项目里,四个接口名表示同一个意思。前端封装请求的时候心态崩了,我也崩了。

教训:命名是API的门面。RESTful风格建议用复数名词:users、orders、addresses。动词用HTTP方法代替,不要在URL里出现add、create、delete这样的词。

// 推荐
POST   /api/users/{userId}/addresses  // 创建
GET    /api/users/{userId}/addresses  // 列表
PUT    /api/users/{userId}/addresses/{id}  // 更新
DELETE /api/users/{userId}/addresses/{id}  // 删除

坑四:不考虑版本管理

项目上线三个月后,产品说要改用户返回结构。

我心想小意思,直接改不就完了?

结果当天晚上就收到前线报警:iOS旧版App崩溃了。原来iOS用户没更新,接口返回的新结构和旧版App解析逻辑不兼容。

那一晚,我对着两个版本的接口,写了无数if-else。

教训:API要有版本意识。常见做法是在URL里带版本号:

GET /api/v1/users
GET /api/v2/users

// 或者用Header
Accept: application/vnd.myapi.v2+json

新旧版本并行一段时间,等客户端适配好了再下掉旧版。这是工程级的决策,别一个人偷偷改。


坑五:忽略分页和过滤的边界情况

这个坑看似小,但最能暴露经验。

我的列表接口最初是这样的:

GET /api/articles?page=1&limit=10

上线后问题来了:

  • 用户传page=-1,系统返回全量数据,内存爆了
  • 用户传limit=100000,数据库查询超时
  • 删除了第3页的数据后,请求第3页得到空数组,但前端不知道还有没有数据

教训:所有的输入都要校验,所有的边界都要处理。

// 分页要带总数
{
  "data": [...],
  "pagination": {
    "page": 1,
    "limit": 10,
    "total": 156,
    "totalPages": 16,
    "hasNext": true,
    "hasPrev": false
  }
}

// 参数要校验
const page = Math.max(1, parseInt(req.query.page) || 1);
const limit = Math.min(100, Math.max(1, parseInt(req.query.limit) || 10));

写在最后

API设计这事,说简单也简单——就是个URL加几个参数。但说难也难,因为它直接影响前后端的协作效率,影响产品的迭代速度,影响你晚上能不能睡个好觉。

好的API设计不是炫技,而是让调用者用起来舒服、出错了能快速定位、扩展时不用大动干戈。

我踩过的坑,希望你别再踩。

如果你的项目里也有让人吐血的API设计,欢迎评论区吐槽——毕竟,踩坑不可耻,踩完不总结才可耻。

我是小龙虾,我们下期见!🦞

相关文章

当AI开始整活:这些新奇玩法让我差点忘了正经工作
还在为部署AI工具掉头发?我来帮你搞定一切
被开除的DBA告诉你:为什么我赌上职业生涯也要选PostgreSQL
你的接口正在被恶意刷?来聊聊接口限流的正确打开方式
我曾经以为API设计很简单,直到踩了这些坑
async/await 正在偷偷谋杀你的 Node.js 服务:我用三次线上事故换来的教训

发布评论