javascript - Hapi 嵌套路由

标签 javascript node.js url-routing hapi.js

假设我想拥有大致如下所示的 REST 端点:

/projects/
/projects/project_id 

/projects/project_id/items/
/projects/project_id/items/item_id

每个 if 的 CRUD 都是有意义的。例如,/projects POST 创建一个新项目,GET 获取所有项目。/projects/project_id GET 只获取那个项目。

项目是特定于项目的,所以我将它们放在 project_id 下,这是一个特定的项目。

有没有办法创建这种嵌套路由?

现在我有这样的东西:

  server.route({
    method: 'GET',
    path: '/projects',
    handler: getAllProjects
  });

  server.route({
    method: 'GET',
    path: '/projects/{project_id}',
    handler: getOneProject
  });

  server.route({
    method: 'GET',
    path: '/projects/{project_id}/items/{item_id}',
    handler: getOneItemForProject
  });

  server.route({
    method: 'GET',
    path: '/projects/{project_id}/items',
    handler: getAllItemsForProject
  })

但我正在寻找一种将项目路由嵌套到项目路由中并进一步传递项目的能力。

有什么建议吗?

最佳答案

虽然 hapi 本身没有“子路由”(我知道)的概念,但基本的实现很容易。

首先,hapi 在路径中提供 wildcard 变量,使用这些变量,您基本上可以为给定路径创建一个包罗万象的路径。 例如:

server.route({
  method: 'GET',
  path: '/projects/{project*}',
  handler: (request, reply) => {
    reply('in /projects, re-dispatch ' + request.params.project);
  }
});

这些通配符路径有一些规则,最重要的是它只能在最后一段中,如果您将其视为“包罗万象”,这是有道理的。

在上面的示例中,{project*}参数将作为 request.params.project 提供并将包含被调用路径的其余部分,例如GET /projects/some/awesome/thing将设置 request.params.projectsome/awesome/project .

下一步是处理这个“子路径”(您的实际问题),这主要取决于您的品味和您希望如何工作。 您的问题似乎暗示您不想创建一个无休止的重复列表,其中包含非常相似的事物,但同时能够拥有非常具体的项目路线。

一种方法是拆分 request.params.project参数分成 block 并查找名称匹配的文件夹,其中可能包含进一步处理请求的逻辑。

让我们通过假设一个文件夹结构(相对于包含路由的文件,例如 index.js )来探索这个概念,该结构可以很容易地用于包含特定路由的处理程序。

const fs = require('fs'); // require the built-in fs (filesystem) module

server.route({
    method: 'GET',
    path: '/projects/{project*}',
    handler: (request, reply) => {
        const segment = 'project' in request.params ? request.params.project.split('/') : [];
        const name = segment.length ? segment.shift() : null;

        if (!name) {
            //  given the samples in the question, this should provide a list of all projects,
            //  which would be easily be done with fs.readdir or glob.
            return reply('getAllProjects');
        }

        let projectHandler = [__dirname, 'projects', name, 'index.js'].join('/');

        fs.stat(projectHandler, (error, stat) => {
            if (error) {
                return reply('Not found').code(404);
            }

            if (!stat.isFile()) {
                return reply(projectHandler + ' is not a file..').code(500);
            }

            const module = require(projectHandler);

             module(segment, request, reply);
        });
    }
});

这样的机制将允许您将每个项目作为应用程序中的一个 Node 模块,并让您的代码找出适当的模块以用于在运行时处理路径。

您甚至不必为每个请求方法都指定这个,因为您可以简单地使用 method: ['GET', 'POST', 'PUT', 'DELETE'] 让路由处理多个方法而不是 method: 'GET' .

然而,它并不完全处理如何处理路由的重复声明,因为每个项目都需要一个相当相似的模块设置。

在上面的示例包含和调用“子路由处理程序”的方式中,示例实现将是:

//  <app>/projects/<projectname>/index.js
module.exports = (segments, request, reply) => {
    //  segments contains the remainder of the called project path
    //  e.g. /projects/some/awesome/project
    //       would become ['some', 'awesome', 'project'] inside the hapi route itself
    //       which in turn removes the first part (the project: 'some'), which is were we are now
    //       <app>/projects/some/index.js
    //       leaving the remainder to be ['awesome', 'project']
    //  request and reply are the very same ones the hapi route has received

    const action = segments.length ? segments.shift() : null;
    const item   = segments.length ? segments.shift() : null;

    //  if an action was specified, handle it.
    if (action) {
        //  if an item was specified, handle it.
        if (item) {
            return reply('getOneItemForProject:' + item);
        }

        //  if action is 'items', the reply will become: getAllItemsForProject
        //  given the example, the reply becomes: getAllAwesomeForProject
        return reply('getAll' + action[0].toUpperCase() + action.substring(1) + 'ForProject');
    }

    //  no specific action, so reply with the entire project
    reply('getOneProject');
};

我认为这说明了如何在运行时在您的应用程序中处理单个项目,尽管它确实引起了您在构建应用程序架构时需要处理的几个问题:

  • 如果项目处理模块真的很相似,你应该 创建一个用于防止复制同一模块的库 一遍又一遍,因为这使得维护更容易(其中,我 侦察,是拥有子路由的最终目标)
  • 如果你能确定在运行时使用哪些模块,你应该 也能够在服务器进程启动时弄清楚这一点。

创建一个库以防止重复代码是您应该尽早做(学会做)的事情,因为这使维护更容易,您 future 的自己会感激不尽。

在应用程序开始时确定哪些模块可用于处理您拥有的各种项目,这将使每个请求都不必一遍又一遍地应用相同的逻辑。 Hapi 可能能够为您缓存它,在这种情况下它并不重要,但如果缓存不是一个选项,您可能最好使用较少的动态路径(我相信这是不提供此功能的主要原因默认情况下是 hapi)。

您可以遍历项目文件夹查找所有<project>/index.js在应用程序开始并使用 glob 注册更具体的路由像这样:

const glob = require('glob');

glob('projects/*', (error, projects) => {
    projects.forEach((project) => {
        const name = project.replace('projects/', '');
        const module = require(project);

        server.route({
            method: 'GET',
            path: '/projects/' + name + '/{remainder*}',
            handler: (request, reply) => {
                const segment = 'remainder' in request.params ? request.params.remainder.split('/') : [];

                module(segment, request, reply);
            }
        });
    });
});

这有效地替换了在每个请求上查找模块的上述逻辑并切换到(稍微)更有效的路由,因为您正在计算您将要服务的项目,同时仍然将实际处理留给每个项目模块你提供。 (不要忘记实现 /projects 路由,因为这现在需要明确地完成)

关于javascript - Hapi 嵌套路由,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/29143194/

相关文章:

ruby-on-rails - 如何在索引操作中找到多态嵌套资源的父模型?

javascript - Mapbox GL JS : How to add a canvas, 并从中获取数据? (天气图)

node.js - 在 Node.js 中实现同步和异步

javascript - 无法在 JS 中设置 .color 属性

javascript - V8 javascript : chrome vs node. js - 'this' 上下文中的差异

node.js - Express.js/Node 禁用某些请求日志

php - Symfony 路由 : sf_format default value

Django url 反向错误

javascript - 如何更改 Anychart 点图的颜色。?

javascript - 读取数据对象的单元格内容