KOA技术分享

专注 Koa.js 框架的编程知识分享

Koa.js RESTful API进阶设计与HATEOAS实践

HATEOAS超媒体控制

HATEOAS(Hypermedia as the Engine of Application State)是 RESTful API 成熟度模型中最高级别的特性。通过在响应中包含相关链接,客户端可以动态发现和导航 API,而无需硬编码 URL。

基础 HATEOAS 实现

// HATEOAS 响应包装器
function createHateoasResponse(data, ctx, relations = {}) {
  const baseUrl = `${ctx.protocol}://${ctx.host}`;

  const hateoas = {
    data,
    _links: {
      self: { href: `${baseUrl}${ctx.path}` },
      ...relations
    }
  };

  // 如果是列表,添加分页链接
  if (data.items && data.total !== undefined) {
    hateoas._links.first = { href: `${baseUrl}${ctx.path}?page=1&size=${data.size}` };
    hateoas._links.last = { href: `${baseUrl}${ctx.path}?page=${data.totalPages}&size=${data.size}` };

    if (data.page > 1) {
      hateoas._links.prev = { href: `${baseUrl}${ctx.path}?page=${data.page - 1}&size=${data.size}` };
    }
    if (data.page < data.totalPages) {
      hateoas._links.next = { href: `${baseUrl}${ctx.path}?page=${data.page + 1}&size=${data.size}` };
    }
  }

  return hateoas;
}

// 使用示例
router.get('/users', async (ctx) => {
  const users = await User.findAll({ page: ctx.query.page, size: ctx.query.size });

  ctx.body = createHateoasResponse(users, ctx, {
    'create-user': { href: `${ctx.origin}/users`, method: 'POST' }
  });
});

router.get('/users/:id', async (ctx) => {
  const user = await User.findById(ctx.params.id);

  ctx.body = createHateoasResponse(user, ctx, {
    'update-user': { href: `${ctx.origin}/users/${user.id}`, method: 'PUT' },
    'delete-user': { href: `${ctx.origin}/users/${user.id}`, method: 'DELETE' },
    'user-orders': { href: `${ctx.origin}/users/${user.id}/orders`, method: 'GET' }
  });
});

响应示例

{
  "data": {
    "id": 1,
    "name": "张三",
    "email": "zhangsan@example.com"
  },
  "_links": {
    "self": { "href": "https://api.example.com/users/1" },
    "update-user": { "href": "https://api.example.com/users/1", "method": "PUT" },
    "delete-user": { "href": "https://api.example.com/users/1", "method": "DELETE" },
    "user-orders": { "href": "https://api.example.com/users/1/orders", "method": "GET" }
  }
}

高级过滤与查询

实现灵活且高效的查询接口,支持复杂的过滤、排序、分页需求。

通用查询构建器

// 查询参数解析中间件
const queryParser = async (ctx, next) => {
  const { page = 1, size = 10, sort, fields, ...filters } = ctx.query;

  // 解析分页
  ctx.queryBuilder = {
    page: parseInt(page),
    size: Math.min(parseInt(size), 100), // 限制最大100条
    offset: (parseInt(page) - 1) * parseInt(size)
  };

  // 解析排序
  if (sort) {
    ctx.queryBuilder.sort = sort.split(',').map(s => {
      const [field, direction] = s.split(':');
      return { field, direction: direction || 'asc' };
    });
  }

  // 解析字段过滤
  if (fields) {
    ctx.queryBuilder.fields = fields.split(',');
  }

  // 解析过滤条件
  ctx.queryBuilder.filters = Object.entries(filters)
    .filter(([key]) => !['page', 'size', 'sort', 'fields'].includes(key))
    .reduce((acc, [key, value]) => {
      // 支持 operators: gt, lt, gte, lte, like, in
      const [field, operator] = key.split('_');
      acc.push({ field, operator: operator || 'eq', value });
      return acc;
    }, []);

  await next();
};

// 应用查询构建器
router.get('/products', queryParser, async (ctx) => {
  const products = await Product.query(ctx.queryBuilder);
  ctx.body = products;
});

使用示例

查询参数 说明 示例
page, size 分页参数 ?page=2&size=20
sort 排序字段 ?sort=created_at:desc,price:asc
fields 返回字段 ?fields=id,name,price
price_gt 大于过滤 ?price_gt=100
name_like 模糊匹配 ?name_like=手机

响应数据结构标准化

统一的响应格式便于前端处理,提高 API 的一致性和可预测性。

统一响应格式

// 统一响应格式中间件
const responseFormatter = async (ctx, next) => {
  // 保存原始body
  await next();

  // 如果已经格式化,直接返回
  if (ctx.body && ctx.body._formatted) return;

  const originalBody = ctx.body;

  // 构建统一响应
  ctx.body = {
    success: true,
    data: originalBody,
    timestamp: Date.now(),
    path: ctx.path,
    method: ctx.method
  };

  ctx.body._formatted = true;
};

响应格式示例

// 成功响应
{
  "success": true,
  "data": { "id": 1, "name": "产品A", "price": 99.00 },
  "timestamp": 1623456789000,
  "path": "/api/products/1",
  "method": "GET"
}

// 列表响应(带分页)
{
  "success": true,
  "data": {
    "items": [...],
    "total": 100,
    "page": 1,
    "size": 20,
    "totalPages": 5
  },
  "timestamp": 1623456789000,
  "path": "/api/products",
  "method": "GET"
}

// 错误响应
{
  "success": false,
  "error": {
    "code": "INVALID_PARAMETER",
    "message": "价格不能为负数"
  },
  "timestamp": 1623456789000,
  "path": "/api/products",
  "method": "POST"
}

API 版本控制策略

RESTful API 需要考虑版本管理,确保向后兼容。

URL 版本控制

// 版本控制路由
const v1Router = new Router({ prefix: '/v1' });
const v2Router = new Router({ prefix: '/v2' });

// v1 路由
v1Router.get('/users', userController.listV1);
v1Router.get('/users/:id', userController.getV1);

// v2 路由 - 添加了新字段和功能
v2Router.get('/users', userController.listV2);
v2Router.get('/users/:id', userController.getV2);
v2Router.get('/users/:id/profile', userController.getProfileV2);

app.use(v1Router.routes());
app.use(v2Router.routes());

总结

HATEOAS、过滤查询、统一响应格式和版本控制是 RESTful API 进阶设计的核心要素。合理运用这些技术可以显著提升 API 的可用性、可维护性和扩展性。

← 下一篇:Koa.js异步编程深入实践与性能优化 上篇:Koa.js服务端渲染框架与首屏优化 →