Riposare con il router nidificato Express.js


136

Supponiamo di voler avere endpoint REST che abbiano un aspetto simile a questo:

/user/
/user/user_id 

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

CRUD su ciascuno se ha senso. Ad esempio, / user POST crea un nuovo utente, GET recupera tutti gli utenti. / user / user_id GET recupera solo quell'utente.

Gli elementi sono specifici dell'utente, quindi li inserisco in user_id , che è un utente specifico.

Ora per rendere modulare il routing Express ho creato alcune istanze del router. C'è un router per l'utente e un router per l'articolo.

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

L'URL a itemè il discendente della gerarchia degli URL di user. Ora come posso ottenere l'URL con /usersqualunque cosa a userRouter ma il percorso più specifico di /user/*user_id*/items/itemRouter? E vorrei anche che user_id fosse accessibile anche a itemRouter, se possibile.


Ottime risposte già sull'uso di Express per risolvere questo problema. Tuttavia, è possibile utilizzare Loopback (basato su Express) per implementare un'API basata su Swagger e aggiungere relazioni tra i modelli per eseguire il CRUD come richiesto. La cosa bella è dopo la curva di apprendimento iniziale, è molto più veloce da montare. loopback.io
Mike S.,

Risposte:


278

È possibile nidificare i router collegandoli come middleware su un altro router, con o senza params.

È necessario passare {mergeParams: true}al router figlio se si desidera accedere al paramsrouter principale.

mergeParamsè stato introdotto in Express4.5.0 (5 luglio 2014)

In questo esempio la itemRoutersi attacca alla userRoutersulla /:userId/itemsrotta

Ciò comporterà i seguenti percorsi possibili:

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

3
Grazie per la risposta. Il router che usi qui è nidificato più esplicitamente di quello condiviso da Jordonias. Ma funziona lo stesso sotto il cofano? Vorrei concederti la generosità per la completezza, ma non posso farlo fino a poche ore dopo.
Huggie,

Grazie per la risposta. Esiste un modo simile per ottenere dalla route figlio i parametri di query della route padre?
Cwarny,

1
Mi sorprenderebbe se non fossero disponibili su nessuna rotta, poiché i parametri della query non sono legati a nessuna rotta in modo specifico ...
Willem D'Haeseleer,

Risposta molto approfondita! Una domanda: per motivi di incapsulamento e separazione delle conoscenze tra il router utente e il router articolo esiste un modo dichiarativo per specificare che un subrouter richiede un parametro? In altre parole, esiste un modo esplicito per scrivere la registrazione o accedere alle chiamate in modo tale che il router degli articoli ci faccia sapere che si aspetta che venga passato un ID utente? Esempio di situazione, l'articolo router è completamente in un altro file, strutturalmente non è chiaro che richiede un utente a meno che non si ricevano le sue chiamate ed è chiaro nel router utente che passerebbe un ID utente
yo.ian.g

Questo non è più leggibile dell'uso "standard" dei router, sto cercando un modo per visualizzare l'annidamento quando si visualizza il codice.
DrewInTheMountains

127

percorsi nidificati gestibili ...

Volevo un esempio specifico di fare percorsi nidificati in modo molto gestibile in express 4 e questo era il risultato di ricerca principale per "percorsi nidificati in express". Ecco un'API che avrebbe molte rotte che dovrebbero essere spezzate per esempio.

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

Esempio di nidificazione nella struttura delle cartelle

Ho notato alcuni commenti su "annidamento della struttura delle cartelle". È implicito in questo tuttavia non ovvio, quindi ho aggiunto la sezione seguente. Ecco un esempio specifico di una struttura di cartelle nidificata per le route .

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

Questo è più un esempio generale di come funziona il nodo. Se si utilizza "index.js" in cartelle in modo simile al modo in cui "index.html" funziona nelle pagine Web per impostazione predefinita di una directory, sarà facile ridimensionare l'organizzazione in base alla ricorsione senza modificare i punti di ingresso in codice. "index.js" è il documento predefinito a cui si accede quando si utilizza un requisito in una directory.

contenuto di index.js

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

contenuti di /api/index.js

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

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

contenuti di /api/admin/users/index.js

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

Ci sono alcuni problemi di ASCIUTTA qui forse ma si presta bene all'incapsulamento delle preoccupazioni.

Cordiali saluti, recentemente sono entrato in Actionhero e l'ho trovato pieno di funzionalità con socket e compiti, più come un vero framework all-in-one che lancia il paradigma REST sulla sua testa. Probabilmente dovresti dare un'occhiata su andando nudo con express.


11
Vedo come questo divide i percorsi, ma come risolve la nidificazione?
1252748,

perfetto .... e ha senso. Questa è un'opzione scalabile. Sarei curioso di sapere come l'op implementerà il versioning (v1, v2 ecc.)
Kermit_ice_tea,

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

La chiave per la seconda parte della domanda è l'uso dell'opzione mergeParams

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

Da /user/jordan/item/catottengo una risposta:

{"user_id":"jordan","item_id":"cat"}

Freddo. Sia il tuo che il metodo di Willem funzionano per quello che volevo. Controllerò la sua completezza ma segnerò anche te. Molte grazie. Il tuo metodo non sembra nidificato ma fa praticamente quello che volevo, penso di preferire persino il tuo. Grazie.
Huggie,

l'opzione mergeParams è la chiave qui!
MrE

2

Utilizzo della soluzione @Jason Sebring e adattamento per 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;

Potresti fornire ./api/routes?
Julian,

1
@Julian: ho corretto i percorsi dei file. ./api/routesha due file index.tse home.ts. Il primo è usato da server.ts. Spero che ti possa aiutare.
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 () {});
    
    
    }

-9

È necessario un solo router e utilizzarlo in questo modo:

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

Sì, ma voglio separare le logiche tra gli elementi e gli utenti, e quindi preferisco separarli. Non so se sia possibile.
Huggie,

@Huggie itemsappartiene a usersdestra, perché devi separarlo ? è possibile definirli in file diversi utilizzando comunque lo stesso router, se lo si desidera.
eguneys,

Appartiene all'utente, ma voglio essere in grado di collegarlo o disconnetterlo facilmente senza influire sull'utente. E attualmente ho ciascun router per endpoint URL diversi. Lo stile sembra essere incoraggiato da express-generator. Se non è possibile, sì, forse dovrei inviare l'istanza del router a file diversi? Ma questo non è coerente con le strutture originali.
Huggie,

È possibile aggiungere un router sotto un altro? Poiché l'architettura del middleware Express sembra essere gestita dal router sottostante (non sono del tutto sicuro che lo sia) penso che potrebbe essere possibile.
Huggie,

2
-1 Questo non risponde alla domanda che riguarda i router nidificati
Willem D'Haeseleer
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.