Hapi nested routing

Supposons que je souhaite avoir des points de terminaison REST qui ressemblent à ceci:

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

CRUD sur chacun d'entre eux a du sens. Par exemple, les projets POST créent un nouveau projet, GET récupère tous les projets. / Projects / project_id GET récupère uniquement ce projet.

Les éléments sont spécifiques au projet, donc je les place sous project_id, qui est un projet particulier.

Existe-t-il un moyen de créer ce type d'itinéraires imbriqués?

À l'heure actuelle, j'ai quelque chose comme ça:

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

Mais je suis à la recherche d'un moyen de jeter des objets dans les itinéraires des projets et de pouvoir réussir le projet.

Des recommandations?

Bien qu'il n'y ait pas de concept de "sous-programme" (que je sais) dans l'hapi lui-même, les bases sont assez faciles à mettre en œuvre.

Tout d'abord, hapi offre des variables génériques dans les chemins, en utilisant ces derniers, vous créez fondamentalement un itinéraire attrayant pour un chemin donné. Par exemple:

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

Il existe certaines règles sur ces trajets génériques, le plus important étant qu'il ne peut être que dans le dernier segment, ce qui est logique si vous le considérez comme un «catch-all».

Dans l'exemple ci-dessus, le paramètre {project*} sera disponible sous request.params.project et contiendra le reste du chemin appelé, par exemple GET /projects/some/awesome/thing mettra request.params.project dans some/awesome/project .

La prochaine étape consiste à gérer ce "sous-chemin" (votre question réelle), qui est avant tout une question de goût et comment vous souhaitez travailler. Votre question semble impliquer que vous ne voulez pas créer une liste répétitive sans fin de choses similaires, mais en même temps pour pouvoir avoir des itinéraires de projets très spécifiques.

L'une des façons serait de diviser le paramètre request.params.project en morceaux et de chercher des dossiers avec des noms correspondants, ce qui pourrait contenir une logique pour traiter davantage la requête.

Examinons ce concept en supposant une structure de dossier (par rapport au fichier contenant l'itinéraire, par exemple index.js ) qui peut être facilement utilisé pour inclure les gestionnaires pour les routes spécifiques.

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

Un mécanisme comme celui-ci vous permettrait d'avoir chaque projet en tant que module de noeud dans votre application et que votre code figure sur le module approprié à utiliser pour gérer le chemin d'exécution.

Vous ne devez même pas spécifier cela pour chaque méthode de demande, car vous pouvez simplement utiliser les routes pour gérer plusieurs méthodes en utilisant la method: ['GET', 'POST', 'PUT', 'DELETE'] au lieu de method: 'GET' .

Cependant, il ne traite pas entièrement de la déclaration répétitive de la façon de gérer les itinéraires, car vous aurez besoin d'une configuration de module assez similaire pour chaque projet.

Dans la manière dont l'exemple ci-dessus inclut et invoque les "gestionnaires de sous-route", un exemple de mise en œuvre serait:

 // <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'); }; 

Je pense que cela illustre comment les projets individuels peuvent être gérés dans votre application au moment de l'exécution, même si cela soulève quelques préoccupations dont vous voudrez aborder lors de la construction de votre architecture d'application:

  • Si le module de gestion de projet est vraiment très similaire, vous devez créer une bibliothèque que vous utilisez pour éviter de copier le même module à plusieurs reprises, ce qui rend la maintenance plus facile (ce qui, je reconnais, était le but ultime d'avoir un sous-routage)
  • Si vous pouvez déterminer les modules à utiliser au moment de l'exécution, vous devriez également pouvoir le comprendre lorsque le processus du serveur démarre.

La création d'une bibliothèque pour empêcher le code répétitif est quelque chose que vous devriez faire (apprendre à faire) dès le début, car cela rend la maintenance plus facile et votre avenir sera reconnaissant.

Le fait de déterminer quels modules seront disponibles pour gérer les différents projets que vous avez au début de l'application sauvera toutes les demandes d'avoir à appliquer la même logique encore et encore. Hapi peut être en mesure de mettre en cache ce pour vous, auquel cas cela n'a pas d'importance, mais si la mise en cache n'est pas une option, vous feriez mieux d'utiliser des chemins moins dynamiques (ce qui, je crois, est la principale raison pour laquelle cela n'est pas offert Par hapi par défaut).

Vous pouvez traverser le dossier des projets à la recherche de tous les <project>/index.js au début de l'application et enregistrer un itinéraire plus spécifique à l'aide de glob comme ceci:

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

Cela remplace efficacement la logique ci-dessus du module de recherche sur chaque demande et passe à un routage (légèrement) plus efficace car vous lisez hapi exactement quels projets vous servirons tout en laissant la gestion réelle à chaque projet-module que vous fournissez. (N'oubliez pas de mettre en œuvre la route /projects , car cela doit maintenant être fait explicitement)

Ce que vous recherchez, c'est quelque chose de similaire au Router Express . En fait, Express fait un bon travail pour enterrer l'utilité de cette fonctionnalité, alors je vais re-post un exemple ici:

 // 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; 

Ensuite, dans votre application.js ou les principales routes / index.js où vous assemblez vos itinéraires:

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

Je suis vraiment déçu de trouver cette publication de SO sans d'autres réponses, donc je suppose que rien de la boîte (ou même un module tiers!) Pour ce faire. J'imagine qu'il pourrait ne pas être trop difficile de créer un module simple qui utilise la composition fonctionnelle pour supprimer la duplication. Étant donné que chacun de ces défauts de route hapi n'est qu'un objet, il semble que vous pourriez créer un emballage similaire à celui qui suit (non testé):

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

ÉDITER Dans votre cas, car vous avez plusieurs couches de nidification, une fonctionnalité similaire à celle du router.param d'Express router.param serait également extrêmement utile. Je ne suis pas très familier avec hapi, donc je ne sais pas si elle a déjà cette capacité.

EDIT # 2 Pour répondre plus directement à la question initiale, voici un hapi-route-builder a une méthode setRootPath() qui vous permet de réaliser quelque chose de très similaire en vous setRootPath() une setRootPath() la partie de base du chemin.