Ruhen Sie sich mit dem verschachtelten Router von Express.j aus

136

Angenommen, ich möchte REST-Endpunkte haben, die ungefähr so ​​aussehen:

/user/
/user/user_id 

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

CRUD auf jedem, wenn es Sinn macht. Wenn beispielsweise / user POST einen neuen Benutzer erstellt, ruft GET alle Benutzer ab. / user / user_id GET ruft nur diesen einen Benutzer ab.

Die Elemente sind benutzerspezifisch, daher habe ich sie unter user_id abgelegt , bei der es sich um einen bestimmten Benutzer handelt.

Um das Express-Routing modular zu gestalten, habe ich einige Router-Instanzen erstellt. Es gibt einen Router für den Benutzer und einen Router für das Element.

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

URL to itemist ein Nachkomme der URL-Hierarchie des user. Wie erhalte ich nun eine URL mit /usersirgendetwas zu userRouter, aber der spezifischeren Route /user/*user_id*/items/zum itemRouter? Außerdem möchte ich, dass user_id nach Möglichkeit auch itemRouter zugänglich ist.

Huggie
quelle
Tolle Antworten zur Verwendung von Express, um dieses Problem zu lösen. Sie können jedoch Loopback (basierend auf Express) verwenden, um eine Swagger-basierte API zu implementieren und Beziehungen zwischen Modellen hinzuzufügen, um die CRUD wie gewünscht auszuführen. Das Schöne ist, dass es nach der ersten Lernkurve viel schneller zusammengebaut werden kann. loopback.io
Mike S.

Antworten:

276

Sie können Router verschachteln, indem Sie sie mit oder ohne Middleware an einen anderen Router anschließen params.

Sie müssen {mergeParams: true}an den untergeordneten Router übergeben, wenn Sie paramsvom übergeordneten Router aus auf den zugreifen möchten .

mergeParamswurde in Express eingeführt4.5.0 (5. Juli 2014)

In diesem Beispiel itemRouterwird das userRouteran die /:userId/itemsRoute angehängt

Dies führt zu folgenden möglichen Routen:

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
quelle
3
Danke für die Antwort. Der hier verwendete Router ist expliziter verschachtelt als der von Jordonias gemeinsam genutzte. Aber funktioniert es auch unter der Haube? Ich möchte Ihnen das Kopfgeld für Vollständigkeit gewähren, aber ich kann es erst einige Stunden später tun.
Huggie
Danke für die Antwort. Gibt es eine ähnliche Möglichkeit, die Abfrageparameter der übergeordneten Route von der untergeordneten Route abzurufen?
Cwarny
1
Es würde mich überraschen, wenn sie auf keiner Route verfügbar sind, da die Abfrageparameter nicht an eine bestimmte Route gebunden sind ...
Willem D'Haeseleer
Sehr gründliche Antwort! Eine Frage: Gibt es aus Gründen der Kapselung und Trennung von Wissen zwischen dem Benutzer-Router und dem Item-Router eine deklarative Möglichkeit, anzugeben, dass ein Subrouter einen Parameter benötigt? Mit anderen Worten, gibt es eine explizite Möglichkeit, die Registrierungs- oder Zugriffsaufrufe so zu schreiben, dass der Artikelrouter uns mitteilt, dass er erwartet, dass eine Benutzer-ID übergeben wird? Beispielsituation: Der Elementrouter befindet sich insgesamt in einer anderen Datei. Strukturell ist nicht klar, dass ein Benutzer erforderlich ist, es sei denn, Sie
rufen an,
Dies ist nicht mehr lesbar als die "Standard" -Verwendung von Routern. Ich suche nach einer Möglichkeit, die Verschachtelung beim Anzeigen des Codes zu visualisieren.
DrewInTheMountains
127

überschaubare verschachtelte Routen ...

Ich wollte ein spezielles Beispiel dafür, wie verschachtelte Routen in Express 4 auf sehr überschaubare Weise ausgeführt werden. Dies war das Top-Suchergebnis für "verschachtelte Routen in Express". Hier ist eine API mit vielen Routen, die beispielsweise aufgelöst werden müssten.

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

Verschachtelungsbeispiel in Ordnerstruktur

