2015-03-19 20 views
16

Załóżmy, że chcemy mieć punktów końcowych REST, które wyglądają mniej więcej tak:Hapi zagnieżdżone routingu

/projects/ 
/projects/project_id 

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

CRUD na siebie, jeśli ma sens. Na przykład:/projects POST tworzy nowy projekt, GET pobiera wszystkie projekty./projects/project_id GET pobiera tylko jeden projekt.

Przedmioty są specyficzne dla projektu, więc umieszczam je w project_id, który jest konkretnym projektem.

Czy istnieje sposób na utworzenie tego rodzaju tras zagnieżdżonych?

Teraz mam coś takiego:

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

Ale szukam sposób gniazdo do elementów tras w projekty tras i zdolności do dalszego przekazać projekt.

Jakieś rekomendacje?

+0

Nie bardzo rozumiem, jakie jest twoje pytanie. Twoje poszukiwane trasy zagnieżdżone, a powyższy kod został już dostarczony. Więc gdzie utknąłeś? –

+1

Zastanawiam się, czy istnieje jakikolwiek inny, wygodniejszy sposób, ponieważ w tym podejściu routing na trzeci poziom stanie się ogromnym bałaganem. 'server.route ({ sposób: 'GET', ścieżkę: '/ projekty/{PROJECT_ID}/szt/{item_id}/Wyniki/{result_id}', obsługi: getAllItemsForProject })' Więc im poszukuje rozwiązania podobnego do tego: [link] (http://stackoverflow.com/questions/25260818/rest-with-express-js-nested-router) – PoMaHTuK

+0

Myślę, że problem z zagnieżdżaniem jest taki, że Hapi nie naprawdę ma sposób posiadania wielu programów obsługi na definicję trasy. Nadal musisz jawnie zadeklarować każdą trasę i od tej pory Hapi tak naprawdę nie ma sposobu deklarowania tras podrzędnych. – Osukaa

Odpowiedz

3

To, czego szukasz, to coś podobnego do Express's Router. W rzeczywistości, ekspresowe robi dobrą robotę zakopać przydatność tej funkcji, więc będę ponownie zakładać przykład tutaj:

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

Następnie w swoim app.js lub głównych dróg/index.js gdzie zainstalować trasy:

const userRoutes = require('./routes/users') 

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

jestem naprawdę rozczarowany, aby znaleźć to tak pisać bez innych odpowiedzi więc zakładam, że nie ma nic po wyjęciu z pudełka do osiągnięcia tego celu (lub nawet trzeciej modułu!). Wyobrażam sobie, że może nie być zbyt trudne stworzenie prostego modułu, który wykorzystuje funkcjonalną kompozycję do usuwania duplikatów. Ponieważ każda z tych HAPI DEFS trasa jest tylko obiekt wydaje się, że można zrobić podobną owijkę jak następuje (niesprawdzone):

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

EDIT W twoim przypadku, ponieważ masz wiele warstw zagnieżdżenia, funkcja podobnie jak Express's router.param również byłby bardzo pomocny. Nie jestem strasznie zaznajomiony z hapi, więc nie wiem, czy ma już taką możliwość.

EDIT # 2 Aby bardziej bezpośrednio odpowiedzieć na oryginalne pytanie, o to hapi-route-builder ma setRootPath() metodę, która pozwala osiągnąć coś bardzo podobnego, pozwalając określić część bazową ścieżkę raz.

+2

Tak, pytanie wymaga rozwiązania podobnego do Express zagnieżdżonych tras ... ale dla ** struktury Hapi **! Nie jest konstruktywne proponowanie odpowiedzi w ramach B, jeśli pytanie dotyczy ram odniesienia A - chyba że wyjaśnia również, w jaki sposób można razem stosować A i B. – estus

+0

Uczciwa krytyka. Opublikowalem to w odpowiedzi na komentarz @Matta Harrisona "jak zrobiłbyś to w innym systemie." Myślę, że to wyraźnie wyjaśnia, jak to się robi w Expressie dla porównania. Express i Hapi z pewnością mogą być używane obok siebie (lub trasy pośredniczące Hapi na serwerze Express), ale nie jestem świadomy sposobu bezpośredniego korzystania z tras Express na serwerze Hapi. –

+1

Rozumiem. Nie sądzę, że możliwe jest zintegrowanie Hapi i Express bez dodatkowej pracy, wydaje się, że istniały pewne inicjatywy, aby zapewnić adaptery Hapi, ale przestały działać dawno temu. Dzięki za temat "hapi-route-builder" na temat tematu, jest to bardzo pomocne. – estus

6

Chociaż nie ma pojęcia "podprogramu" (o którym wiem) w samym hapi, podstawy są łatwe do wdrożenia.

Po pierwsze, hapi oferuje wildcard zmienne w ścieżkach, przy użyciu tych w zasadzie utworzyć ścieżkę catch-all dla danej ścieżki. Na przykład:

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

Istnieje kilka zasad do tych wieloznacznych ścieżkami, najważniejsze z nich to może być dopiero w ostatnim segmencie, który ma sens, jeśli myślisz o nim jako „catch-all”.

W powyższym przykładzie parametr {project*} będzie dostępny pod numerem request.params.project i będzie zawierał pozostałą część wywoływanej ścieżki, np.GET /projects/some/awesome/thing ustawi request.params.project na some/awesome/project.

Następnym krokiem jest obsłużenie tej "ścieżki podrzędnej" (twoje aktualne pytanie), która jest głównie kwestią gustu i tego, jak chciałbyś pracować. Twoje pytanie sugeruje, że nie chcesz tworzyć nieskończonej, powtarzającej się listy podobnych rzeczy, ale jednocześnie możesz mieć bardzo konkretne trasy projektu.

Jednym ze sposobów jest podzielenie parametru request.params.project na porcje i poszukiwanie folderów z pasującymi nazwami, które mogą zawierać logikę, aby dalej obsłużyć żądanie.

Przeanalizujmy tę koncepcję, przyjmując strukturę folderów (względem pliku zawierającego trasę, np. index.js), która może być z łatwością wykorzystana do włączenia procedur obsługi określonych tras.

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

Mechanizm taki pozwoliłby masz każdy projekt jako moduł węzła w aplikacji i mieć swój kod wymyślić odpowiedniego modułu używać do obsługi ścieżkę przy starcie.

Nie musisz nawet określać tego dla każdej metody żądania, ponieważ możesz po prostu mieć trasy obsługujące wiele metod za pomocą method: ['GET', 'POST', 'PUT', 'DELETE'] zamiast method: 'GET'.

Nie zajmuje się jednak w pełni powtarzającą się deklaracją obsługi tras, ponieważ do każdego projektu będzie potrzebna dość podobna konfiguracja modułu.

W sposób powyższy przykład zawiera i wywołuje „sub-route-ładowarki”, implementacja próbka będzie:

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

Myślę, że to pokazuje, jak poszczególne projekty mogą być obsługiwane w tobie aplikacji w czasie wykonywania, choć budzi kilka obaw będziesz mieć do czynienia z przy tworzeniu architektury aplikacji:

  • jeśli moduł obsługa projekt naprawdę są bardzo podobne, należy utworzyć bibliotekę którego używasz, aby zapobiec kopiowaniu samo moduł w kółko, ponieważ to ułatwia konserwację (która, I recon, była ostatecznym celem posiadania pod-routingu)
  • jeśli możesz dowiedzieć się, które moduły użyć w czasie wykonywania, powinieneś także również być w stanie aby to zrozumieć, gdy rozpocznie się proces serwera.

Tworzenie biblioteki zapobiegającej powtarzaniu kodu jest czymś, co powinieneś zrobić (nauczyć się robić) na wczesnym etapie, ponieważ ułatwia to konserwację, a twoja przyszłość będzie wdzięczna.

Ustalenie, które moduły będą dostępne do obsługi różnych projektów, które masz na początku aplikacji, będzie zapisywać każde żądanie od konieczności stosowania tej samej logiki w kółko. Hapi może być w stanie buforować to dla ciebie, w takim przypadku tak naprawdę nie ma to znaczenia, ale jeśli buforowanie nie jest opcją, możesz być lepiej używając mniej dynamicznych ścieżek (co - jak sądzę - jest głównym powodem, dla którego nie jest to oferowane domyślnie przez hapi).

można przemierzać projekty folderze szuka wszystkich <project>/index.js na początku stosowania i zarejestrować bardziej konkretną trasę korzystając glob takiego:

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

ten skutecznie zastępuje powyższą logikę patrząc moduł na każdy Żądajcie i przełączcie się na (nieco) bardziej efektywną trasę, ponieważ jesteście hapi dokładnie tym, które projekty będziecie serwować, pozostawiając rzeczywistą obsługę każdemu dostarczonemu modułowi projektu. (Nie zapomnij zaimplementować trasy /projects, ponieważ teraz trzeba to zrobić wyraźnie)

Powiązane problemy