Node.js RESTful API

用 Express 写一套规范的 CRUD 接口:状态码、错误格式、分页、版本。

Intermediate
Key Points资源用名词复数做路径,动作用 HTTP 方法表达,状态码表达结果。

资源与方法

Method + PathMeaningSuccess
GET /users列表200
GET /users/1详情200
POST /users创建201 + Location
PUT /users/1整体替换200
PATCH /users/1局部更新200
DELETE /users/1删除204

完整实现

JavaScript
const express = require('express');
const app = express();
app.use(express.json());

let seq = 2;
const users = [
  { id: 1, name: '张三', email: 'zhang@example.com' },
  { id: 2, name: '李四', email: 'li@example.com' },
];

// 列表(带分页)
app.get('/api/v1/users', (req, res) => {
  const page = Math.max(1, Number(req.query.page) || 1);
  const size = Math.min(100, Number(req.query.size) || 10);
  const start = (page - 1) * size;

  res.json({
    data: users.slice(start, start + size),
    page,
    size,
    total: users.length,
  });
});

// 详情
app.get('/api/v1/users/:id', (req, res) => {
  const user = users.find((u) => u.id === Number(req.params.id));
  if (!user) return res.status(404).json({ error: '用户不存在' });
  res.json(user);
});

// 创建
app.post('/api/v1/users', (req, res) => {
  const { name, email } = req.body;
  if (!name || !email) {
    return res.status(422).json({ error: 'name 和 email 必填' });
  }
  const user = { id: ++seq, name, email };
  users.push(user);
  res.status(201).location(`/api/v1/users/${user.id}`).json(user);
});

// 局部更新
app.patch('/api/v1/users/:id', (req, res) => {
  const user = users.find((u) => u.id === Number(req.params.id));
  if (!user) return res.status(404).json({ error: '用户不存在' });
  Object.assign(user, req.body, { id: user.id });
  res.json(user);
});

// 删除
app.delete('/api/v1/users/:id', (req, res) => {
  const i = users.findIndex((u) => u.id === Number(req.params.id));
  if (i === -1) return res.status(404).json({ error: '用户不存在' });
  users.splice(i, 1);
  res.status(204).end();
});

app.listen(3000);

试一试

Bash
curl http://127.0.0.1:3000/api/v1/users?page=1&size=1

curl -X POST http://127.0.0.1:3000/api/v1/users \
  -H "Content-Type: application/json" \
  -d '{"name":"王五","email":"wang@example.com"}'

curl -X DELETE -i http://127.0.0.1:3000/api/v1/users/3
Output
{"data":[{"id":1,"name":"张三","email":"zhang@example.com"}],"page":1,"size":1,"total":2}

HTTP/1.1 201 Created
Location: /api/v1/users/3
Content-Type: application/json; charset=utf-8

{"id":3,"name":"王五","email":"wang@example.com"}

HTTP/1.1 204 No Content

状态码怎么选

CodeWhen
200查询、更新成功且返回内容
201创建成功,配 Location
204成功但没有响应体(删除)
400请求格式错(JSON 解析失败)
401没登录 / token 无效
403登录了但没权限
404资源不存在
409冲突(邮箱已注册)
422格式对但语义错(校验失败)
429限流
Never return 200 with {code: 500}. That blinds every HTTP middleware, monitor, cache and gateway — they only read the status line. Put business codes in the body as a supplement, not a replacement.

分页与筛选

JavaScript
// 偏移分页:简单,但深翻页慢
GET /users?page=100&size=20

// 游标分页:稳定,适合信息流
GET /users?cursor=eyJpZCI6MTk5fQ&size=20

// 筛选与排序
GET /users?status=active&sort=-createdAt&fields=id,name

Friends & Partners