Odpocznij z zagnieżdżonym routerem Express.js

136

Załóżmy, że chcę mieć punkty końcowe REST, które wyglądają mniej więcej tak:

/user/
/user/user_id 

/user/user_id/items/
/user/user_id/items/item_id

CRUD na każdym, jeśli ma sens. Na przykład / user POST tworzy nowego użytkownika, GET pobiera wszystkich użytkowników. / user / user_id GET pobiera tylko tego jednego użytkownika.

Elementy są specyficzne dla użytkownika, więc umieściłem je pod user_id , który jest konkretnym użytkownikiem.

Aby uczynić routing Express modularnym, stworzyłem kilka instancji routera. Istnieje router dla użytkownika i router dla elementu.

var userRouter = require('express').Router();
userRouter.route('/')
  .get(function() {})
  .post(function() {})
userRouter.route('/:user_id')
  .get(function() {})

var itemRouter = require('express').Router();
itemRouter.route('/')
  .get(function() {})
  .post(function() {})
itemRouter.route('/:item_id')
  .get(function() {})

app.use('/users', userRouter);

// Now how to add the next router?
// app.use('/users/', itemRouter);

Adres URL do itemjest potomkami hierarchii adresów URL pliku user. Teraz, jak mogę uzyskać adres URL z /usersczymkolwiek do userRouter, ale z bardziej szczegółową trasą /user/*user_id*/items/do itemRouter? Chciałbym również, aby user_id był dostępny również dla itemRouter, jeśli to możliwe.

przytulić
źródło
Świetne odpowiedzi już dotyczące używania Express do rozwiązania tego problemu. Możesz jednak użyć Loopback (zbudowanego na Express), aby zaimplementować interfejs API oparty na Swagger i dodać relacje między modelami, aby wykonać CRUD, tak jak prosiłeś. Fajne jest to, że po początkowej krzywej uczenia się jest znacznie szybszy w montażu. loopback.io
Mike S.

Odpowiedzi:

278

Routery można zagnieżdżać, dołączając je jako oprogramowanie pośredniczące na innym routerze, z lub bez params.

Musisz przejść {mergeParams: true}do routera podrzędnego, jeśli chcesz uzyskać dostęp do paramsroutera nadrzędnego.

mergeParamszostał wprowadzony w Express4.5.0 (5 lipca 2014)

W tym przykładzie itemRouterprzywiązuje się do userRouterna /:userId/itemstrasie

Skutkuje to następującymi możliwymi trasami:

GET /user-> hello user
GET /user/5-> hello user 5
GET /user/5/items-> hello items from user 5
GET /user/5/items/6->hello item 6 from user 5

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

var userRouter = express.Router();
// you need to set mergeParams: true on the router,
// if you want to access params from the parent router
var itemRouter = express.Router({mergeParams: true});

// you can nest routers by attaching them as middleware:
userRouter.use('/:userId/items', itemRouter);

userRouter.route('/')
    .get(function (req, res) {
        res.status(200)
            .send('hello users');
    });

userRouter.route('/:userId')
    .get(function (req, res) {
        res.status(200)
            .send('hello user ' + req.params.userId);
    });

itemRouter.route('/')
    .get(function (req, res) {
        res.status(200)
            .send('hello items from user ' + req.params.userId);
    });

itemRouter.route('/:itemId')
    .get(function (req, res) {
        res.status(200)
            .send('hello item ' + req.params.itemId + ' from user ' + req.params.userId);
    });

app.use('/user', userRouter);

