Descanse con el enrutador anidado Express.js

136

Supongamos que quiero tener puntos finales REST que se vean más o menos así:

/user/
/user/user_id 

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

CRUDO en cada uno si tiene sentido. Por ejemplo, / user POST crea un nuevo usuario, GET obtiene todos los usuarios. / user / user_id GET obtiene solo ese usuario.

Los elementos son específicos del usuario, por lo que los coloco en user_id , que es un usuario en particular.

Ahora, para hacer que el enrutamiento Express sea modular, hice algunas instancias de enrutador. Hay un enrutador para el usuario y un enrutador para el artículo.

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 a itemes descendientes de la jerarquía de URL de user. Ahora, ¿cómo obtengo URL con /userslo que sea para userRouter pero la ruta más específica de /user/*user_id*/items/itemRouter? Y también, me gustaría que user_id también sea accesible para itemRouter, si es posible.

huggie
fuente
Grandes respuestas ya sobre el uso de Express para resolver esto. Sin embargo, podría usar Loopback (construido en Express) para implementar una API basada en Swagger y agregar relaciones entre modelos para realizar el CRUD como lo solicitó. Lo bueno es que después de la curva de aprendizaje inicial, es mucho más rápido ensamblar. loopback.io
Mike S.

Respuestas:

278

Puede anidar enrutadores al conectarlos como middleware en otro enrutador, con o sin ellos params.

Debe pasar {mergeParams: true}al enrutador secundario si desea acceder al paramsenrutador principal.

mergeParamsse introdujo en Express4.5.0 (5 de julio de 2014)

En este ejemplo, el itemRouterse adjunta a userRouterla /:userId/itemsruta

Esto dará como resultado las siguientes rutas posibles:

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
fuente
3
Gracias por la respuesta. El enrutador que usa aquí está anidado más explícitamente que el compartido por Jordonias. ¿Pero funciona igual debajo del capó? Me gustaría otorgarle la recompensa por la exhaustividad, pero no puedo hacerlo hasta unas horas más tarde.
huggie
Gracias por la respuesta. ¿Hay una manera similar de obtener de la ruta secundaria los parámetros de consulta de la ruta principal?
cauteloso
1
Me sorprendería si no están disponibles en ninguna ruta, ya que el parámetro de consulta no está vinculado a ninguna ruta en específico ...
Willem D'Haeseleer
Respuesta muy completa! Una pregunta: en aras de la encapsulación y la separación del conocimiento entre el enrutador del usuario y el enrutador del elemento, ¿hay alguna forma declarativa de especificar que un subenrutador requiere un parámetro? En otras palabras, ¿hay una manera explícita de escribir el registro o acceder a las llamadas de tal manera que el enrutador del elemento nos permita saber que espera pasar una identificación de usuario? Situación de ejemplo, el enrutador del elemento está en otro archivo completo, estructuralmente no está claro que requiera un usuario a menos que ingrese a sus llamadas y solo está claro en el enrutador de usuario que pasaría una identificación de usuario
yo.ian.g
Esto no es más legible que el uso "estándar" de enrutadores, estoy buscando una forma de visualizar el anidamiento al ver el código.
DrewInTheMountains
127

rutas anidadas manejables ...

Quería un ejemplo específico de hacer rutas anidadas de una manera muy manejable en express 4 y este fue el principal resultado de búsqueda para "rutas anidadas en express". Aquí hay una API que tendría muchas rutas que deberían dividirse, por ejemplo.

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

Ejemplo de anidamiento en estructura de carpetas

Noté algunos comentarios sobre "estructura de carpetas de anidación". Está implícito en esto, sin embargo, no es obvio, así que agregué la sección a continuación. Aquí hay un ejemplo específico de una estructura de carpetas anidadas para rutas .

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

Este es más un ejemplo general de cómo funciona el nodo. Si usa "index.js" en carpetas de forma similar a cómo funciona "index.html" en las páginas web para un directorio predeterminado, será fácil escalar su organización en función de la recursividad sin cambiar sus puntos de entrada al código. "index.js" es el documento predeterminado al que se accede cuando se requiere require en un directorio.

contenido de index.js

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

contenido de /api/index.js

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

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

contenido de /api/admin/users/index.js

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

Posiblemente hay algunos problemas SECOS aquí, pero se presta bien para encapsular las preocupaciones.

Para su información, recientemente me metí en actionhero y he encontrado que tiene todas las funciones con enchufes y tareas, más como un verdadero marco todo en uno que cambia el paradigma REST en su cabeza. Probablemente deberías echarle un vistazo antes de desnudarte con express.

Jason Sebring
fuente
11
Veo cómo esto divide las rutas, pero ¿cómo aborda la anidación?
1252748
perfecto ... y tiene sentido. Esta es una opción escalable. Me gustaría saber cómo el
operador
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 clave para la segunda parte de su pregunta es el uso de la opción mergeParams

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

De /user/jordan/item/catrecibo una respuesta:

{"user_id":"jordan","item_id":"cat"}
Jordonias
fuente
Frio. Tanto el tuyo como el de Willem funcionan para lo que quería. Comprobaré su integridad, pero también te marcaré. Muchas gracias. Su método no parece anidado, pero hace lo que quería, creo que incluso prefiero el suyo. Gracias.
huggie
¡La opción mergeParams es clave aquí!
MrE
2

Usando la solución @Jason Sebring y adaptándome para Typecript.

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
fuente
¿Podría proporcionar ./api/routes?
Julian
1
@Julian: he arreglado las ubicaciones de los archivos. ./api/routestiene dos archivos index.tsy home.ts. El primero es usado por server.ts. Espero que te ayude.
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
fuente
-9

Solo necesita un enrutador y úselo así:

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
fuente
Sí, pero quiero separar las lógicas entre los elementos y los usuarios, por lo que prefiero separarlas. No se si es posible.
Huggie
@huggie itemspertenece a la usersderecha, ¿por qué necesitas separar eso? puede definirlos en diferentes archivos que todavía usan el mismo enrutador si así lo desea.
eguneys
Pertenece al usuario, pero quiero poder conectarlo o desconectarlo fácilmente sin afectar al usuario. Y actualmente tengo cada enrutador para diferentes puntos finales de URL. El estilo parece ser alentado por express-generator. Si no es posible, entonces sí, ¿debería enviar la instancia del enrutador a diferentes archivos? Pero eso no es consistente con las estructuras originales.
Huggie
¿Es posible agregar un enrutador debajo de otro? Dado que la arquitectura de middleware Express parece ser manejada por un enrutador debajo (no estoy completamente seguro de si lo es), creo que podría ser posible.
Huggie
2
-1 Esto no responde a la pregunta sobre los enrutadores anidados
Willem D'Haeseleer