You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Node.js Express中实现嵌套递归式API路由功能

Express实现多层嵌套递归路由的方案

核心思路

这类嵌套API路径本质是资源类型/资源标识的重复组合,比如/users/54282/friends/0就是两组资源对:[users, 54282]、[friends, 0],我们只需要把路径拆分后按顺序递归查询资源即可。

方案1:正则路由全匹配+自行解析路径(最简便)

直接用Express的正则路由规则捕获所有嵌套段,手动拆分路径后逐层处理资源,示例代码如下:

const express = require('express');
const app = express();

// 模拟数据,实际替换为自己的数据库查询逻辑
const mockData = {
  users: {
    54282: {
      id: 54282,
      name: '张三',
      friends: [
        {
          id: 97420,
          name: '李四',
          friends: [
            { id: 31230, name: '王五', friends: [] }
          ]
        }
      ]
    }
  }
}

// 逐层处理资源链
async function resolveResourceChain(pairs) {
  let current = null;
  for (const [type, id] of pairs) {
    if (!current) {
      // 第一层资源直接从根查询
      current = mockData[type]?.[id];
    } else {
      // 嵌套资源从上级资源的对应属性查询
      const resourceList = current[type];
      // 支持数字索引或者ID匹配
      current = /^\d+$/.test(id) ? resourceList?.[+id] : resourceList?.find(item => item.id == id);
    }
    if (!current) throw new Error(`资源 ${type}/${id} 不存在`);
  }
  return current;
}

// 匹配所有/api开头的嵌套资源路径
app.get(/^\/api((?:\/\w+\/[^/]+)+)/, async (req, res) => {
  try {
    // 拆分路径得到["users", "54282", "friends", "0", "friends", "0"]格式的数组
    const segments = req.params[0].split('/').filter(Boolean);
    // 两两分组得到资源对
    const resourcePairs = [];
    for (let i = 0; i < segments.length; i += 2) {
      resourcePairs.push([segments[i], segments[i+1]]);
    }
    // 限制最大嵌套层数,避免恶意请求
    if (resourcePairs.length > 6) return res.status(400).json({error: '嵌套层数过多,最多支持6层'});
    
    const result = await resolveResourceChain(resourcePairs);
    res.json(result);
  } catch (e) {
    res.status(404).json({error: e.message});
  }
});

app.listen(3000, () => console.log('服务运行在 http://localhost:3000'));

启动服务后访问GET http://localhost:3000/api/users/54282/friends/0/friends/0就可以直接得到最终的王五用户数据。

方案2:递归挂载子资源路由(更灵活)

如果需要对不同资源做单独的权限校验、逻辑处理,可以给每个资源注册可嵌套的路由:

const express = require('express');
const app = express();
const apiRouter = express.Router();

// 注册资源的处理逻辑和子资源关联
const resourceConfig = {
  users: {
    handler: (id) => mockData.users[id],
    children: ['friends', 'repos']
  },
  friends: {
    handler: (parent, id) => /^\d+$/.test(id) ? parent.friends[+id] : parent.friends.find(f => f.id == id),
    children: ['friends']
  },
  repos: {
    handler: (parent, id) => parent.repos.find(r => r.id == id),
    children: ['collaborators']
  },
  collaborators: {
    handler: (parent, id) => /^\d+$/.test(id) ? parent.collaborators[+id] : parent.collaborators.find(c => c.id == id),
    children: ['repos']
  }
}

// 递归挂载路由
function mountResourceRouter(parentRouter, resourceType, parentResource = null) {
  parentRouter.get(`/${resourceType}/:id/*?`, async (req, res, next) => {
    try {
      const currentResource = resourceConfig[resourceType].handler(parentResource, req.params.id);
      if (!currentResource) return res.status(404).json({error: `${resourceType}不存在`});
      
      // 检查是否还有后续路径,如果有就挂载子资源路由继续处理
      const remainingPath = req.params[0] || '';
      if (remainingPath) {
        const childType = remainingPath.split('/')[0];
        if (resourceConfig[resourceType].children.includes(childType)) {
          const childRouter = express.Router({mergeParams: true});
          mountResourceRouter(childRouter, childType, currentResource);
          return childRouter(req, res, next);
        }
        return res.status(404).json({error: '不支持的子资源'});
      }
      
      // 没有后续路径直接返回当前资源
      res.json(currentResource);
    } catch (e) {
      next(e);
    }
  });
}

// 初始化根资源路由
mountResourceRouter(apiRouter, 'users');
app.use('/api', apiRouter);

注意事项

  • 每一层资源查询都需要加上权限校验,避免未授权用户越权访问嵌套资源
  • 必须限制最大嵌套层数,防止恶意构造超长嵌套路径导致服务性能下降
  • 高频访问的嵌套资源可以增加缓存,降低数据库压力

内容的提问来源于stack exchange,提问作者user16238654

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.02 11:15:03