REST API design 面试题
REST API 设计面试评估你构建可扩展、可维护且用户友好的 Web API 的能力。这些面试常见于后端、全栈和平台工程岗位,尤其是高级职位。面试官评估你对 REST 原则、资源建模、错误处理、分页、版本化和安全性的理解。你将面临概念讨论和动手编码挑战。
REST API design 面试涵盖内容
RESTful 原则与约束
理解无状态、统一接口、资源识别,以及如何正确使用 HTTP 方法(GET、POST、PUT、DELETE、PATCH)和状态码。
资源建模与命名
为嵌套资源、复数名词、过滤、排序设计直观的 URI 模式,并避免在端点中使用动词。
分页、过滤与版本化
实现基于游标与偏移量的分页、过滤/排序策略,以及 URL 与头部版本化方法。
错误处理与安全
设计一致的错误响应结构,正确使用 HTTP 状态码,以及常见安全实践如身份验证、速率限制和输入验证。
REST API design 面试题示例
- 为新闻文章系统设计一个 REST API。你将如何为文章、评论和标签建模资源和端点?好回答应覆盖
- 资源建模清晰,区分文章、评论和标签
- 使用标准HTTP方法和状态码
- 合理设计URL结构,支持嵌套和关联
查看范例答案
为新闻文章系统设计REST API时,首先确定核心资源:文章(articles)、评论(comments)和标签(tags)。每篇文章可以有多个评论和多个标签。建议将评论作为文章的子资源,例如 /articles/{articleId}/comments,这样能明确表达从属关系。标签可以单独管理,同时也支持文章关联标签,例如通过 /articles/{articleId}/tags 获取文章的标签,或通过 /tags/{tagId}/articles 获取标签下的文章。资源使用名词复数形式,如 GET /articles 获取文章列表,POST /articles 创建文章,GET /articles/{id} 获取单篇文章,PATCH /articles/{id} 部分更新,DELETE /articles/{id} 删除。评论类似,GET /articles/{id}/comments 获取评论列表,POST 创建评论等。标签可独立进行CRUD,但需要注意更新文章标签时可能需要替换操作(如 PUT /articles/{id}/tags)。整体设计要考虑分页、过滤和排序,例如GET /articles?page=1&size=20&sort=createdAt,desc。状态码使用200成功,201创建,204删除无内容,400请求错误,404未找到等。需要注意避免过深嵌套,通常不超过两级。
- 给定一个用户 CRUD API,如何实现软删除以及检索活动用户和已删除用户?好回答应覆盖
- 引入 is_deleted 字段标记逻辑删除
- API端点区分活动用户和已删除用户
- 实现软删除时考虑数据完整性和查询性能
查看范例答案
软删除通过标记记录为删除状态而非物理删除实现。在用户表中添加布尔字段如 `is_deleted`(默认false)。删除操作时,将 `is_deleted` 设置为true,并可能记录删除时间。这样原有的GET /users仍返回活动用户(需过滤 `is_deleted=false`),创建新的端点或查询参数来获取已删除用户,例如 GET /users?deleted=true 或 GET /users/deleted。检索活动用户时默认过滤删除标记,检索已删除用户时过滤相反。实现时需注意:自动在查询层添加过滤条件,避免业务代码遗漏;考虑唯一约束,软删除可能导致重复数据,可采用复合唯一索引(如email + is_deleted)或使用全局唯一标识符(如UUID)。另外,软删除会影响关联查询性能,可添加索引。同时,恢复用户时只需将is_deleted设为false。此方法比物理删除更安全,但需要定期清理真正无用的数据以节省存储。
- 如何为大型推文集合设计分页?比较基于偏移量和基于游标的分页,并选择一种。好回答应覆盖
- 基于偏移量分页简单但存在一致性问题
- 基于游标分页更稳定且适合实时数据
- 根据需求选择:实时性高用游标,简单列表可用偏移量
查看范例答案
分页是REST API的常见需求。基于偏移量(offset/limit)分页通过 `offset` 和 `limit` 参数实现,简单直观,但存在两大问题:一是在高并发下,数据插入或删除会导致偏移量偏移,用户可能看到重复或缺失内容;二是在大数据集上,深层分页(如offset=10000)性能下降,因为数据库仍需扫描前面所有行。而基于游标分页使用一个不重复的排序字段(如 `created_at` 或自增ID)作为游标,通过 `cursor` 和 `limit` 参数实现,如GET /tweets?cursor=2023-01-01T00:00:00Z&limit=20。游标分页保证了分页的稳定性:新插入的数据不会影响已有游标位置,性能也更好(可基于索引直接定位)。对于大型推文集合(实时性强、数据量大),推荐使用基于游标的分页。前端实现时,游标通常编码在响应中的 `next_cursor` 字段。缺点是需要客户端维护游标状态,且排序字段必须全局唯一且有序。
- 设计一个允许客户端请求特定字段(稀疏字段集)并包含相关资源(例如带作者详情的文章)的 API。好回答应覆盖
- 使用 fields 参数实现稀疏字段集
- 使用 include 或 expand 参数实现包含关联资源
- 注意性能优化和防止过深嵌套
查看范例答案
客户端请求特定字段可以使用 `fields` 查询参数,如 GET /articles?fields=id,title,content 只返回指定字段。服务器解析该参数后,在数据库查询或序列化时仅加载这些字段,减少数据传输和处理开销。对于包含相关资源(如带作者详情的文章),可使用 `include` 或 `expand` 参数,例如 GET /articles?include=author,comments。服务器需要支持按需加载关联关系,并在响应中将这些资源内联或并行返回。实现时需要注意:限制可包含的关联深度,防止无限递归;允许客户端指定关联资源的子字段,如 `include=author(id,name)`;考虑性能,若关联数据量大可能会引发N+1查询问题,应使用预加载(如SQL中的JOIN或ORM的eager loading)。此外,返回的JSON结构应清晰,例如使用 `data` 和 `included` 列表(类似JSON API规范)或直接嵌套。两种风格各有优劣,嵌套更直观但可能产生冗余数据。
- 如何对 REST API 进行版本化?解释 URI 版本化与自定义请求头部版本化的优缺点。好回答应覆盖
- URI版本化(如/v1/users)直观且易于缓存
- 请求头部版本化更干净但调试复杂
- 选择取决于团队偏好和生态
查看范例答案
REST API版本化常用两种方法:URI版本化和自定义请求头部版本化。URI版本化在URL路径中包含版本号,例如 /v1/users。优点:简单直观,客户端易实现,可同时部署多个版本,便于缓存(不同版本URL不同)。缺点:URL显得杂乱,版本号可能在资源路径中占据层级,长期维护多个版本导致代码分支多。自定义请求头部版本化通过HTTP头部(如 Accept: application/vnd.myapi.v1+json)或自定义头部X-API-Version实现。优点:URL干净,不污染资源标识,便于使用内容协商,版本变更对客户端更透明。缺点:客户端实现复杂(需设置头部),难以在浏览器中直接测试,缓存策略需考虑Vary头部,API文档需额外说明。通常,公共API推荐URI版本化,因为它更易于消费者理解;内部API或移动端API可能更倾向于头部版本化。最终选择没有绝对对错,关键是一致性并做好文档。
- 编写一个简单的 Express.js 中间件,验证 Authorization 头部中的 API 密钥,如果缺失或无效则返回 401。好回答应覆盖
- 中间件函数接收req, res, next
- 验证Authorization头部中的API密钥
- 返回401状态码和JSON错误消息
查看范例答案
以下是Express.js中间件实现,用于验证Authorization头部中的API密钥。该中间件会在路由处理之前执行,从请求头中提取API密钥并与预设的密钥比较(这里简单比较一个固定值,实际可能查询数据库或密钥服务)。如果头部缺失或密钥无效,则返回401状态码和描述错误的JSON。注意:实际生产环境中应使用更安全的比较方式(如恒定时间比较),并考虑将密钥配置在环境变量中。代码示例如下。
- 为支付 API 设计一个错误响应结构。包括验证错误、余额不足和服务器错误的示例。好回答应覆盖
- 统一的错误响应结构包含错误码、消息和详情
- 针对不同错误类型设计不同的错误码和HTTP状态码
- 提供足够的调试信息同时不泄露敏感细节
查看范例答案
设计支付API的错误响应时,应遵循一致的结构。建议包含以下字段:`status`(HTTP状态码)、`error`(简短错误类型)、`message`(人类可读的描述)、`details`(可选,附加信息如验证错误列表)。例如,对于验证错误(如信用卡号无效),使用400状态码,错误码如 "VALIDATION_ERROR",details包含具体字段错误。对于余额不足,使用402 Payment Required状态码(或409 Conflict),错误码 "INSUFFICIENT_BALANCE"。对于服务器内部错误,使用500状态码,错误码 "INTERNAL_SERVER_ERROR"。注意:避免暴露堆栈跟踪等敏感信息。示例响应: - 验证错误:{"status":400,"error":"VALIDATION_ERROR","message":"Invalid card number","details":[{"field":"cardNumber","reason":"must be 16 digits"}]} - 余额不足:{"status":402,"error":"INSUFFICIENT_BALANCE","message":"Your account balance is insufficient to complete this payment"} - 服务器错误:{"status":500,"error":"INTERNAL_SERVER_ERROR","message":"An unexpected error occurred. Please try again later"}
- 如何为公共 API 实现速率限制?描述算法以及如何向客户端传达限制。好回答应覆盖
- 常用令牌桶算法实现速率限制
- 在响应头中传达限制信息
- 考虑分布式环境下的同步问题
查看范例答案
实现公共API的速率限制,推荐使用令牌桶算法。为每个客户端(通常基于IP或API密钥)维护一个桶,桶中有固定数量的令牌,以恒定速率补充。每个请求消耗一个令牌,当桶为空时拒绝请求。实现时,可以在内存中存储桶状态(如使用Redis),并设置时间窗口。另一种常见算法是滑动窗口日志,更精确但内存消耗大。速率限制信息应通过HTTP响应头传达给客户端:`X-RateLimit-Limit`(总限额)、`X-RateLimit-Remaining`(剩余请求数)、`X-RateLimit-Reset`(重置时间戳或秒数)。当超出限制时,返回429 Too Many Requests状态码,并在响应头中包含 `Retry-After` 指示等待时间。在分布式系统中,需要共享状态(如使用Redis或数据库)以避免各节点计数不一致。另外,可考虑使用现成的网关或中间件(如Express-rate-limit)简化实现。
如何准备
- 始终从识别资源及其关系开始,然后将它们映射到使用正确 HTTP 方法的端点。
- 用你喜欢的语言(例如 Node.js/Express、Python/Flask)模拟一个简单的 API,练习 CRUD 操作和错误处理。
- 学习常见的 API 设计模式,如 HATEOAS、PUT/DELETE 的幂等性以及安全/幂等方法。
- 准备讨论权衡:例如,为什么选择特定的分页方法或版本化策略。
- 练习白板面试:清晰解释资源命名、状态码和错误格式的设计决策。
常见问题
REST API 设计中最常见的错误是什么?
在端点中使用动词(例如 /users/getUser 而不是 GET /users/:id)。此外,未使用正确的 HTTP 状态码来指示错误。
我应该总是使用 HATEOAS 吗?
HATEOAS 是完整 REST 遵循的约束,但对于实际的微服务,通常为了简单而省略。关注清晰的资源链接。
如何处理部分更新?
使用 PATCH 配合 JSON Patch 或 Merge Patch 格式。对于部分更新,在请求体中仅包含要更改的字段。
对 API 进行版本化最好的方式是什么?
URI 版本化(例如 /v1/users)明确且易于缓存,但头部版本化保持 URL 整洁。根据团队偏好选择。
如何准备 REST API 设计面试?
使用白板或 Postman 等工具进行模拟面试。重点解释权衡和你的思考过程。
练习 REST API design 题目,即时获取 AI 反馈
上传简历,获得个性化模拟面试,并了解需要改进的地方——免费开始。