项目概述
本项目实现基于文档库的 REST API:以严格 schema 校验写操作、用游标分页保证稳定、并以条件更新避免丢失更新。偏移分页是列表端点里的隐性缺陷——翻页时插入一条就会重复或跳漏——游标基于稳定键即可消除这类问题。
架构与关键决策
路由器把端点映射到仓储函数,仓储是唯一接触集合的模块,使访问规则可脱离 HTTP 测试;写入一律经 JSON schema 校验并拒绝未知字段,在边界处校验比事后修复更便宜。读取以排序键加文档 id 构造游标,使页边界不随插入移动;乐观更新以版本或匹配条件为准,基于过期读的写入返回冲突而非覆盖较新值。
实施与测试
每个测试文件使用独立数据库;在页间插入文档断言分页不重不漏;陈旧更新断言返回冲突;非法文档断言被拒;断言仓储投影不会泄漏后加字段。
限制与后续
- 仅单集合 CRUD,不含聚合与跨集合连接。
- 游标分页只支持索引覆盖的排序。
- 无既有文档的迁移工具,schema 变更需另行回填。
代码示例
pnpm init
pnpm add mongodb express ajv
pnpm add -D typescript tsx vitest
mongodb-rest-api/
├── src/router.ts
├── src/repository.ts
├── src/validate.ts
├── src/pagination.ts
├── tests/api.test.ts
└── package.json
export const findByCursor = (db, { after, limit }) =>
db.collection('items').find(after ? { _id: { $gt: after } } : {}).sort({ _id: 1 }).limit(limit + 1).toArray();
const valid = ajv.validate(schema, body);
if (!valid) return res.status(400).json({ errors: ajv.errors });
const next = rows.length > limit ? rows[limit - 1]._id : null;
await db.collection('items').createIndex({ _id: 1, createdAt: -1 });
const updated = await db.collection('items').findOneAndUpdate(
{ _id, version }, { $set: patch, $inc: { version: 1 } }, { returnDocument: 'after' });
if (!updated) return res.status(409).json({ error: 'stale_version' });
pnpm vitest run