Istirahat dengan router bersarang Express.js

136

Misalkan saya ingin memiliki titik akhir REST yang terlihat seperti ini:

/user/
/user/user_id 

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

CRUD pada masing-masing jika masuk akal. Sebagai contoh, / pengguna POST membuat pengguna baru, GET mengambil semua pengguna. / user / user_id GET mengambil hanya satu pengguna.

Item spesifik untuk pengguna, jadi saya menempatkannya di bawah user_id , yang merupakan pengguna tertentu.

Sekarang untuk membuat Express routing modular, saya membuat beberapa instance router. Ada router untuk pengguna, dan router untuk 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);

URL ke itemadalah turunan dari hierarki URL user. Sekarang bagaimana cara mendapatkan URL dengan /usersapa saja untuk userRouter tetapi rute yang lebih spesifik /user/*user_id*/items/ke itemRouter? Dan juga, saya ingin user_id dapat diakses oleh itemRouter juga, jika memungkinkan.

Huggie
sumber
Jawaban bagus sudah menggunakan Express untuk menyelesaikan ini. Namun, Anda bisa menggunakan Loopback (dibangun berdasarkan Express) untuk mengimplementasikan API berbasis Swagger dan menambahkan hubungan antar model untuk melakukan CRUD seperti yang Anda minta. Yang menyenangkan adalah setelah kurva pembelajaran awal, jauh lebih cepat untuk berkumpul. loopback.io
Mike S.

Jawaban:

278

Anda dapat membuat sarang router dengan melampirkannya sebagai middleware pada router lain, dengan atau tanpa params.

Anda harus meneruskan {mergeParams: true}ke router anak jika Anda ingin mengakses paramsdari router induk.

mergeParamsdiperkenalkan di Express4.5.0 (5 Jul 2014)

Dalam contoh ini itemRouterakan dilampirkan userRouterpada /:userId/itemsrute

Ini akan menghasilkan kemungkinan rute berikut:

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
sumber
3
Terima kasih atas jawabannya. Perute yang Anda gunakan di sini lebih bersarang daripada router yang dibagikan oleh Jordonias. Tetapi apakah itu bekerja sama di bawah tenda? Saya ingin memberi Anda hadiah untuk kelengkapan tetapi saya tidak bisa melakukannya sampai beberapa jam kemudian.
huggie
Terima kasih atas jawabannya. Apakah ada cara serupa untuk mendapatkan dari rute anak kueri params dari rute induk?
cwarny
1
Akan mengejutkan saya jika mereka tidak tersedia pada rute apa pun, karena param kueri tidak terkait dengan rute tertentu ...
Willem D'Haeseleer
Jawaban yang sangat teliti! Satu pertanyaan: demi enkapsulasi dan pemisahan pengetahuan antara router pengguna dan router item apakah ada cara deklaratif untuk menentukan bahwa subrouter memerlukan parameter? Dengan kata lain, apakah ada cara eksplisit untuk menulis registrasi atau mengakses panggilan sedemikian rupa sehingga perute item memberi tahu kami bahwa ia diharapkan lulus id pengguna? Contoh situasi, perute item ada di file lain sama sekali, secara struktural tidak jelas bahwa ia memerlukan pengguna kecuali Anda masuk ke panggilannya dan hanya jelas di perute pengguna bahwa perute itu akan melewati id pengguna
yo.ian.g
Ini tidak lagi terbaca dari penggunaan router "standar", saya mencari cara untuk memvisualisasikan sarang ketika melihat kode.
DrewInTheMountains
127

rute bersarang yang dapat dikelola ...

Saya ingin contoh spesifik melakukan rute bersarang dengan cara yang sangat mudah diatur dalam ekspres 4 dan ini adalah hasil pencarian teratas untuk "rute bersarang dalam ekspres". Berikut adalah API yang akan memiliki banyak rute yang perlu dipecah misalnya.

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

Contoh bersarang dalam struktur folder

