พักผ่อนด้วยเราเตอร์ที่ซ้อนกันของ Express.js


143

สมมติว่าฉันต้องการมีจุดสิ้นสุด 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. ตอนนี้ฉันจะรับ URL ที่มี/usersอะไรก็ได้ให้กับ userRouter แต่เส้นทาง/user/*user_id*/items/ไปยัง itemRouter ที่เฉพาะเจาะจงมากขึ้นได้อย่างไร และฉันต้องการให้ user_id สามารถเข้าถึง itemRouter ได้เช่นกันถ้าเป็นไปได้


คำตอบที่ดีอยู่แล้วในการใช้ Express เพื่อแก้ปัญหานี้ อย่างไรก็ตามคุณสามารถใช้ Loopback (สร้างขึ้นบน Express) เพื่อใช้งาน API ที่ใช้ Swagger และเพิ่มความสัมพันธ์ระหว่างโมเดลเพื่อดำเนินการ CRUD ตามที่คุณถาม สิ่งที่ดีคือหลังจากช่วงการเรียนรู้เริ่มต้นการประกอบจะเร็วขึ้นมาก loopback.io
Mike S.

คำตอบ:


295

คุณสามารถเราเตอร์รังโดยการแนบพวกเขาเป็นตัวกลางในเราเตอร์อื่น ๆ paramsด้วยหรือไม่

คุณต้องส่งผ่าน{mergeParams: true}ไปยังเราเตอร์ลูกหากคุณต้องการเข้าถึงparamsจากเราเตอร์หลัก

mergeParamsได้รับการแนะนำในExpress4.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 แต่มันทำงานเหมือนกันใต้ฝากระโปรงหรือไม่? ฉันอยากจะมอบเงินรางวัลให้กับคุณเพื่อความครอบคลุม แต่ฉันไม่สามารถทำได้จนกว่าสองสามชั่วโมงต่อมา
huggie

ขอบคุณสำหรับคำตอบ. มีลักษณะที่คล้ายกันจะได้รับจากเส้นทางเด็กแบบสอบถาม params เส้นทางแม่?
cwarny

1
มันจะทำให้ฉันประหลาดใจถ้าไม่มีในเส้นทางใด ๆ เนื่องจากพารามิเตอร์การค้นหาไม่ได้เชื่อมโยงกับเส้นทางใด ๆ โดยเฉพาะ ...
Willem D'Haeseleer

ตอบละเอียดมาก! คำถามหนึ่ง: เพื่อประโยชน์ในการห่อหุ้มและการแยกความรู้ระหว่างเราเตอร์ผู้ใช้และเราเตอร์รายการมีวิธีการประกาศในการระบุว่า subrouter ต้องการพารามิเตอร์หรือไม่? กล่าวอีกนัยหนึ่งมีวิธีที่ชัดเจนในการเขียนการลงทะเบียนหรือการเรียกเข้าเพื่อให้รายการเราเตอร์แจ้งให้เราทราบว่าคาดว่าจะส่งรหัสผู้ใช้หรือไม่ ตัวอย่างสถานการณ์เราเตอร์รายการอยู่ในไฟล์อื่นโดยสิ้นเชิงโครงสร้างไม่ชัดเจนว่าต้องใช้ผู้ใช้เว้นแต่คุณจะได้รับสายและเป็นที่ชัดเจนในเราเตอร์ผู้ใช้ว่าจะส่งรหัสผู้ใช้
yo.ian.g

นี่เป็นเรื่องที่อ่านไม่ได้อีกต่อไปว่าการใช้เราเตอร์แบบ "มาตรฐาน" ฉันกำลังมองหาวิธีที่จะเห็นภาพการซ้อนกันเมื่อดูโค้ด
DrewInTheMountains

132

เส้นทางซ้อนที่จัดการได้ ...

ฉันต้องการตัวอย่างเฉพาะของการสร้างเส้นทางซ้อนในวิธีที่จัดการได้ง่ายใน Express 4 และนี่คือผลการค้นหาอันดับต้น ๆ สำหรับ "เส้นทางที่ซ้อนกันใน Express" นี่คือ 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" เป็นเอกสารเริ่มต้นที่เข้าถึงเมื่อใช้needในไดเร็กทอรี

เนื้อหาของ 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;

อาจมีปัญหาเกี่ยวกับความแห้งอยู่ที่นี่ แต่ก็ช่วยให้สามารถสรุปข้อกังวลได้ดี

FYI เมื่อเร็ว ๆ นี้ฉันได้เข้าสู่วงการแอ็คชั่นฮีโร่และพบว่ามันมีคุณสมบัติครบถ้วนพร้อมซ็อกเก็ตและงานต่างๆเหมือนกับเฟรมเวิร์กที่แท้จริงแบบออล - อิน - วันที่พลิกกระบวนทัศน์ REST บนหัวของมัน คุณน่าจะลองดูการเปลือยกายด้วยการแสดงออก


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/catฉันได้รับคำตอบ:

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

เย็น. ทั้งวิธีของคุณและวิลเลมใช้ได้กับสิ่งที่ฉันต้องการ ฉันจะตรวจสอบความครอบคลุมของเขา แต่ฉันจะทำเครื่องหมายให้คุณด้วย ขอบคุณมาก. วิธีการของคุณดูไม่ซ้อนกัน แต่มันก็ทำได้ในสิ่งที่ฉันต้องการฉันคิดว่าฉันชอบของคุณด้วยซ้ำ ขอบคุณ.
huggie

ตัวเลือก mergeParams เป็นกุญแจสำคัญที่นี่!
MrE

1

ใช้โซลูชัน @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.tsและhome.ts. อันแรกถูกใช้โดยserver.ts. หวังว่ามันจะช่วยคุณได้
Pierre RA

1
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 () {});
    
    
    }

-7

คุณต้องการเราเตอร์เพียงตัวเดียวและใช้งานได้ดังนี้:

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 itemsเป็นของusersถูกทำไมคุณต้องแยกมัน? คุณสามารถกำหนดไฟล์เหล่านี้ในไฟล์ต่างๆโดยใช้เราเตอร์เดียวกันได้หากต้องการ
eguneys

มันเป็นของผู้ใช้ แต่ฉันต้องการให้สามารถเสียบเข้าหรือออกได้อย่างง่ายดายโดยไม่ส่งผลกระทบต่อผู้ใช้ และตอนนี้ฉันมีเราเตอร์แต่ละตัวสำหรับปลายทาง URL ที่แตกต่างกัน รูปแบบดูเหมือนจะได้รับการสนับสนุนจากเครื่องกำเนิดไฟฟ้าด่วน ถ้าเป็นไปไม่ได้ฉันควรส่งอินสแตนซ์เราเตอร์ไปยังไฟล์อื่น? แต่ไม่สอดคล้องกับโครงสร้างดั้งเดิม
huggie

เป็นไปได้หรือไม่ที่จะเพิ่มเราเตอร์ตัวหนึ่งไว้ข้างใต้อีกตัว? เนื่องจากสถาปัตยกรรมมิดเดิลแวร์ของ Express ดูเหมือนจะถูกจัดการโดยเราเตอร์ด้านล่าง (ฉันไม่แน่ใจทั้งหมดว่าเป็น) ฉันคิดว่ามันเป็นไปได้
huggie

2
-1 นี่ไม่ได้ตอบคำถามเกี่ยวกับเราเตอร์แบบซ้อน
Willem D'Haeseleer
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.