阶段一:RESTful基础
API(Application Programming Interface,应用程序编程接口)是现代软件系统的神经系统。无论是移动应用调用后端服务、微服务之间的通信,还是第三方平台的集成,API都是连接一切的关键纽带。本课将带你从宏观视角理解API设计的核心概念、发展历程和设计哲学。
API是一组定义了软件组件之间如何交互的协议、规程和工具。在Web开发的语境下,API通常指Web API——通过HTTP协议暴露的接口,允许客户端与服务端进行数据交换。
| 要素 | 说明 | 类比 |
|---|---|---|
| 接口协议 | 定义通信规则和数据格式 | 语言语法 |
| 请求方式 | 客户端如何发起调用 | 提问方式 |
| 响应格式 | 服务端如何返回结果 | 回答方式 |
| 认证机制 | 验证调用者身份 | 门禁系统 |
| 错误处理 | 异常情况的标准化响应 | 应急预案 |
整个API应该遵循统一的命名规范、错误格式和响应结构。一致性让开发者只需学习一次,就能预测所有接口的行为。
// ❌ 不一致的命名
GET /api/users/list
GET /api/getUser?id=1
POST /api/user/create
DELETE /api/removeUser
// ✅ 一致的RESTful风格
GET /api/users // 列表
GET /api/users/:id // 详情
POST /api/users // 创建
DELETE /api/users/:id // 删除
开发者应该能根据已有知识推断未使用的接口。URL结构、参数命名、响应格式都应有规律可循。
// 如果开发者知道:
GET /api/users/:id → 获取用户
GET /api/orders/:id → 获取订单
// 他们能自然推断出:
GET /api/products/:id → 获取产品
// 而不需要:
GET /api/fetchProduct?id=xxx // ❌ 不可预测
API的行为应该符合开发者的直觉预期。不要让DELETE请求产生副作用,不要让GET请求修改数据。
新版本不应破坏现有客户端。添加字段是安全的,删除或重命名字段则需要版本管理策略。
以资源为中心而非操作为中心。URL标识资源,HTTP动词表达操作。
| 原则 | 核心思想 | 检验标准 |
|---|---|---|
| 一致性 | 全局统一规范 | 新接口是否遵循已有模式? |
| 可预测性 | 行为可推断 | 不看文档能否猜出接口? |
| 最小惊讶 | 符合直觉预期 | 行为是否出人意料? |
| 向后兼容 | 不破坏旧客户端 | 旧代码能否继续工作? |
| 面向资源 | URL即资源 | URL是否描述"是什么"而非"做什么"? |
让我们从零创建一个简单的API服务,体验API设计的核心概念。
// server.js - 第一个API服务
const http = require('http');
const url = require('url');
// 模拟数据存储
const books = [
{ id: 1, title: 'API设计之道', author: '张三', price: 59.9, isbn: '978-7-001-00001-0' },
{ id: 2, title: 'RESTful实战', author: '李四', price: 49.9, isbn: '978-7-001-00002-8' },
{ id: 3, title: 'GraphQL入门', author: '王五', price: 39.9, isbn: '978-7-001-00003-6' },
];
let nextId = 4;
// 统一响应格式 —— 一致性的体现
function response(res, statusCode, data) {
res.writeHead(statusCode, {
'Content-Type': 'application/json; charset=utf-8',
'X-Request-ID': Date.now().toString(36),
});
res.end(JSON.stringify(data));
}
// 成功响应
function success(res, data, statusCode = 200) {
response(res, statusCode, {
success: true,
data,
timestamp: new Date().toISOString(),
});
}
// 错误响应 —— 标准化错误格式
function error(res, statusCode, code, message) {
response(res, statusCode, {
success: false,
error: { code, message },
timestamp: new Date().toISOString(),
});
}
const server = http.createServer((req, res) => {
const parsedUrl = url.parse(req.url, true);
const pathname = parsedUrl.pathname;
const method = req.method;
// CORS 支持
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
if (method === 'OPTIONS') { res.writeHead(204); res.end(); return; }
// 路由匹配
// GET /api/books - 获取书籍列表
if (method === 'GET' && pathname === '/api/books') {
return success(res, books);
}
// GET /api/books/:id - 获取单本书籍
const match = pathname.match(/^\/api\/books\/(\d+)$/);
if (method === 'GET' && match) {
const id = parseInt(match[1]);
const book = books.find(b => b.id === id);
if (!book) return error(res, 404, 'NOT_FOUND', `书籍 ID ${id} 不存在`);
return success(res, book);
}
// POST /api/books - 创建书籍
if (method === 'POST' && pathname === '/api/books') {
let body = '';
req.on('data', chunk => body += chunk);
req.on('end', () => {
try {
const data = JSON.parse(body);
if (!data.title || !data.author) {
return error(res, 400, 'VALIDATION_ERROR', 'title 和 author 为必填字段');
}
const book = { id: nextId++, ...data, price: data.price || 0 };
books.push(book);
return success(res, book, 201);
} catch (e) {
return error(res, 400, 'INVALID_JSON', '请求体不是有效的JSON');
}
});
return;
}
// PUT /api/books/:id - 更新书籍
if (method === 'PUT' && match) {
// ... 省略更新逻辑
return error(res, 501, 'NOT_IMPLEMENTED', 'PUT 方法暂未实现');
}
// DELETE /api/books/:id - 删除书籍
if (method === 'DELETE' && pathname.match(/^\/api\/books\/(\d+)$/)) {
const id = parseInt(pathname.match(/^\/api\/books\/(\d+)$/)[1]);
const index = books.findIndex(b => b.id === id);
if (index === -1) return error(res, 404, 'NOT_FOUND', `书籍 ID ${id} 不存在`);
books.splice(index, 1);
return success(res, null, 204);
}
// 404 - 路由不存在
error(res, 404, 'NOT_FOUND', `路径 ${pathname} 不存在`);
});
server.listen(3000, () => {
console.log('📚 API服务运行在 http://localhost:3000');
console.log('可用接口:');
console.log(' GET /api/books - 获取书籍列表');
console.log(' GET /api/books/:id - 获取书籍详情');
console.log(' POST /api/books - 创建书籍');
console.log(' DELETE /api/books/:id - 删除书籍');
});
# 安装Node.js并启动服务
node server.js
# 测试1: 获取书籍列表
curl http://localhost:3000/api/books
{
"success": true,
"data": [
{"id":1,"title":"API设计之道","author":"张三","price":59.9,"isbn":"978-7-001-00001-0"},
{"id":2,"title":"RESTful实战","author":"李四","price":49.9,"isbn":"978-7-001-00002-8"},
{"id":3,"title":"GraphQL入门","author":"王五","price":39.9,"isbn":"978-7-001-00003-6"}
],
"timestamp":"2025-01-15T08:30:00.000Z"
}
# 测试2: 创建新书
curl -X POST http://localhost:3000/api/books \
-H "Content-Type: application/json" \
-d '{"title":"gRPC权威指南","author":"赵六","price":69.9}'
{
"success": true,
"data": {"id":4,"title":"gRPC权威指南","author":"赵六","price":69.9},
"timestamp":"2025-01-15T08:31:00.000Z"
}
# 测试3: 请求不存在的资源
curl http://localhost:3000/api/books/999
{
"success": false,
"error": {"code":"NOT_FOUND","message":"书籍 ID 999 不存在"},
"timestamp":"2025-01-15T08:32:00.000Z"
}
Leonard Richardson提出的REST成熟度模型,是评估API设计质量的重要工具:
| 层级 | 名称 | 特征 | 示例 |
|---|---|---|---|
| Level 0 | HTTP隧道 | 只用HTTP做传输,所有操作都是POST | POST /api(action=create) |
| Level 1 | 资源 | 引入资源概念,不同URL代表不同资源 | POST /api/users(action=create) |
| Level 2 | HTTP动词 | 正确使用HTTP方法(GET/POST/PUT/DELETE) | POST /api/users |
| Level 3 | 超媒体控制 | 响应中包含相关操作的链接(HATEOAS) | 响应含 links: {self, next, prev} |
| 工具 | 用途 | 特点 |
|---|---|---|
| OpenAPI/Swagger | API描述规范 | 行业标准,生态完善 |
| Stoplight Studio | 可视化API设计 | 所见即所得,支持Mock |
| Apiary | API文档与协作 | Blueprint语法,Mock服务 |
| Postman | API测试与管理 | 全链路工具,团队协作 |
| 工具 | 用途 | 特点 |
|---|---|---|
| Express/Fastify | Node.js API框架 | 轻量灵活/高性能 |
| FastAPI | Python API框架 | 自动生成OpenAPI文档 |
| json-server | 快速Mock API | 零代码生成REST API |
| Prisma | ORM与数据建模 | 类型安全,自动迁移 |
以下API设计违反了哪些原则?请逐一分析:
POST /api/getUserInfo
POST /api/createNewUser
POST /api/deleteUserById
GET /api/users/list?delete=true
POST /api/user/update
参考答案:
为一个Todo应用设计RESTful API,要求:
参考设计:
# 资源操作
GET /api/todos # 列表(?status=completed 过滤)
GET /api/todos/:id # 详情
POST /api/todos # 创建
PUT /api/todos/:id # 全量更新
PATCH /api/todos/:id # 部分更新
DELETE /api/todos/:id # 删除
# 标准响应
{
"success": true,
"data": { ... },
"meta": { "total": 42, "page": 1 }
}
# 标准错误
{
"success": false,
"error": {
"code": "VALIDATION_ERROR",
"message": "title字段不能为空",
"details": [{ "field": "title", "message": "必填" }]
}
}
启动本课的server.js,完成以下操作:
恭喜!你已经理解了API设计的核心概念和原则。你学会了:
下一课我们将深入HTTP协议与REST架构风格,理解RESTful API的理论根基。