Saya memperhatikan beberapa komentar tentang "struktur folder bersarang". Ini tersirat dalam hal ini tetapi tidak jelas jadi saya menambahkan bagian di bawah ini. Berikut adalah contoh spesifik dari struktur folder bersarang untuk rute .

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

Ini lebih merupakan contoh umum tentang cara kerja simpul. Jika Anda menggunakan "index.js" di folder yang mirip dengan cara "index.html" bekerja di halaman web untuk direktori default, ini akan mudah untuk skala organisasi Anda berdasarkan rekursi tanpa mengubah titik masuk Anda ke kode. "index.js" adalah dokumen default yang diakses ketika menggunakan memerlukan dalam direktori.

isi dari index.js

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

isi /api/index.js

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

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

isi dari /api/admin/users/index.js

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

Ada beberapa masalah KERING di sini mungkin tapi itu cocok untuk merangkum keprihatinan.

FYI, baru-baru ini saya masuk ke dalam actionhero dan telah menemukan itu sebagai fitur lengkap dengan soket dan tugas, lebih seperti kerangka kerja all-in-one yang membalik paradigma REST di kepalanya. Anda mungkin harus memeriksanya agar tidak telanjang dengan ekspres.

Jason Sebring
sumber
11
Saya melihat bagaimana hal ini memecah rute, tetapi bagaimana cara mengatasi sarang?
1252748
sempurna .... dan masuk akal. Ini adalah opsi yang dapat diskalakan. Saya ingin tahu bagaimana op akan mengimplementasikan versi (v1, v2 dll)
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);

Kunci untuk bagian kedua dari pertanyaan Anda adalah penggunaan opsi mergeParams

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

Dari /user/jordan/item/catsaya mendapat tanggapan:

{"user_id":"jordan","item_id":"cat"}
Jordonias
sumber
Keren. Baik metode Anda maupun Willem bekerja untuk apa yang saya inginkan. Saya akan memeriksa kelengkapannya tetapi saya juga akan menandai Anda. Terima kasih banyak. Metode Anda tidak terlihat bersarang tetapi cukup banyak melakukan apa yang saya inginkan. Saya pikir saya bahkan lebih suka Anda. Terima kasih.
huggie
opsi mergeParams adalah kunci di sini!
MrE
2

Menggunakan solusi @Jason Sebring, dan mengadaptasi untuk naskah.

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
sumber
Bisakah Anda memberikan ./api/routes?
Julian
1
@Julian: Saya sudah memperbaiki lokasi file. ./api/routesmemiliki dua file index.tsdan home.ts. Yang pertama digunakan oleh server.ts. Semoga ini bisa membantu Anda.
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
sumber
-9

Anda hanya perlu satu router, dan gunakan seperti ini:

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);
eguney
sumber
Ya tapi saya ingin memisahkan logika antara item dan pengguna, jadi saya memilih untuk memisahkannya. Saya tidak tahu apakah itu mungkin.
huggie
@ Huggie itemsmilik usershak, mengapa Anda harus memisahkan itu? Anda dapat mendefinisikannya dalam file yang berbeda masih menggunakan router yang sama jika Anda menginginkannya.
eguneys
Itu milik pengguna tetapi saya ingin dapat dengan mudah menghubungkannya atau keluar tanpa mempengaruhi pengguna. Dan saat ini saya memiliki setiap router untuk titik akhir URL yang berbeda. Gaya ini tampaknya didorong oleh generator ekspres. Jika tidak memungkinkan, maka ya mungkin saya harus mengirim contoh router ke file yang berbeda? Tapi itu tidak konsisten dengan struktur aslinya.
huggie
Apakah mungkin untuk menambahkan satu router di bawah yang lain? Karena arsitektur Express middleware tampaknya ditangani oleh router di bawahnya (saya tidak sepenuhnya yakin apakah itu) saya pikir itu mungkin.
huggie
2
-1 Ini tidak menjawab pertanyaan tentang router bersarang
Willem D'Haeseleer