app.listen(3003);
Willem D'Haeseleer
źródło
3
Dziękuję za odpowiedź. Router, którego tutaj używasz, jest zagnieżdżony bardziej wyraźnie niż ten współdzielony przez firmę Jordonias. Ale czy działa tak samo pod maską? Chciałbym przyznać Ci nagrodę za kompleksowość, ale nie mogę tego zrobić dopiero kilka godzin później.
huggie
Dziękuję za odpowiedź. Czy istnieje podobny sposób pobierania z trasy podrzędnej parametrów zapytania trasy nadrzędnej?
cwarny
1
Zaskoczyłoby mnie, gdyby nie były dostępne na żadnej trasie, ponieważ parametr zapytania nie jest powiązany z żadną konkretną trasą ...
Willem D'Haeseleer
Bardzo dokładna odpowiedź! Jedno pytanie: ze względu na hermetyzację i oddzielenie wiedzy między routerem użytkownika a routerem przedmiotu, czy istnieje deklaratywny sposób określenia, że ​​router podrzędny wymaga parametru? Innymi słowy, czy istnieje wyraźny sposób na zapisanie wywołań rejestracji lub dostępu w taki sposób, że router pozycji informuje nas, że oczekuje przekazania identyfikatora użytkownika? Przykładowa sytuacja: router item jest w całości w innym pliku, strukturalnie nie jest jasne, że wymaga użytkownika, chyba że dostaniesz się do jego wywołań i tylko w routerze użytkownika jest jasne, że przekaże identyfikator użytkownika
yo.ian.g
Nie jest to bardziej czytelne niż „standardowe” użycie routerów, szukam sposobu na wizualizację zagnieżdżenia podczas przeglądania kodu.
DrewInTheMountains
127

zarządzalne trasy zagnieżdżone ...

Chciałem uzyskać konkretny przykład wykonywania zagnieżdżonych tras w bardzo łatwy w zarządzaniu sposób w ekspresie 4 i był to najlepszy wynik wyszukiwania dla „tras zagnieżdżonych w ekspresie”. Oto interfejs API, który miałby wiele tras, które należałoby na przykład podzielić.

./index.js:

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

// anything beginning with "/api" will go into this
app.use('/api', require('./routes/api'));

app.listen(3000);

./routes/api/index.js:

var router = require('express').Router();

// split up route handling
router.use('/products', require('./products'));
router.use('/categories', require('./categories'));
// etc.

module.exports = router;

./routes/api/products.js:

var router = require('express').Router();

// api/products
router.get('/', function(req, res) {
  res.json({ products: [] });
});

// api/products/:id
router.get('/:id', function(req, res) {
  res.json({ id: req.params.id });
});

module.exports = router;

Przykład zagnieżdżenia w strukturze folderów

Zauważyłem kilka komentarzy na temat „zagnieżdżania struktury folderów”. Jest to zasugerowane, ale nie jest oczywiste, więc dodałem sekcję poniżej. Oto konkretny przykład zagnieżdżonej struktury folderów dla tras .

index.js
/api
  index.js
  /admin
    index.js
    /users
      index.js
      list.js
    /permissions
      index.js
      list.js

To jest bardziej ogólny przykład działania węzła. Jeśli używasz „index.js” w folderach, podobnie jak „index.html” działa na stronach internetowych dla domyślnego katalogu, będzie to łatwe do skalowania organizacji w oparciu o rekursję bez zmiany punktów wejścia na kod. „index.js” jest domyślnym dokumentem, do którego uzyskuje się dostęp podczas korzystania z require w katalogu.

zawartość pliku index.js

const express = require('express');
const router = express.Router();
router.use('/api', require('./api'));
module.exports = router;

zawartość /api/index.js

const express = require('express');
const router = express.Router();
router.use('/admin', require('./admin'));
module.exports = router;

zawartość /api/admin/index.js

const express = require('express');
const router = express.Router();
router.use('/users', require('./users'));
router.use('/permissions', require('./permissions'));
module.exports = router;

zawartość /api/admin/users/index.js

const express = require('express');
const router = express.Router();
router.get('/', require('./list'));
module.exports = router;

Prawdopodobnie jest tu kilka problemów SUCHYCH, ale dobrze nadaje się do hermetyzacji obaw.

FYI, niedawno wkręciłem się w actionhero i odkryłem, że jest to w pełni funkcjonalny z gniazdami i zadaniami, bardziej jak prawdziwy framework all-in-one, który przewraca paradygmat REST na głowę. Prawdopodobnie powinieneś to sprawdzić na nago w / express.

