You need to enable JavaScript to run this app. Express搭配Mongoose开发REST API的Schema与路由设计咨询
优惠活动
大模型
产品
解决方案
定价
更多

Express搭配Mongoose开发REST API的Schema与路由设计咨询

方案建议

一、Mongoose Schema 设计选型

你的场景选择反规范化(嵌入)设计更适配,理由如下:

  • 文档仅包含2个字段,数据量极小,嵌入不会导致用户单条文档体积超出合理范围
  • 文档完全从属于用户,没有跨用户查询文档、全局独立查询文档的需求,嵌入后查询用户数据时可以一次性拿到所有关联文档,不需要额外的populate联表操作,性能更好
  • 操作逻辑更简单,增删改文档只需要操作用户对象的documents数组即可,不需要维护两个模型的关联一致性
  • 后续如果需求迭代,文档字段增多、单用户文档量超过数千、或者产生了独立查询文档的需求,再迁移到规范化引用设计的成本也很低

修改后的models/user.js参考代码:

const userSchema = new mongoose.Schema({
    username: {
        type: String,
        required: true,
        unique: true
    },
    password: {
        type: String,
        required: true
    },
    // 新增嵌入的documents数组
    documents: [{
        path: {
            type: String,
            required: true
        },
        dateCreated: {
            type: Date,
            default: Date.now
        }
    }]
});

额外提示:生产环境请不要明文存储password字段,建议使用bcrypt等加密工具对密码做哈希处理后再存储


二、API 路由设计选型

推荐你在现有用户路由下新增嵌套子资源端点,也就是/api/users/:userId/documents格式的路径,理由如下:

  • 符合REST API的语义规范,路径本身就清晰表达了「文档是用户的从属资源」的逻辑,可读性更强
  • 权限校验更方便:用户只能操作自己的文档时,直接拿路由参数中的userId和当前登录用户的ID做对比即可,不需要额外查询文档所属的用户ID
  • 代码维护成本更低,和用户相关的资源逻辑都收拢在users路由下,不需要额外拆分文件,适合你的基础需求场景

如果后续出现了不需要关联用户的文档操作(比如全局检索所有公开文档),再新增独立的/api/documents路由即可,两者并不冲突。

新增到routes/users.js的参考代码:

// 获取指定用户的所有文档
router.get('/:userId/documents', async (req, res, next) => {
  try {
    const user = await User.findById(req.params.userId).select('documents');
    if (!user) return res.status(404).json({ message: '用户不存在' });
    res.json(user.documents);
  } catch (err) {
    next(err);
  }
});

// 获取指定用户的单个文档
router.get('/:userId/documents/:documentId', async (req, res, next) => {
  try {
    const user = await User.findById(req.params.userId);
    if (!user) return res.status(404).json({ message: '用户不存在' });
    const document = user.documents.id(req.params.documentId);
    if (!document) return res.status(404).json({ message: '文档不存在' });
    res.json(document);
  } catch (err) {
    next(err);
  }
});

// 为指定用户创建新文档
router.post('/:userId/documents', async (req, res, next) => {
  try {
    const user = await User.findById(req.params.userId);
    if (!user) return res.status(404).json({ message: '用户不存在' });
    const newDocumentIndex = user.documents.push(req.body);
    await user.save();
    res.status(201).json(user.documents[newDocumentIndex - 1]);
  } catch (err) {
    next(err);
  }
});

// 更新指定用户的单个文档
router.put('/:userId/documents/:documentId', async (req, res, next) => {
  try {
    const user = await User.findById(req.params.userId);
    if (!user) return res.status(404).json({ message: '用户不存在' });
    const document = user.documents.id(req.params.documentId);
    if (!document) return res.status(404).json({ message: '文档不存在' });
    // 仅允许修改path字段,dateCreated不支持更新
    if (req.body.path) document.path = req.body.path;
    await user.save();
    res.json(document);
  } catch (err) {
    next(err);
  }
});

// 删除指定用户的单个文档
router.delete('/:userId/documents/:documentId', async (req, res, next) => {
  try {
    const user = await User.findById(req.params.userId);
    if (!user) return res.status(404).json({ message: '用户不存在' });
    user.documents.pull(req.params.documentId);
    await user.save();
    res.json({ message: '文档删除成功' });
  } catch (err) {
    next(err);
  }
});

内容的提问来源于stack exchange,提问作者M. Çağlar TUFAN

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.21 05:36:10