Phần còn lại với bộ định tuyến lồng nhau Express.js


136

Giả sử tôi muốn có các điểm cuối REST trông giống như thế này:

/user/
/user/user_id 

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

CRUD trên mỗi nếu có ý nghĩa. Ví dụ: / user POST tạo một người dùng mới, GET tìm nạp tất cả người dùng. / user / user_id GET chỉ tìm nạp một người dùng đó.

Các mục cụ thể là người dùng, vì vậy tôi đặt chúng dưới user_id , một người dùng cụ thể.

Bây giờ để thực hiện mô đun định tuyến Express tôi đã thực hiện một vài trường hợp bộ định tuyến. Có một bộ định tuyến cho người dùng và một bộ định tuyến cho các mục.

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 đến itemlà hậu duệ của hệ thống phân cấp URL của user. Bây giờ làm cách nào để tôi nhận được URL với /usersbất cứ điều gì với userRouter nhưng tuyến đường cụ thể hơn /user/*user_id*/items/đến itemRouter? Ngoài ra, tôi cũng muốn user_id có thể truy cập vào itemRouter, nếu có thể.


Câu trả lời tuyệt vời đã được sử dụng Express để giải quyết điều này. Tuy nhiên, bạn có thể sử dụng Loopback (được xây dựng trên Express) để triển khai API dựa trên Swagger và thêm quan hệ giữa các mô hình để thực hiện CRUD như bạn đã yêu cầu. Điều tốt đẹp là sau khi học đường ban đầu, nó nhanh hơn nhiều để lắp ráp. loopback.io
Mike S.

Câu trả lời:


276

Bạn có thể lồng các bộ định tuyến bằng cách gắn chúng làm phần mềm trung gian trên một bộ định tuyến khác, có hoặc không có params.

Bạn phải chuyển {mergeParams: true}đến bộ định tuyến con nếu bạn muốn truy cập paramstừ bộ định tuyến mẹ.

mergeParamsđã được giới thiệu trên Express4.5.0 (ngày 5 tháng 7 năm 2014)

Trong ví dụ này, itemRouterđược gắn userRoutervào /:userId/itemstuyến đường

Điều này sẽ dẫn đến các tuyến đường sau:

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
Cảm ơn câu trả lời. Bộ định tuyến bạn sử dụng ở đây được lồng rõ ràng hơn bộ định tuyến được chia sẻ bởi Jordonias. Nhưng nó hoạt động tương tự bên dưới mui xe? Tôi muốn cấp cho bạn tiền thưởng cho sự toàn diện nhưng tôi không thể làm điều đó cho đến một vài giờ sau đó.
huggie

Cảm ơn câu trả lời. Có cách nào tương tự để lấy từ tuyến con các tham số truy vấn của tuyến cha không?
cwarny

1
Tôi sẽ ngạc nhiên nếu chúng không có sẵn trên bất kỳ tuyến đường nào, vì thông số truy vấn không được liên kết với bất kỳ tuyến đường nào cụ thể ...
Willem D'Haeseleer

Câu trả lời rất kỹ lưỡng! Một câu hỏi: vì mục đích đóng gói và phân tách kiến ​​thức giữa bộ định tuyến người dùng và bộ định tuyến vật phẩm, có cách nào để xác định rằng một bộ lọc con yêu cầu một tham số không? Nói cách khác, có một cách rõ ràng để viết đăng ký hoặc truy cập các cuộc gọi sao cho bộ định tuyến vật phẩm cho chúng ta biết nó dự kiến ​​sẽ được thông qua một id người dùng? Ví dụ tình huống, bộ định tuyến mục nằm trong một tệp khác hoàn toàn, về mặt cấu trúc, không rõ ràng rằng nó yêu cầu người dùng trừ khi bạn nhận được các cuộc gọi của nó và chỉ rõ ràng trong bộ định tuyến người dùng rằng nó sẽ chuyển id người dùng
yo.ian.g

Điều này không còn rõ ràng nữa khi sử dụng các bộ định tuyến "tiêu chuẩn", tôi đang tìm cách trực quan hóa việc lồng nhau khi xem mã.
DrewInTheMenezs

127

các tuyến lồng nhau có thể quản lý ...

Tôi muốn có một ví dụ cụ thể về việc thực hiện các tuyến đường lồng nhau theo cách rất dễ quản lý trong express 4 và đây là kết quả tìm kiếm hàng đầu cho "các tuyến đường lồng nhau". Đây là một API sẽ có nhiều tuyến đường cần được chia nhỏ chẳng hạn.

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