Jason Sebring
źródło
11
Widzę, jak to rozdziela trasy, ale jak rozwiązuje problem zagnieżdżania?
1252748
idealne .... i ma sens. Jest to opcja skalowalna. Byłbym ciekawy, jak
operacja
8
var userRouter = require('express').Router();
var itemRouter = require('express').Router({ mergeParams: true }); 

userRouter.route('/')
  .get(function(req, res) {})
  .post(function(req, res) {})
userRouter.route('/:user_id')
  .get(function() {})

itemRouter.route('/')
  .get(function(req, res) {})
  .post(function(req, res) {})
itemRouter.route('/:item_id')
  .get(function(req, res) {
    return res.send(req.params);
  });

app.use('/user/', userRouter);
app.use('/user/:user_id/item', itemRouter);

Kluczem do drugiej części twojego pytania jest użycie opcji mergeParams

var itemRouter = require('express').Router({ mergeParams: true }); 

Od /user/jordan/item/catkiedy otrzymam odpowiedź:

{"user_id":"jordan","item_id":"cat"}
Jordonias
źródło
Chłodny. Zarówno twoja, jak i metoda Willema działają na to, co chciałem. Sprawdzę, czy jest zrozumiały, ale ciebie też zaznaczę. Wielkie dzięki. Twoja metoda nie wygląda na zagnieżdżoną, ale robi to, co chciałem. Myślę, że nawet wolę twoją. Dzięki.
przytulanie
opcja mergeParams jest tutaj kluczowa!
Pan E Lutego
2

Korzystanie z rozwiązania @Jason Sebring i dostosowywanie do Typescript.

server.ts

import Routes from './api/routes';
app.use('/api/', Routes);

/api/routes/index.ts

import { Router } from 'express';
import HomeRoutes from './home';

const router = Router();

router.use('/', HomeRoutes);
// add other routes...

export default router;

/api/routes/home.ts

import { Request, Response, Router } from 'express';

const router = Router();

router.get('/', (req: Request, res: Response) => {
  res.json({
    message: 'Welcome to API',
  });
});

export default router;
Pierre RA
źródło
Czy możesz to zapewnić ./api/routes?
Julian
1
@Julian: Poprawiłem lokalizacje plików. ./api/routesma dwa pliki index.tsi home.ts. Pierwszy jest używany przez server.ts. Mam nadzieję, że to ci pomoże.
Pierre RA
0
try to add  { mergeParams: true } look to simple example  which it middlware use it in controller file getUser at the same for  postUser
    const userRouter = require("express").Router({ mergeParams: true });
    export default ()=>{
    userRouter
      .route("/")
      .get(getUser)
      .post(postUser);
    userRouter.route("/:user_id").get(function () {});
    
    
    }
Mohammed_Alreai
źródło
-9

Potrzebujesz tylko jednego routera i używaj go w ten sposób:

router.get('/users');
router.get('/users/:user_id');

router.get('/users/:user_id/items');
router.get('/users/:user_id/items/:item_id');

app.use('api/v1', router);
eguneys
źródło
Tak, ale chcę oddzielić logikę między elementami i użytkownikami, więc wolę je jednak rozdzielić. Nie wiem, czy to możliwe.
przytulanie
@huggie itemsnależy do usersright, dlaczego musisz to oddzielać? jeśli chcesz, możesz zdefiniować je w różnych plikach, nadal używając tego samego routera.
eguneys
Należy do użytkownika, ale chcę mieć możliwość łatwego podłączania i odłączania bez wpływu na użytkownika. Obecnie mam każdy router dla różnych punktów końcowych adresu URL. Styl wydaje się być wspierany przez ekspres-generator. Jeśli nie jest to możliwe, to tak, może powinienem wysłać instancję routera do różnych plików? Ale to nie jest zgodne z oryginalnymi strukturami.
przytulić
Czy można dodać jeden router pod drugim? Ponieważ architektura oprogramowania pośredniego Express wydaje się być obsługiwana przez router pod spodem (nie jestem do końca pewien, czy tak jest), myślę, że byłoby to możliwe.
huggie
2
-1 To nie jest odpowiedzią na pytanie, które dotyczy zagnieżdżonych routerów
Willem D'Haeseleer