Отдых с вложенным роутером Express.js

136

Предположим, я хочу иметь конечные точки REST, которые выглядят примерно так:

/user/
/user/user_id 

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

CRUD на каждый, если имеет смысл. Например, / user POST создает нового пользователя, GET выбирает всех пользователей. / user / user_id GET выбирает только этого одного пользователя.

Элементы зависят от пользователя, поэтому я помещаю их в user_id , который является конкретным пользователем.

Теперь, чтобы сделать модульную маршрутизацию Express, я сделал несколько экземпляров маршрутизатора. Существует маршрутизатор для пользователя и маршрутизатор для элемента.

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 itemявляется потомком иерархии URL user. Теперь, как мне получить URL с /usersuserRouter, но с более конкретным маршрутом /user/*user_id*/items/к itemRouter? А также, я бы хотел, чтобы user_id был доступен для itemRouter, если это возможно.

Huggie
источник
Хорошие ответы уже на использовании Express, чтобы решить эту проблему. Однако вы можете использовать Loopback (построенный на Express) для реализации API на основе Swagger и добавлять отношения между моделями для выполнения CRUD, как вы и просили. Приятно то, что после начальной кривой обучения, сборка происходит намного быстрее. loopback.io
Майк С.

Ответы:

278

Вы можете вкладывать маршрутизаторы, подключая их в качестве промежуточного ПО на другом маршрутизаторе, с или без params.

Вы должны перейти {mergeParams: true}к дочернему маршрутизатору, если хотите получить доступ paramsот родительского маршрутизатора.

mergeParamsбыл представлен в Экспресс4.5.0 (5 июля 2014)

В этом примере itemRouterпривязывается к userRouterна /:userId/itemsмаршруте

Это приведет к следующим возможным маршрутам:

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
Спасибо за ответ. Маршрутизатор, который вы здесь используете, более явно вложен, чем тот, который используется Jordonias. Но работает ли он так же под капотом? Я хотел бы предоставить вам награду за всесторонность, но я не могу сделать это до нескольких часов спустя.
Хагги
Спасибо за ответ. Есть ли аналогичный способ получить от дочернего маршрута параметры запроса родительского маршрута?
cwarny
1
Меня удивило бы, если бы они не были доступны ни на одном маршруте, поскольку параметры запроса не привязаны ни к какому конкретному маршруту ...
Виллем Д'Азелер
Очень подробный ответ! Один вопрос: ради инкапсуляции и разделения знаний между пользовательским маршрутизатором и маршрутизатором элемента существует ли декларативный способ указать, что подчиненному маршрутизатору требуется параметр? Другими словами, существует ли явный способ записать регистрацию или вызовы доступа, чтобы маршрутизатор элементов позволил нам узнать, что ожидается, что ему будет передан идентификатор пользователя? Пример ситуации, когда маршрутизатор элемента находится в другом файле, структурно не ясно, что он требует пользователя, если вы не входите в его вызовы, и только в маршрутизаторе пользователя ясно, что он передаст идентификатор пользователя
yo.ian.g
Это не более разборчиво, чем «стандартное» использование маршрутизаторов, я ищу способ визуализации вложенности при просмотре кода.
DrewInTheMountains
127

управляемые вложенные маршруты ...

Я хотел конкретный пример выполнения вложенных маршрутов в Express 4 очень управляемым способом, и это был лучший результат поиска для «вложенных маршрутов в экспрессе». Вот API, который будет иметь много маршрутов, которые необходимо разбить, например.

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

Пример вложенности в структуру папок

Я заметил несколько комментариев о "структуре вложенных папок". Это подразумевается в этом, однако не очевидно, поэтому я добавил раздел ниже. Вот конкретный пример структуры вложенных папок для маршрутов .

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

Это более общий пример того, как работает узел. Если вы используете «index.js» в папках аналогично тому, как «index.html» работает на веб-страницах для каталога по умолчанию, будет легко масштабировать вашу организацию на основе рекурсии, не меняя точки входа на код. index.js - это документ по умолчанию, доступ к которому осуществляется при использовании require в каталоге.

содержимое index.js

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

содержимое /api/index.js

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

содержимое /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;

содержимое /api/admin/users/index.js

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

Возможно, здесь есть некоторые проблемы с СУХОЙ, но это хорошо поддается инкапсуляции проблем.

К вашему сведению, недавно я попал в actionhero и обнаружил, что он является полнофункциональным с сокетами и задачами, больше похож на настоящую платформу «все в одном», которая переворачивает парадигму REST с ног на голову. Вы, вероятно, должны проверить это, пройдя голый с экспрессом.

Джейсон Себринг
источник
11
Я вижу, как это разделяет маршруты, но как это влияет на вложение?
1252748 15.09.16
идеально .... и имеет смысл. Это масштабируемый вариант. Мне было бы интересно узнать, как операционная система будет реализовывать версии (v1, v2 и т. Д.)
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);

Ключ ко второй части вашего вопроса - использование опции mergeParams

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

От /user/jordan/item/catответа я получаю:

{"user_id":"jordan","item_id":"cat"}
Jordonias
источник
Прохладно. И твой, и метод Виллема работают для того, что я хотел. Я проверю его на предмет полноты, но я также отмечу вас. Большое спасибо. Ваш метод не выглядит вложенным, но он в значительной степени делает то, что я хотел, я думаю, что я даже предпочитаю ваш. Спасибо.
Хагги
ключ mergeParams является ключевым здесь!
Мистер
2

Использование решения @Jason Sebring и адаптация для 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;
Пьер РА
источник
Не могли бы вы предоставить ./api/routes?
Джулиан
1
@Julian: я исправил расположение файлов. ./api/routesимеет два файла index.tsи home.ts. Первый используется server.ts. Надеюсь, это поможет вам.
Пьер РА
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
источник
-9

Вам нужен только один маршрутизатор, и используйте его так:

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
источник
Да, но я хочу разделить логику между элементами и пользователями, и поэтому я предпочитаю разделить их. Я не знаю, возможно ли это.
Хагги
@ Huggie itemsпринадлежат к usersправу, почему вы должны отделить это? Вы можете определить их в разных файлах, используя тот же маршрутизатор, если хотите.
eguneys
Он принадлежит пользователю, но я хочу иметь возможность легко подключать или отключать его, не затрагивая пользователя. И в настоящее время у меня есть каждый маршрутизатор для разных конечных точек URL. Стиль, кажется, поощряется экспресс-генератором. Если это невозможно, то да, может быть, я должен отправить экземпляр маршрутизатора в другие файлы? Но это не соответствует оригинальным структурам.
Хагги
Можно ли добавить один роутер под другим? Поскольку архитектура промежуточного программного обеспечения Express, кажется, обрабатывается маршрутизатором снизу (я не совсем уверен, так ли это), я думаю, что это возможно.
Хагги
2
-1 Это не ответ на вопрос о вложенных роутерах
Willem D'Haeseleer