Descansar com o roteador aninhado Express.js

136

Suponha que eu queira ter pontos de extremidade REST parecidos com este:

/user/
/user/user_id 

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

CRUD em cada se faz sentido. Por exemplo, / user POST cria um novo usuário, GET busca todos os usuários. / user / user_id GET busca apenas esse usuário.

Os itens são específicos do usuário, então eu os coloquei em user_id , que é um usuário específico.

Agora, para modular o roteamento expresso, criei algumas instâncias de roteador. Há um roteador para o usuário e um roteador para o item.

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

O URL para itemé descendente da hierarquia do URL do user. Agora, como obtenho um URL /userspara o userRouter, mas a rota mais específica /user/*user_id*/items/para o itemRouter? E também gostaria que o user_id também estivesse acessível ao itemRouter, se possível.

huggie
fonte
Ótimas respostas já sobre o uso do Express para resolver isso. No entanto, você pode usar o Loopback (construído no Express) para implementar uma API baseada no Swagger e adicionar relações entre modelos para executar o CRUD, como você pediu. O bom é que após a curva de aprendizado inicial, é muito mais rápido montar. loopback.io
Mike S.

Respostas:

278

Você pode aninhar roteadores anexando-os como middleware em outro roteador, com ou sem params.

Você deve passar {mergeParams: true}para o roteador filho se quiser acessar o paramsdo roteador pai.

mergeParamsfoi introduzido no Express4.5.0 (5 de julho de 2014)

Neste exemplo, o itemRouteré anexado ao userRouterna /:userId/itemsrota

Isso resultará nas seguintes rotas possíveis:

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
fonte
3
Obrigado pela resposta. O roteador usado aqui é mais explicitamente aninhado que o compartilhado por Jordonias. Mas funciona da mesma forma debaixo do capô? Gostaria de conceder a você a recompensa pela abrangência, mas não posso fazê-lo até poucas horas depois.
huggie
Obrigado pela resposta. Existe uma maneira semelhante de obter da rota filho os parâmetros de consulta da rota pai?
cwarny
1
Surpreenderia-me se eles não estiverem disponíveis em nenhuma rota, já que os parâmetros da consulta não estão vinculados a nenhuma rota em ...
Willem D'Haeseleer
Resposta muito completa! Uma pergunta: para encapsular e separar o conhecimento entre o roteador do usuário e o roteador de itens, existe uma maneira declarativa de especificar que um sub-roteador requer um parâmetro? Em outras palavras, existe uma maneira explícita de gravar as chamadas de registro ou acesso, de modo que o roteador de itens nos avise que espera receber uma identificação de usuário? Exemplo de situação: o roteador de itens está em outro arquivo, estruturalmente, não está claro que exija um usuário, a menos que você
atenda
Isso não é mais legível que o uso "padrão" de roteadores, estou procurando uma maneira de visualizar o aninhamento ao visualizar o código.
DrewInTheMountains
127

rotas aninhadas gerenciáveis ​​...

Eu queria um exemplo específico de fazer rotas aninhadas de uma maneira muito gerenciável no express 4 e este foi o resultado da pesquisa principal de "rotas aninhadas no express". Aqui está uma API que teria muitas rotas que precisariam ser divididas, por exemplo.

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

Exemplo de aninhamento na estrutura de pastas

Notei alguns comentários sobre a "estrutura de pastas aninhadas". Está implícito nisso, porém não é óbvio, então eu adicionei a seção abaixo. Aqui está um exemplo específico de uma estrutura de pastas aninhada para rotas .

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

Este é mais um exemplo geral de como o nó funciona. Se você usar "index.js" em pastas da mesma forma que "index.html" funciona em páginas da Web para um diretório padrão, será fácil dimensionar sua organização com base na recursão sem alterar seus pontos de entrada para código. "index.js" é o documento padrão acessado ao usar require em um diretório.

conteúdo de index.js

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

conteúdo de /api/index.js

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

conteúdo 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;

conteúdo de /api/admin/users/index.js

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

Existem alguns problemas de DRY aqui, possivelmente, mas ele se presta bem ao encapsulamento de preocupações.

Para sua informação, recentemente entrei em ação e descobri que ela possui recursos completos com soquetes e tarefas, mais como uma verdadeira estrutura tudo em um, lançando o paradigma REST de cabeça para baixo. Você provavelmente deveria dar uma olhada em ficar nu com o expresso.

Jason Sebring
fonte
11
Vejo como isso divide as rotas, mas como ele aborda o aninhamento?
1252748
perfeito .... e faz sentido. Esta é uma opção escalável. Eu seria curioso para saber como o op irá implementar controle de versão (v1, v2 etc)
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);

A chave para a segunda parte da sua pergunta é o uso da opção mergeParams

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

De /user/jordan/item/cateu recebo uma resposta:

{"user_id":"jordan","item_id":"cat"}
Jordonias
fonte
Legal. Tanto o seu como o método de Willem funcionam para o que eu queria. Vou verificar a abrangência dele, mas também vou marcar você. Muito obrigado. Seu método não parece aninhado, mas praticamente faz o que eu queria, acho que até prefiro o seu. Obrigado.
huggie
a opção mergeParams é a chave aqui!
MrE 07/02
2

Usando a solução @Jason Sebring e adaptando-se ao 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
fonte
Você poderia fornecer ./api/routes?
Julian
1
@ Julian: Corrigi os locais dos arquivos. ./api/routestem dois arquivos index.tse home.ts. O primeiro é usado por server.ts. Espero que ajude você.
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
fonte
-9

Você precisa de apenas um roteador e use-o assim:

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
fonte
Sim, mas quero separar as lógicas entre os itens e os usuários e, portanto, prefiro separá-los. Não sei se é possível.
precisa saber é
@ huggie itemspertence à usersdireita, por que você precisa separar isso? você pode defini-los em arquivos diferentes, ainda usando o mesmo roteador, se desejar.
precisa saber é
Pertence ao usuário, mas quero poder conectá-lo ou removê-lo facilmente sem afetar o usuário. E atualmente tenho cada roteador para diferentes pontos de extremidade de URL. O estilo parece ser encorajado pelo express-generator. Se não for possível, então sim, talvez eu deva enviar a instância do roteador para arquivos diferentes? Mas isso não é consistente com as estruturas originais.
huggie
É possível adicionar um roteador embaixo de outro? Como a arquitetura do middleware Express parece ser manipulada pelo roteador abaixo (não tenho muita certeza se é), acho que isso pode ser possível.
huggie
2
-1 Esta não está a responder à questão que é de cerca de routers aninhados
Willem D'Haeseleer