用 Node 标准库把 API 立起来
目标
不使用 Nest,只用 Node 标准库构建一个小型订单 API。八个步骤依次涉及路由表、 输入验证、输出结构、依赖注入和事件循环。
为什么这样做
实验 Pod 只开放 DNS,无法执行 npm install。但这个限制反而很适合本课程——
亲自做出原本由框架代替你做的判断,今后阅读 Nest 时,就能看出每个机制
替代了什么工作。
格式
创建 /root/work/api/app.mjs,并 export 以下内容。
handle(req)——req是{method, path, body, headers},返回值是{status, body}。不要打开真实 socket。createApp({store})——在第 6 步创建。
不打开 socket,是为了让评分不受端口冲突或启动时序影响。只有失败确实代表 真实失败,测试才有价值。
搭起服务器骨架
执行 mkdir -p /root/work/api 后,在 app.mjs 中定义 export async function handle(req)。req 是 {method, path, body, headers},返回值是 {status, body}。/healthz 应返回 200 和 {status:'ok'}。
handle('/healthz') 返回 200 和 {status:'ok'}
创建路由表——区分 404 与 405
把路径列表放在数组中,先筛出路径匹配的项,再检查 method。没有路径时返回 404;路径存在但 method 不支持时返回 405。GET /items 返回一个数组。
不存在的路径返回 404;已存在路径上的其他 method 返回 405
验证输入并说明错在哪里
POST /items 接收 {name: string, qty: number}。输入错误时返回 400 和 {errors:[...]},明确写出哪个字段为什么错误;正常时返回 201。
qty 为字符串时返回 400,errors 中含 qty;正常时返回 201
区分存储结构与输出结构
存储中有 secret,但响应中不能有。不要在每个 handler 中分别删除,而要在同一个地方选择允许输出的字段——这就是 allowlist。
GET /items 响应中没有 secret,但包含 id、name 和 qty
不存在的资源应返回 404
实现 GET /items/:id。找不到时返回 404。如果用 200 搭配 {error:...},客户端会把它当作成功——状态码本身就是契约。
不存在的 id 返回 404;存在的 id 返回 200 和对应项
通过注入接收存储
创建 export function createApp({store}),让 handler 只使用传入的 store。直接引用全局变量,就无法在下一步替换它。
createApp({store}) 中注入的存储被实际使用,输出结构也保持不变
不要阻塞事件循环
实现 GET /slow,使它至少耗时 40ms。若用 while 消耗时间,计时器也会一起停住。用 await new Promise(r => setTimeout(r, 50)) 主动让出执行权。
同时处理四个请求期间,5ms 计时器仍能继续运行
不启动服务器也能测试契约
在 app.test.mjs 中编写至少两个可由 node --test 运行的测试。通过 createApp 注入假存储进行测试——不得打开端口。
node --test 至少通过 2 个测试,并且没有启动服务器