
REST API(表述性状态转移应用程序接口)是现代应用与后端服务交互最常见的方式。不管你是做 React 前端、移动应用、SaaS 平台还是内部工具,一个设计良好的 API 就是可靠数据交换的基石。
Node.js 让 REST API 开发变得平易近人,因为你可以全程使用 JavaScript。它的异步运行时、轻量架构和庞大的生态,让它成为构建 API 的实用选择——从简单的 CRUD(增删改查)操作到生产级负载都能 handle。
REST API 把应用功能组织在资源(resource)和标准 HTTP 方法周围。GET 通常用来获取数据,POST 创建资源,PUT 替换整个资源,PATCH 更新资源的部分内容,DELETE 删除资源。好的 API 设计还会在响应中返回有意义的状态码:200 表示成功,201 表示资源创建成功,400 表示输入无效,404 表示资源找不到,500 表示服务器内部错误。
这里我们用纯 Node.js 内置模块写一个任务管理 API。提供列出任务、创建任务、查询单个任务、更新任务和删除任务的接口。不用任何第三方依赖,能更清楚地看到 Express 这类框架底层到底干了什么。
一个有用的生产思维是:即使项目很小也要分离职责。请求解析、路由、验证、业务逻辑、响应格式化应该有清晰的边界。下面的例子虽然为了方便学习放在一个文件里,但已经展示了验证、HTTP 方法、状态码、JSON 响应、错误处理和结构化日志。
const http = require("http");
const { URL } = require("url"); const PORT = 3000; // 演示用的内存数据存储
// 真实生产环境会用 PostgreSQL、MongoDB 或其他数据库
let tasks = [ { id: 1, title: "学习 Node.js REST API", completed: false }, { id: 2, title: "做一个生产级项目", completed: false }
]; let nextId = 3; // 发送一致的 JSON 响应给客户端
function sendJson(res, statusCode, data) { const body = JSON.stringify(data); res.writeHead(statusCode, { "Content-Type": "application/json", "Content-Length": Buffer.byteLength(body) }); res.end(body);
} // 读取并解析 JSON 请求体
function readBody(req) { return new Promise((resolve, reject) => { let body = ""; req.on("data", chunk => { body += chunk.toString(); }); req.on("end", () => { if (!body) return resolve({}); try { resolve(JSON.parse(body)); } catch (error) { reject(new Error("请求体必须包含有效的 JSON")); } }); req.on("error", reject); });
} // 验证 task 资源接受的字段
function validateTask(payload) { if (!payload.title || typeof payload.title !== "string") { return "title 是必填字段,且必须是字符串"; } if (payload.completed !== undefined && typeof payload.completed !== "boolean") { return "completed 必须是布尔值"; } return null;
} const server = http.createServer(async (req, res) => { const requestUrl = new URL(req.url, `http://${req.headers.host}`); const pathname = requestUrl.pathname; const method = req.method; console.log(`\n[REQUEST] ${method} ${pathname}`); try { // GET /tasks - 返回所有任务 if (method === "GET" && pathname === "/tasks") { console.log("[STEP 1] 获取所有任务"); return sendJson(res, 200, { success: true, data: tasks }); } // GET /tasks/:id - 返回单个任务 const taskMatch = pathname.match(/^\/tasks\/(\d+)$/); if (method === "GET" && taskMatch) { const id = Number(taskMatch[1]); console.log(`[STEP 1] 查找 ID 为 ${id} 的任务`); const task = tasks.find(item => item.id === id); if (!task) { console.log("[STEP 2] 任务未找到"); return sendJson(res, 404, { success: false, error: "Task not found" }); } console.log("[STEP 2] 任务查找成功"); return sendJson(res, 200, { success: true, data: task }); } // POST /tasks - 创建新任务 if (method === "POST" && pathname === "/tasks") { console.log("[STEP 1] 读取请求体"); const payload = await readBody(req); console.log("[STEP 2] 验证任务数据"); const validationError = validateTask(payload); if (validationError) { console.log(`[VALIDATION] ${validationError}`); return sendJson(res, 400, { success: false, error: validationError }); } const task = { id: nextId++, title: payload.title.trim(), completed: payload.completed ?? false }; tasks.push(task); console.log(`[STEP 3] 创建任务成功,ID 为 ${task.id}`); return sendJson(res, 201, { success: true, data: task }); } // PATCH /tasks/:id - 更新已有任务 if (method === "PATCH" && taskMatch) { const id = Number(taskMatch[1]); console.log(`[STEP 1] 更新任务 ${id}`); const task = tasks.find(item => item.id === id); if (!task) { return sendJson(res, 404, { success: false, error: "Task not found" }); } const payload = await readBody(req); console.log("[STEP 2] 验证更新数据"); if (payload.title !== undefined && typeof payload.title !== "string") { return sendJson(res, 400, { success: false, error: "title 必须是字符串" }); } if (payload.completed !== undefined && typeof payload.completed !== "boolean") { return sendJson(res, 400, { success: false, error: "completed 必须是布尔值" }); } if (payload.title !== undefined) task.title = payload.title.trim(); if (payload.completed !== undefined) task.completed = payload.completed; console.log("[STEP 3] 任务更新成功"); return sendJson(res, 200, { success: true, data: task }); } // DELETE /tasks/:id - 删除任务 if (method === "DELETE" && taskMatch) { const id = Number(taskMatch[1]); console.log(`[STEP 1] 删除任务 ${id}`); const index = tasks.findIndex(item => item.id === id); if (index === -1) { return sendJson(res, 404, { success: false, error: "Task not found" }); } const deletedTask = tasks.splice(index, 1)[0]; console.log("[STEP 2] 任务删除成功"); return sendJson(res, 200, { success: true, data: deletedTask }); } // 对不支持的路由或方法返回明确的响应 console.log("[ROUTE] 路由未找到"); return sendJson(res, 404, { success: false, error: "Route not found" }); } catch (error) { // 防止意外错误导致服务器崩溃 console.error("[ERROR]", error.message); return sendJson(res, 500, { success: false, error: "Internal server error" }); }
}); server.listen(PORT, () => { console.log("========================================"); console.log(`REST API 运行在 http://localhost:${PORT}`); console.log("可用接口:"); console.log("GET /tasks"); console.log("GET /tasks/:id"); console.log("POST /tasks"); console.log("PATCH /tasks/:id"); console.log("DELETE /tasks/:id"); console.log("========================================");
});
用 Node.js 构建 REST API,核心不是写路由,而是建立客户端和服务器之间可预测的契约。一致的响应格式、有意义的 HTTP 状态码、输入验证、清晰的资源命名、以及防御性错误处理,这些让 API 更易于消费和维护。
内置的 http 模块对理解底层原理很有帮助,但生产环境通常会用 Express 或 Fastify 这类框架来减少样板代码,并获得成熟的中间件生态。随着应用增长,下一步改进应该包括持久化数据库存储、认证、限流、请求验证、集中式错误处理、日志、测试和 API 文档。
重要的一点是:先把这些 HTTP 和 REST 概念学透,理解框架背后的原理。等基础打扎实了,从一个小 Node.js 服务迁移到可扩展的 API 架构就会轻松很多。