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 ( 특정 사용자) 아래에 놓습니다 .

이제 익스프레스 라우팅을 모듈화하기 위해 몇 가지 라우터 인스턴스를 만들었습니다. 사용자를위한 라우터와 아이템을위한 라우터가 있습니다.

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 item은의 URL 계층 구조의 하위 항목입니다 user. 이제 /usersuserRouter에 대한 것이지만 itemRouter에 대한보다 구체적인 경로는 /user/*user_id*/items/무엇입니까? 또한 가능한 경우 item_uter에서도 user_id에 액세스 할 수 있기를 바랍니다.


이 문제를 해결하기 위해 Express를 사용하는 것에 대한 훌륭한 답변이 이미 있습니다. 그러나 Express에 빌드 된 Loopback을 사용하여 Swagger 기반 API를 구현하고 요청한대로 CRUD를 수행하기 위해 모델간에 관계를 추가 할 수 있습니다. 좋은 점은 초기 학습 곡선 후에 조립하는 것이 훨씬 빠릅니다. loopback.io
Mike S.

답변:


278

라우터를 사용하거나 사용하지 않고 다른 라우터에 미들웨어 로 연결하여 라우터를 중첩 할 수 있습니다 params.

상위 라우터에서 {mergeParams: true}액세스하려면 하위 라우터로 전달해야 params합니다.

mergeParamsExpress4.5.0 에 도입되었습니다 (2014 년 7 월 5 일)

이 예에서는 경로 에 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가 공유 한 라우터보다 더 명확하게 중첩됩니다. 그러나 그것은 후드 아래에서 동일하게 작동합니까? 포괄성에 대한 현상금을 부여하고 싶지만 몇 시간 후에는 할 수 없습니다.
huggie

답변 해주셔서 감사합니다. 자식 경로에서 부모 경로의 쿼리 매개 변수 를 얻는 비슷한 방법이 있습니까?
cwarny

1
쿼리 매개 변수가 특정 경로에 연결되어 있지 않기 때문에 어떤 경로에서도 사용할 수 없다면 놀랄 것입니다 ...
Willem D' Haeseleer

매우 철저한 답변! 하나의 질문 : 사용자 라우터와 항목 라우터 사이의 지식 캡슐화 및 지식 분리를 위해 서브 라우터에 매개 변수가 필요하다는 것을 지정하는 선언적 방법이 있습니까? 즉, 항목 라우터가 사용자 ID를 전달할 것으로 예상하도록 알려주는 등록 또는 액세스 호출을 작성하는 명시적인 방법이 있습니까? 예 상황, 항목 라우터는 구조적으로 당신이 전화로 얻을하지 않는 한 사용자가 필요 명확하지 않으며 그것은 사용자 ID를 통과 할 것이라는 사용자의 라우터 만 분명, 완전히 다른 파일에
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;

폴더 구조의 중첩 예

"중첩 폴더 구조"에 대한 의견을 발견했습니다. 그러나 이것이 명확하지는 않기 때문에 아래 섹션을 추가했습니다. 다음 은 route 에 대한 중첩 폴더 구조 의 특정 예입니다 .

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

이것은 노드의 작동 방식에 대한 일반적인 예입니다. 디렉토리 기본값으로 웹 페이지에서 "index.html"이 작동하는 방식과 유사하게 폴더에서 "index.js"를 사용하는 경우 진입 점을 코드로 변경하지 않고 재귀에 따라 조직을 쉽게 확장 할 수 있습니다. "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;

여기에 일부 DRY 문제가있을 수 있지만 문제를 캡슐화하는 데 적합합니다.

참고로, 최근에 나는 actionhero에 들어가서 REST 패러다임을 뒤집는 진정한 프레임 워크 올인원과 같은 완전한 기능의 w / 소켓 및 작업 인 것을 발견했습니다. 당신은 아마 명시 적 / 알몸으로 그것을 확인해야합니다.


11
이것이 경로를 분할하는 방법을 보았지만 어떻게 중첩을 해결합니까?
1252748

완벽합니다 .... 그리고 말이됩니다. 이것은 확장 가능한 옵션입니다. op가 버전 관리 (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/catI reponse를 얻을 :

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

멋있는. 당신과 Willem의 방법 모두 내가 원하는 것에 효과적입니다. 나는 그의 포괄 성을 점검 할 것이다. 그러나 나는 또한 당신을 표시 할 것이다. 고마워 귀하의 방법은 중첩되어 보이지 않지만 내가 원하는 것을 거의 좋아합니다. 감사.
huggie

mergeParams 옵션이 여기에 중요합니다!
MrE

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있습니까?
Julian

1
@ 줄리안 : 파일 위치를 수정했습니다. ./api/routes두 파일을 가지고 index.tshome.ts. 첫 번째는에 의해 사용됩니다 server.ts. 그것이 도움이되기를 바랍니다.
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

라우터는 하나만 필요하며 다음과 같이 사용하십시오.

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

예,하지만 항목과 사용자 사이의 논리를 분리하고 싶습니다. 따라서 분리하는 것을 선호합니다. 가능한지 모르겠습니다.
huggie

@ huggie itemsusers오른쪽 에 속하며 왜 분리해야합니까? 원하는 경우 동일한 라우터를 사용하여 다른 파일에서 파일을 정의 할 수 있습니다.
eguneys

그것은 사용자에게 있지만 사용자에게 영향을 미치지 않고 쉽게 꽂거나 뺄 수 있기를 원합니다. 그리고 현재 다른 URL 끝점에 대한 각 라우터가 있습니다. 급행 발전기가 스타일을 장려하는 것 같습니다. 가능하지 않다면 라우터 인스턴스를 다른 파일로 보내야합니까? 그러나 그것은 원래 구조와 일치하지 않습니다.
huggie

하나의 라우터를 다른 라우터 아래에 추가 할 수 있습니까? Express 미들웨어 아키텍처는 아래 라우터에 의해 처리되는 것처럼 보이므로 (전혀 있는지 확실하지는 않습니다) 가능하다고 생각합니다.
huggie

2
-1 이것은 중첩 라우터에 관한 질문에 답하지 않습니다
Willem D' Haeseleer
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.