Hapi嵌套路由

假设我想让REST端点看起来像这样:

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

每个CRUD都是有道理的。 例如,/项目POST创build一个新项目,GET获取所有项目。 / projects / project_id GET只提取一个项目。

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

有没有办法创build这种嵌套的路线?

现在我有这样的东西:

  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 }) 

但是我正在寻找一种方法将物品路线embedded到项目路线中,并且有能力进一步传递项目。

任何build议?

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

首先,hapi在path中提供了通配符variables,使用这些variables可以为给定的path创build一条全path的path。 例如:

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

这些通配符path有一些规则,最重要的是它只能在最后一段,如果你认为它是一个“全部通过”,这是有道理的。

在上面的例子中, {project*}参数将作为request.params.project可用,并将包含被调用path的其余部分,例如GET /projects/some/awesome/thingrequest.params.project设置为some/awesome/project

下一步是处理这个“子path”(你真正的问题),这主要是一个品味和你想如何工作的问题。 你的问题似乎意味着你不想创build一个非常相似的东西无尽的重复列表,但同时能够具有非常具体的项目路线。

一种方法是将request.params.project参数拆分为块,并查找具有匹配名称的文件夹,这些文件夹可能包含进一步处理请求的逻辑。

让我们通过假设一个文件夹结构(相对于包含path的文件,例如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); }); } }); 

像这样的机制可以让你把每个项目作为你的应用程序中的一个节点模块,让你的代码找出合适的模块来在运行时处理path。

您甚至不必为每个请求方法指定此值,因为您可以简单地使用method: ['GET', 'POST', 'PUT', 'DELETE']处理多个方法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 // eg /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'); }; 

我认为这说明了如何在运行时在应用程序中处理单个项目,尽pipe它在构build应用程序体系结构时引发了一些您将要处理的问题:

  • 如果项目处理模块非常相似,则应该创build一个库,用来防止一次又一次复制同一个模块,因为这样可以使维护变得更加容易(我认为,这是子路由的最终目标)
  • 如果你能确定在运行时使用哪个模块,那么当服务器进程启动的时候,你也应该能够弄清楚。

创build一个库来防止重复代码是你应该尽早做(学会做)的事情,因为这样可以使维护变得更容易,而你将来的自我感谢。

弄清楚哪些模块可用来处理应用程序开始时的各种项目,这样可以节省每次请求的次数,从而不得不一次又一次地应用相同的逻辑。 Hapi也许能够为你caching,在这种情况下,这并不重要,但是如果caching不是一个选项,那么使用较less的dynamicpath可能会更好(我相信 – 这是不提供的主要原因默认情况下是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路线,因为这需要明确地完成)

你正在寻找的东西类似于Express的Router 。 事实上,Express在挖掘这个特性的function方面做得很好,所以我会在这里重新发布一个例子:

 // routes/users.js: // Note we are not specifying the '/users' portion of the path here... const router = express.Router(); // index route router.get('/', (req, res) => {... }); // item route router.get('/:id', (req, res) => { ... }); // create route router.post('/', (req,res) => { ... }); // update route router.put('/:id', (req,res) => { ... }); // Note also you should be using router.param to consolidate lookup logic: router.param('id', (req, res, next) => { const id = req.params.id; User.findById(id).then( user => { if ( ! user ) return next(Boom.notFound(`User [${id}] does not exist`)); req.user = user; next(); }).catch(next); }); module.exports = router; 

然后在你的app.js或main routes / index.js中组装你的路线:

 const userRoutes = require('./routes/users') // now we say to mount those routes at /users! Yay DRY! server.use('/users', userRoutes) 

我实际上很失望地发现这个SOpost没有其他答案,所以我会假设没有什么开箱即用(甚至是第三方模块!)来实现这一点。 我想,创build一个使用function组合来消除重复的简单模块可能不会太困难。 由于每个hapi路由defs只是一个对象,所以你可以像下面这样做一个类似的包装(未经testing):

 function mountRoutes(pathPrefix, server, routes) { // for the sake of argument assume routes is an array and each item is // what you'd normally pass to hapi's `server.route routes.forEach( route => { const path = `${pathPrefix}{route.path}`; server.route(Object.assign(routes, {path})); }); } 

编辑在你的情况下,因为你有多层嵌套,类似于Express的router.paramfunction也将是非常有帮助的。 我不熟悉hapi,所以我不知道它是否已经有这个能力。

编辑#2为了更直接地回答原来的问题,这里有一个hapi-route-builder有一个setRootPath()方法,通过让你指定path的基础部分一次就可以实现非常相似的事情。