Ich habe einige Kommentare zu "Verschachtelungsordnerstruktur" bemerkt. Dies ist jedoch nicht offensichtlich, daher habe ich den folgenden Abschnitt hinzugefügt. Hier ist ein spezielles Beispiel für eine verschachtelte Ordnerstruktur für Routen .

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

Dies ist eher ein allgemeines Beispiel für die Funktionsweise des Knotens. Wenn Sie "index.js" in Ordnern ähnlich wie "index.html" auf Webseiten für einen Verzeichnisstandard verwenden, können Sie Ihre Organisation auf der Grundlage der Rekursion leicht skalieren, ohne Ihre Einstiegspunkte in Code zu ändern. "index.js" ist das Standarddokument, auf das bei Verwendung von require in einem Verzeichnis zugegriffen wird .

Inhalt von index.js

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

Inhalt von /api/index.js

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

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

Inhalt von /api/admin/users/index.js

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

Möglicherweise gibt es hier einige DRY-Probleme, aber es eignet sich gut zur Zusammenfassung von Bedenken.

Zu Ihrer Information, vor kurzem bin ich zu Actionhero gekommen und habe festgestellt, dass es mit Sockets und Aufgaben ausgestattet ist, eher wie ein echtes All-in-One-Framework, das das REST-Paradigma auf den Kopf stellt. Du solltest es dir wahrscheinlich ansehen, wenn du nackt mit Express bist.

Jason Sebring
quelle
11
Ich sehe, wie dies die Routen aufteilt, aber wie wird das Verschachteln angegangen?
1252748
perfekt .... und macht Sinn. Dies ist eine skalierbare Option. Ich wäre gespannt, wie die
Operation
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);

Der Schlüssel zum zweiten Teil Ihrer Frage ist die Verwendung der Option mergeParams

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

Von /user/jordan/item/catbekomme ich eine Antwort:

{"user_id":"jordan","item_id":"cat"}
Jordonias
quelle
Cool. Sowohl deine als auch Willems Methode funktionieren für das, was ich wollte. Ich werde seine auf Vollständigkeit prüfen, aber ich werde Sie auch markieren. Vielen Dank. Ihre Methode sieht nicht verschachtelt aus, aber sie macht so ziemlich das, was ich wollte. Ich glaube, ich bevorzuge sogar Ihre. Vielen Dank.
Huggie
Die Option mergeParams ist hier der Schlüssel!
MrE
2

Verwenden der @ Jason Sebring-Lösung und Anpassen an 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
quelle
Könnten Sie zur Verfügung stellen ./api/routes?
Julian
1
@ Julian: Ich habe Dateispeicherorte festgelegt. ./api/routeshat zwei Dateien index.tsund home.ts. Der erste wird von verwendet server.ts. Hoffe es wird dir helfen.
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
quelle
-9

Sie benötigen nur einen Router und verwenden ihn folgendermaßen:

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
quelle
Ja, aber ich möchte die Logik zwischen den Elementen und den Benutzern trennen. Daher ziehe ich es vor, sie zu trennen. Ich weiß nicht, ob es möglich ist.
Huggie
@huggie itemsgehören nach usersrechts, warum musst du das trennen? Sie können sie in verschiedenen Dateien definieren, die immer noch denselben Router verwenden, wenn Sie dies möchten.
Eguneys
Es gehört dem Benutzer, aber ich möchte es einfach ein- oder ausstecken können, ohne den Benutzer zu beeinträchtigen. Und derzeit habe ich jeden Router für einen anderen URL-Endpunkt. Der Stil scheint vom Express-Generator gefördert zu werden. Wenn es nicht möglich ist, sollte ich die Router-Instanz vielleicht an verschiedene Dateien senden? Dies entspricht jedoch nicht den ursprünglichen Strukturen.
Huggie
Ist es möglich, einen Router unter einem anderen hinzuzufügen? Da die Express-Middleware-Architektur anscheinend von einem darunter liegenden Router verwaltet wird (ich bin mir nicht ganz sicher, ob dies der Fall ist), denke ich, dass dies möglich sein könnte.
Huggie
2
-1 Dies beantwortet nicht die Frage nach verschachtelten Routern
Willem D'Haeseleer