Ví dụ lồng nhau trong cấu trúc thư mục

Tôi nhận thấy một số ý kiến ​​về "cấu trúc thư mục lồng nhau". Nó được ngụ ý trong điều này tuy nhiên không rõ ràng vì vậy tôi đã thêm phần bên dưới. Đây là một ví dụ cụ thể về cấu trúc thư mục lồng nhau cho các tuyến đường .

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

Đây là một ví dụ chung về cách thức hoạt động của nút. Nếu bạn sử dụng "index.js" trong các thư mục tương tự như cách "index.html" hoạt động trong các trang web cho một thư mục mặc định, điều này sẽ dễ dàng mở rộng quy mô tổ chức của bạn dựa trên đệ quy mà không thay đổi điểm nhập thành mã. "Index.js" là tài liệu mặc định được truy cập khi sử dụng yêu cầu trong một thư mục.

nội dung của index.js

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

nội dung của /api/index.js

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

nội dung của /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;

nội dung của /api/admin/users/index.js

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

Có một số vấn đề DRY ở đây có thể nhưng nó cho vay chính nó để đóng gói các mối quan tâm.

FYI, gần đây tôi đã tham gia vào Actionhero và nhận thấy nó có đầy đủ tính năng w / socket và nhiệm vụ, giống như một khung công tác thực sự tất cả trong một mô hình REST trên đầu. Bạn có lẽ nên kiểm tra nó qua việc đi khỏa thân w / express.


11
Tôi thấy cách này phân chia các tuyến đường, nhưng làm thế nào để giải quyết việc lồng nhau?
1252748

hoàn hảo .... và có ý nghĩa. Đây là một tùy chọn có thể mở rộng. Tôi sẽ tò mò muốn biết op sẽ triển khai phiên bản như thế nào (v1, v2, v.v.)
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);

Chìa khóa cho phần thứ hai của câu hỏi của bạn là việc sử dụng tùy chọn mergeParams

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

Từ /user/jordan/item/cattôi nhận được phản hồi:

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

Mát mẻ. Cả phương pháp của bạn và Willem đều hoạt động cho những gì tôi muốn. Tôi sẽ kiểm tra tính toàn diện của anh ấy nhưng tôi cũng sẽ đánh dấu bạn. Cảm ơn rất nhiều. Phương pháp của bạn trông không được lồng nhau nhưng nó thực hiện khá nhiều những gì tôi muốn Tôi nghĩ tôi thậm chí còn thích phương pháp của bạn. Cảm ơn.
huggie

tùy chọn mergeParams là chìa khóa ở đây!
MrE 7/2/2016

2

Sử dụng giải pháp @Jason Sebring và điều chỉnh cho Bản in.

máy chủ

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;

Bạn có thể cung cấp ./api/routes?
Julian

1
@Julian: Tôi đã cố định vị trí tệp. ./api/routescó hai tập tin index.tshome.ts. Cái đầu tiên được sử dụng bởi server.ts. Hy vọng nó sẽ giúp bạn.
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

Bạn chỉ cần một bộ định tuyến và sử dụng nó như thế này:

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

Có nhưng tôi muốn tách biệt logic giữa các mục và người dùng, và vì vậy tôi thích tách chúng hơn. Tôi không biết nếu nó có thể.
huggie

@huggie itemsthuộc về usersquyền, tại sao bạn cần tách riêng điều đó? bạn có thể định nghĩa chúng trong các tệp khác nhau vẫn sử dụng cùng một bộ định tuyến nếu bạn muốn điều đó.
eguneys

Nó thuộc về người dùng nhưng tôi muốn có thể dễ dàng cắm vào hoặc ra mà không ảnh hưởng đến người dùng. Và hiện tại tôi có mỗi bộ định tuyến cho một điểm cuối URL khác nhau. Phong cách dường như được khuyến khích bởi máy phát nhanh. Nếu không thể, thì có lẽ tôi nên gửi cá thể bộ định tuyến đến các tệp khác nhau? Nhưng điều đó không phù hợp với cấu trúc ban đầu.
huggie

Có thể thêm một bộ định tuyến bên dưới một bộ định tuyến khác? Vì kiến ​​trúc phần mềm trung gian Express dường như được xử lý bởi bộ định tuyến bên dưới (tôi không hoàn toàn chắc chắn nếu có), tôi nghĩ rằng nó có thể thực hiện được.
huggie

2
-1 Đây không phải là trả lời câu hỏi về các bộ định tuyến lồng nhau
Willem D'Haeseleer
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.