六维教程

Node.js RESTful API 设计

接口的 URL 和状态码怎么设计,直接决定前后端联调的效率。上一篇 Node.js Express 中间件 把框架机制讲完,这篇讲接口本身的设计规范。RESTful 是目前最主流的接口风格,核心思想是资源导向,URL 表达资源,HTTP 方法表达操作。

资源导向

把一切数据都看成资源,URL 用名词复数表示资源,HTTP 方法表示对资源做什么。

错误写法 正确写法 说明
/getUsers GET /users 查询用方法表达
/deleteUser/7 DELETE /users/7 删除用方法表达
/createUser POST /users 创建用方法表达

动词进 URL 是典型反模式,/getUser、/deleteUser 这类接口会让 URL 列表越写越乱,前端也无法用统一规则推断接口。资源名永远用名词,操作交给方法。

URL 设计

写法 含义
/users 用户资源集合
/users/:id 单个用户资源
/users/:id/posts 嵌套资源,某用户的文章
/users?page=2&size=10 查询参数表达筛选分页

嵌套资源表达从属关系,比如 /users/7/posts 表示用户 7 的文章列表。多层嵌套会难读难维护,一般嵌套一层就够,再深就拆开。单个资源的地址应该稳定,前端把它当作数据的唯一地址使用。

HTTP 方法和操作

方法 操作 幂等
GET 查询资源
POST 创建资源
PUT 整体更新资源
DELETE 删除资源

幂等指同一个请求重复执行多次,结果和第一次相同。GET、PUT、DELETE 都是幂等的,重复请求不会产生额外副作用。POST 不幂等,每发一次就新建一条数据。PUT 是整体更新,用完整数据替换目标资源,只改一个字段的局部更新用 PATCH。

状态码

HTTP 状态码是语义的一部分,选错会让客户端处理逻辑出错。

状态码 含义 使用场景
200 成功 查询、更新、删除成功
201 已创建 POST 创建成功
204 无内容 成功但不需要返回数据
400 参数错误 缺字段、格式不对
401 未认证 没登录或 token 失效
403 无权限 已登录但没有权限
404 资源不存在 路径或资源找不到
500 服务器错误 代码异常

状态码只用标准语义,别自己发明 2xx 表达错误。参数问题给 400,登录相关给 401,权限不足给 403,三者场景完全不同。

统一响应格式

接口返回的数据结构要统一,前端才能写一套解析逻辑。常用结构是 { code, data, message } 三件套。

// response.js,封装统一的响应函数
function success(res, data, message = "ok") {
  res.json({ code: 0, data, message });
}

function fail(res, status, message) {
  res.status(status).json({ code: status, data: null, message });
}

module.exports = { success, fail };

code 是业务码,0 表示成功,非 0 表示失败。message 给前端直接展示用。HTTP 状态码描述传输层面的结果,业务码描述业务层面的结果,两者分开,前端先看 HTTP 状态码,再看 code。

// 使用方式
app.get("/users/:id", (req, res) => {
  const user = users.find((u) => u.id === Number(req.params.id));
  if (!user) {
    return fail(res, 404, "用户不存在");
  }
  success(res, user);
});

常见坑

状态码用错

删除一个不存在的资源返回 500,这是服务器错误码,实际是客户端传了无效 ID,该给 404。拿不准时按这个顺序判断,先看客户端有没有错(400 系列),再看资源在不在(404),最后才是服务器问题(500)。

用 POST 做更新

POST 语义是创建,重复提交会重复创建数据。更新必须用 PUT,删除必须用 DELETE,方法语义和操作保持一致,别混用。

响应格式不统一

有的接口返回 { code, data },有的直接 res.send 字符串,前端每种情况都要单独处理。从第一个接口开始就用统一的 success 和 fail,后面维护会省很多事。

实践

用内存数组实现用户管理的完整 CRUD 接口,统一响应格式一次到位。数据先放内存,重启就丢,数据库篇会把它换成真数据库。

// server.js
const express = require("express");
const { success, fail } = require("./response");

const app = express();
app.use(express.json());

// 内存数据库,重启后数据丢失
let users = [
  { id: 1, name: "张三", age: 20 },
  { id: 2, name: "李四", age: 18 },
];
let nextId = 3;

// 查询用户列表,支持 ?age=18 筛选
app.get("/users", (req, res) => {
  const { age } = req.query;
  const result = age ? users.filter((u) => u.age === Number(age)) : users;
  success(res, result);
});

// 查询单个用户
app.get("/users/:id", (req, res) => {
  const user = users.find((u) => u.id === Number(req.params.id));
  if (!user) {
    return fail(res, 404, "用户不存在");
  }
  success(res, user);
});

// 创建用户
app.post("/users", (req, res) => {
  const { name, age } = req.body;
  if (!name || !age) {
    return fail(res, 400, "name 和 age 必填");
  }
  const user = { id: nextId++, name, age };
  users.push(user);
  success(res, user, "创建成功");
});

// 整体更新用户
app.put("/users/:id", (req, res) => {
  const id = Number(req.params.id);
  const index = users.findIndex((u) => u.id === id);
  if (index === -1) {
    return fail(res, 404, "用户不存在");
  }
  users[index] = { id, ...req.body };
  success(res, users[index], "更新成功");
});

// 删除用户
app.delete("/users/:id", (req, res) => {
  const id = Number(req.params.id);
  const index = users.findIndex((u) => u.id === id);
  if (index === -1) {
    return fail(res, 404, "用户不存在");
  }
  users.splice(index, 1);
  success(res, null, "删除成功");
});

app.listen(3000, () => {
  console.log("服务器已启动");
});
node server.js
# 逐个测试,注意观察状态码和响应结构
curl http://localhost:3000/users
curl http://localhost:3000/users/1
curl -X POST -H "Content-Type: application/json" -d "{\"name\":\"王五\",\"age\":25}" http://localhost:3000/users
curl -X PUT -H "Content-Type: application/json" -d "{\"name\":\"王五\",\"age\":26}" http://localhost:3000/users/3
curl -X DELETE http://localhost:3000/users/2
# 错误场景
curl http://localhost:3000/users/99
curl -X POST -H "Content-Type: application/json" -d "{}" http://localhost:3000/users

接口规范到这里统一了,端口号还是写死的 3000,下一篇 Node.js 环境变量与配置 解决不同环境配置不同的问题。

上一篇
Node.js Express 中间件
下一篇
Node.js 环境变量与配置