Express 教程 第 4 部分:路由和控制器

在本教程中,我們將為最終在LocalLibrary網站中需要的所有資源端點設定路由(URL 處理程式碼)以及“虛擬”處理程式函式。完成後,我們將擁有一個模組化的路由處理程式碼結構,我們可以在後續文章中使用真實的處理程式函式對其進行擴充套件。我們還將深入瞭解如何使用 Express 建立模組化路由!

先決條件 閱讀Express/Node 簡介。完成之前的教程主題(包括Express 教程第 3 部分:使用資料庫(使用 Mongoose))。
目標 瞭解如何建立簡單的路由。設定我們所有的 URL 端點。

概述

上一篇教程文章中,我們定義了Mongoose模型來與資料庫互動,並使用(獨立的)指令碼建立了一些初始的庫記錄。現在我們可以編寫程式碼將這些資訊呈現給使用者。我們需要做的第一件事是確定我們希望在頁面中顯示哪些資訊,然後為返回這些資源定義相應的 URL。然後,我們需要建立路由(URL 處理程式)和檢視(模板)來顯示這些頁面。

下圖作為處理 HTTP 請求/響應時資料的主要流程和需要實現的事項的提醒。除了檢視和路由之外,該圖還顯示了“控制器”——用於將路由請求的程式碼與實際處理請求的程式碼分開的函式。

由於我們已經建立了模型,因此我們需要建立的主要內容是

  • “路由”,用於將支援的請求(以及請求 URL 中編碼的任何資訊)轉發到相應的控制器函式。
  • 控制器函式,用於從模型中獲取請求的資料,建立顯示資料的 HTML 頁面,並將其返回給使用者在瀏覽器中檢視。
  • 控制器用來渲染資料的檢視(模板)。

Main data flow diagram of an MVC express server: 'Routes' receive the HTTP requests sent to the Express server and forward them to the appropriate 'controller' function. The controller reads and writes data from the models. Models are connected to the database to provide data access to the server. Controllers use 'views', also called templates, to render the data. The Controller sends the HTML HTTP response back to the client as an HTTP response.

最終,我們可能會有頁面來顯示書籍、流派、作者和書籍例項的列表和詳細資訊,以及建立、更新和刪除記錄的頁面。在一篇文章中記錄這麼多內容工作量很大。因此,本文的大部分內容將集中在設定我們的路由和控制器以返回“虛擬”內容。我們將在後續文章中擴充套件控制器方法以使用模型資料。

下面的第一部分簡要介紹瞭如何使用 Express Router 中介軟體。然後,我們在設定 LocalLibrary 路由時將在後續部分使用這些知識。

路由入門

路由是 Express 程式碼的一部分,它將 HTTP 動詞(GETPOSTPUTDELETE 等)、URL 路徑/模式和用於處理該模式的函式關聯起來。

建立路由的方法有很多。在本教程中,我們將使用express.Router中介軟體,因為它允許我們將特定站點部分的路由處理程式組合在一起,並使用公共路由字首訪問它們。我們將把所有與庫相關的路由儲存在“catalog”模組中,如果我們新增用於處理使用者帳戶或其他功能的路由,我們可以將它們分別分組。

注意:我們在Express 簡介 > 建立路由處理程式中簡要討論了 Express 應用程式路由。除了為模組化提供更好的支援(如下面的第一小節中所述)之外,使用Router與直接在Express 應用程式物件上定義路由非常相似。

本節的其餘部分概述瞭如何使用Router定義路由。

定義和使用單獨的路由模組

以下程式碼提供了一個具體的示例,說明我們如何建立路由模組並在Express應用程式中使用它。

首先,我們在名為wiki.js的模組中為維基建立路由。程式碼首先匯入 Express 應用程式物件,使用它獲取Router物件,然後使用get()方法向其中新增幾個路由。最後,模組匯出Router物件。

js
// wiki.js - Wiki route module.

const express = require("express");
const router = express.Router();

// Home page route.
router.get("/", function (req, res) {
  res.send("Wiki home page");
});

// About page route.
router.get("/about", function (req, res) {
  res.send("About this wiki");
});

module.exports = router;

注意:上面我們直接在路由器函式中定義了路由處理程式回撥。在 LocalLibrary 中,我們將在單獨的控制器模組中定義這些回撥。

要在我們的主應用程式檔案中使用路由器模組,我們首先require()路由模組(wiki.js)。然後,我們在Express應用程式上呼叫use()將 Router 新增到中介軟體處理路徑中,並指定 URL 路徑“wiki”。

js
const wiki = require("./wiki.js");
// …
app.use("/wiki", wiki);

然後可以透過/wiki//wiki/about/訪問在我們的 wiki 路由模組中定義的兩個路由。

路由函式

我們上面的模組定義了幾個典型的路由函式。“about”路由(如下所示)使用Router.get()方法定義,該方法僅響應 HTTP GET 請求。此方法的第一個引數是 URL 路徑,第二個引數是在收到具有該路徑的 HTTP GET 請求時將呼叫的回撥函式。

js
router.get("/about", function (req, res) {
  res.send("About this wiki");
});

回撥函式接受三個引數(通常命名為所示:reqresnext),它們將包含 HTTP 請求物件、HTTP 響應和中介軟體鏈中的下一個函式。

注意:路由器函式是Express 中介軟體,這意味著它們必須完成(響應)請求或呼叫鏈中的next函式。在上面的例子中,我們使用send()完成請求,因此不使用next引數(並且我們選擇不指定它)。

上面的路由器函式接受單個回撥,但您可以根據需要指定任意數量的回撥引數,或回撥函式陣列。每個函式都是中介軟體鏈的一部分,並且將按照其新增到鏈中的順序呼叫(除非前面的函式完成了請求)。

此處的回撥函式在響應上呼叫send(),當我們收到路徑為('/about')的 GET 請求時,返回字串“關於此維基”。有其他一些響應方法用於結束請求/響應週期。例如,您可以呼叫res.json()傳送 JSON 響應,或res.sendFile()傳送檔案。在我們構建庫時最常使用的響應方法是render(),它使用模板和資料建立並返回 HTML 檔案——我們將在後面的文章中詳細討論!

HTTP 動詞

上面的示例路由使用Router.get()方法響應具有特定路徑的 HTTP GET 請求。

Router還為所有其他 HTTP 動詞提供了路由方法,這些方法的使用方式大多相同:post()put()delete()options()trace()copy()lock()mkcol()move()purge()propfind()proppatch()unlock()report()mkactivity()checkout()merge()m-search()notify()subscribe()unsubscribe()patch()search()connect()

例如,以下程式碼的行為與之前的/about路由相同,但僅響應 HTTP POST 請求。

js
router.post("/about", (req, res) => {
  res.send("About this wiki");
});

路由路徑

路由路徑定義了可以發出請求的端點。我們到目前為止看到的示例只是一些字串,並且按原樣使用:'/'、'/about'、'/book'、'/any-random.path'。

路由路徑也可以是字串模式。字串模式使用正則表示式語法的一種形式來定義將匹配的端點模式。語法如下所示(請注意,連字元(-)和點(.)在基於字串的路徑中按字面意思解釋)

  • ?:端點必須有 0 個或 1 個前面的字元(或組),例如,路由路徑'/ab?cd'將匹配端點acdabcd
  • +:端點必須有 1 個或多個前面的字元(或組),例如,路由路徑'/ab+cd'將匹配端點abcdabbcdabbbcd等。
  • *:端點可以在放置*字元的位置具有任意字串。例如,路由路徑'/ab*cd'將匹配端點abcdabXcdabSOMErandomTEXTcd等。
  • ():對一組字元進行分組匹配以對其執行其他操作,例如,'/ab(cd)?e'將對組(cd)執行?匹配——它將匹配abeabcde

路由路徑也可以是 JavaScript 正則表示式。例如,下面的路由路徑將匹配catfishdogfish,但不會匹配catflapcatfishhead等。請注意,正則表示式的路徑使用正則表示式語法(它不是像以前的情況那樣是帶引號的字串)。

js
app.get(/.*fish$/, function (req, res) {
  // …
});

注意:LocalLibrary 的大多數路由將使用字串而不是正則表示式。我們還將使用下一節中討論的路由引數。

路由引數

路由引數是用於捕獲 URL 中特定位置值的命名 URL 段。命名段以冒號為字首,然後是名稱(例如,/:your_parameter_name/)。捕獲的值儲存在req.params物件中,使用引數名稱作為鍵(例如,req.params.your_parameter_name)。

例如,考慮一個 URL,其編碼包含有關使用者和書籍的資訊:https://:3000/users/34/books/8989。我們可以如下所示提取這些資訊,其中userIdbookId是路徑引數

js
app.get("/users/:userId/books/:bookId", (req, res) => {
  // Access userId via: req.params.userId
  // Access bookId via: req.params.bookId
  res.send(req.params);
});

路由引數的名稱必須由“單詞字元”(A-Z、a-z、0-9 和 _)組成。

注意:URL /book/create 將與類似/book/:bookId的路由匹配(因為:bookId任何字串的佔位符,因此create匹配)。第一個匹配傳入 URL 的路由將被使用,因此,如果您想專門處理/book/create URL,則其路由處理程式必須在您的/book/:bookId路由之前定義。

這就是開始使用路由所需的一切——如果需要,您可以在 Express 文件中找到更多資訊:基本路由路由指南。以下部分將展示我們如何為 LocalLibrary 設定路由和控制器。

處理路由函式中的錯誤

前面顯示的路由函式都具有reqres引數,它們分別表示請求和響應。路由函式也使用第三個引數next呼叫,該引數可用於將錯誤傳遞給 Express 中介軟體鏈。

以下程式碼展示了它是如何工作的,使用資料庫查詢的示例,該查詢採用回撥函式,並返回錯誤err或一些結果。如果返回err,則使用err作為其第一個引數的值呼叫next(最終錯誤會傳播到我們的全域性錯誤處理程式碼)。成功時,返回所需資料,然後在響應中使用。

js
router.get("/about", (req, res, next) => {
  About.find({}).exec((err, queryResults) => {
    if (err) {
      return next(err);
    }
    //Successful, so render
    res.render("about_view", { title: "About", list: queryResults });
  });
});

處理路由函式中的異常

上一節展示了 Express 如何期望路由函式返回錯誤。該框架設計用於與非同步函式一起使用,這些函式接收一個回撥函式(帶有錯誤和結果引數),並在操作完成後呼叫該函式。這是一個問題,因為稍後我們將進行使用Promise 基於 API 的 Mongoose 資料庫查詢,這些查詢可能會在我們的路由函式中丟擲異常(而不是在回撥中返回錯誤)。

為了使框架能夠正確處理異常,必須捕獲這些異常,然後將其作為錯誤轉發,如上一節所示。

注意:目前處於測試階段的 Express 5 預計將原生處理 JavaScript 異常。

重新構思上一節中使用 About.find().exec() 作為返回 Promise 的資料庫查詢的簡單示例,我們可能會在try...catch 程式碼塊中編寫路由函式,如下所示

js
exports.get("/about", async function (req, res, next) {
  try {
    const successfulResult = await About.find({}).exec();
    res.render("about_view", { title: "About", list: successfulResult });
  } catch (error) {
    return next(error);
  }
});

為每個函式新增如此多的樣板程式碼。相反,在本教程中,我們將使用express-async-handler 模組。這定義了一個包裝函式,它隱藏了 try...catch 程式碼塊和轉發錯誤的程式碼。同一個示例現在非常簡單,因為我們只需要編寫假設成功情況下的程式碼即可

js
// Import the module
const asyncHandler = require("express-async-handler");

exports.get(
  "/about",
  asyncHandler(async (req, res, next) => {
    const successfulResult = await About.find({}).exec();
    res.render("about_view", { title: "About", list: successfulResult });
  }),
);

LocalLibrary 需要哪些路由

我們最終需要的頁面 URL 列在下面,其中 object 被每個模型的名稱(book、bookinstance、genre、author)替換,objects 是 object 的複數形式,id 是預設情況下每個 Mongoose 模型例項賦予的唯一例項欄位 (_id)。

  • catalog/ — 首頁/索引頁。
  • catalog/<objects>/ — 所有書籍、bookinstance、型別或作者的列表(例如 /catalog/books/、/catalog/genres/ 等)。
  • catalog/<object>/<id> — 具有給定 _id 欄位值的特定書籍、bookinstance、型別或作者的詳細資訊頁面(例如 /catalog/book/584493c1f4887f06c0e67d37)
  • catalog/<object>/create — 建立新書籍、bookinstance、型別或作者的表單(例如 /catalog/book/create)
  • catalog/<object>/<id>/update — 更新具有給定 _id 欄位值的特定書籍、bookinstance、型別或作者的表單(例如 /catalog/book/584493c1f4887f06c0e67d37/update)
  • catalog/<object>/<id>/delete — 刪除具有給定 _id 欄位值的特定書籍、bookinstance、型別或作者的表單(例如 /catalog/book/584493c1f4887f06c0e67d37/delete)

第一個主頁和列表頁不編碼任何其他資訊。雖然返回的結果將取決於模型型別和資料庫中的內容,但獲取資訊的查詢始終相同(類似地,物件建立執行的程式碼也將始終類似)。

相反,其他 URL 用於對特定文件/模型例項進行操作——這些 URL 編碼專案的標識(如上所示為 <id>)。我們將使用路徑引數提取編碼的資訊並將其傳遞給路由處理程式(在後面的文章中,我們將使用此資訊動態確定要從資料庫中獲取哪些資訊)。透過在 URL 中編碼資訊,我們只需要為每種型別的每個資源提供一條路由(例如,一條路由來處理每個書籍專案的顯示)。

注意:Express 允許您以任何您喜歡的方式構建 URL——您可以像上面所示那樣在 URL 的主體中編碼資訊,也可以使用 URL GET 引數(例如 /book/?id=6)。無論您使用哪種方法,URL 都應保持簡潔、邏輯和可讀性(在此處檢視 W3C 建議)。

接下來,我們為上述所有 URL 建立路由處理程式回撥函式和路由程式碼。

建立路由處理程式回撥函式

在定義路由之前,我們將首先建立所有它們將呼叫的虛擬/骨架回調函式。回撥函式將儲存在 BookBookInstanceGenreAuthor 的單獨“控制器”模組中(您可以使用任何檔案/模組結構,但這對於本專案來說似乎是一個合適的粒度)。

首先為專案根目錄中的控制器建立一個資料夾 (/controllers),然後為每個模型建立單獨的控制器檔案/模組

/express-locallibrary-tutorial  //the project root
  /controllers
    authorController.js
    bookController.js
    bookinstanceController.js
    genreController.js

控制器將使用 express-async-handler 模組,因此在繼續之前,請使用 npm 將其安裝到庫中

bash
npm install express-async-handler

作者控制器

開啟 /controllers/authorController.js 檔案並輸入以下程式碼

js
const Author = require("../models/author");
const asyncHandler = require("express-async-handler");

// Display list of all Authors.
exports.author_list = asyncHandler(async (req, res, next) => {
  res.send("NOT IMPLEMENTED: Author list");
});

// Display detail page for a specific Author.
exports.author_detail = asyncHandler(async (req, res, next) => {
  res.send(`NOT IMPLEMENTED: Author detail: ${req.params.id}`);
});

// Display Author create form on GET.
exports.author_create_get = asyncHandler(async (req, res, next) => {
  res.send("NOT IMPLEMENTED: Author create GET");
});

// Handle Author create on POST.
exports.author_create_post = asyncHandler(async (req, res, next) => {
  res.send("NOT IMPLEMENTED: Author create POST");
});

// Display Author delete form on GET.
exports.author_delete_get = asyncHandler(async (req, res, next) => {
  res.send("NOT IMPLEMENTED: Author delete GET");
});

// Handle Author delete on POST.
exports.author_delete_post = asyncHandler(async (req, res, next) => {
  res.send("NOT IMPLEMENTED: Author delete POST");
});

// Display Author update form on GET.
exports.author_update_get = asyncHandler(async (req, res, next) => {
  res.send("NOT IMPLEMENTED: Author update GET");
});

// Handle Author update on POST.
exports.author_update_post = asyncHandler(async (req, res, next) => {
  res.send("NOT IMPLEMENTED: Author update POST");
});

該模組首先需要我們稍後將用於訪問和更新資料的 Author 模型,以及我們將用於捕獲路由處理程式函式中丟擲的任何異常的 asyncHandler 包裝器。然後它為我們希望處理的每個 URL 匯出函式。請注意,建立、更新和刪除操作使用表單,因此還具有處理表單釋出請求的其他方法——我們將在以後的“表單文章”中討論這些方法。

所有函式都使用上面在路由函式中處理異常中描述的包裝函式,並帶有請求、響應和 next 的引數。這些函式響應一個字串,指示關聯的頁面尚未建立。如果預期控制器函式接收路徑引數,則這些引數會輸出在訊息字串中(請參見上面的 req.params.id)。

請注意,一旦實現,某些路由函式可能不包含任何可能丟擲異常的程式碼。當我們到達這些函式時,我們可以將它們更改回“普通”路由處理程式函式。

BookInstance 控制器

開啟 /controllers/bookinstanceController.js 檔案並複製以下程式碼(這遵循與 Author 控制器模組相同的模式)

js
const BookInstance = require("../models/bookinstance");
const asyncHandler = require("express-async-handler");

// Display list of all BookInstances.
exports.bookinstance_list = asyncHandler(async (req, res, next) => {
  res.send("NOT IMPLEMENTED: BookInstance list");
});

// Display detail page for a specific BookInstance.
exports.bookinstance_detail = asyncHandler(async (req, res, next) => {
  res.send(`NOT IMPLEMENTED: BookInstance detail: ${req.params.id}`);
});

// Display BookInstance create form on GET.
exports.bookinstance_create_get = asyncHandler(async (req, res, next) => {
  res.send("NOT IMPLEMENTED: BookInstance create GET");
});

// Handle BookInstance create on POST.
exports.bookinstance_create_post = asyncHandler(async (req, res, next) => {
  res.send("NOT IMPLEMENTED: BookInstance create POST");
});

// Display BookInstance delete form on GET.
exports.bookinstance_delete_get = asyncHandler(async (req, res, next) => {
  res.send("NOT IMPLEMENTED: BookInstance delete GET");
});

// Handle BookInstance delete on POST.
exports.bookinstance_delete_post = asyncHandler(async (req, res, next) => {
  res.send("NOT IMPLEMENTED: BookInstance delete POST");
});

// Display BookInstance update form on GET.
exports.bookinstance_update_get = asyncHandler(async (req, res, next) => {
  res.send("NOT IMPLEMENTED: BookInstance update GET");
});

// Handle bookinstance update on POST.
exports.bookinstance_update_post = asyncHandler(async (req, res, next) => {
  res.send("NOT IMPLEMENTED: BookInstance update POST");
});

型別控制器

開啟 /controllers/genreController.js 檔案並複製以下文字(這遵循與 AuthorBookInstance 檔案相同的模式)

js
const Genre = require("../models/genre");
const asyncHandler = require("express-async-handler");

// Display list of all Genre.
exports.genre_list = asyncHandler(async (req, res, next) => {
  res.send("NOT IMPLEMENTED: Genre list");
});

// Display detail page for a specific Genre.
exports.genre_detail = asyncHandler(async (req, res, next) => {
  res.send(`NOT IMPLEMENTED: Genre detail: ${req.params.id}`);
});

// Display Genre create form on GET.
exports.genre_create_get = asyncHandler(async (req, res, next) => {
  res.send("NOT IMPLEMENTED: Genre create GET");
});

// Handle Genre create on POST.
exports.genre_create_post = asyncHandler(async (req, res, next) => {
  res.send("NOT IMPLEMENTED: Genre create POST");
});

// Display Genre delete form on GET.
exports.genre_delete_get = asyncHandler(async (req, res, next) => {
  res.send("NOT IMPLEMENTED: Genre delete GET");
});

// Handle Genre delete on POST.
exports.genre_delete_post = asyncHandler(async (req, res, next) => {
  res.send("NOT IMPLEMENTED: Genre delete POST");
});

// Display Genre update form on GET.
exports.genre_update_get = asyncHandler(async (req, res, next) => {
  res.send("NOT IMPLEMENTED: Genre update GET");
});

// Handle Genre update on POST.
exports.genre_update_post = asyncHandler(async (req, res, next) => {
  res.send("NOT IMPLEMENTED: Genre update POST");
});

書籍控制器

開啟 /controllers/bookController.js 檔案並複製以下程式碼。這遵循與其他控制器模組相同的模式,但另外還有一個用於顯示站點歡迎頁面的 index() 函式

js
const Book = require("../models/book");
const asyncHandler = require("express-async-handler");

exports.index = asyncHandler(async (req, res, next) => {
  res.send("NOT IMPLEMENTED: Site Home Page");
});

// Display list of all books.
exports.book_list = asyncHandler(async (req, res, next) => {
  res.send("NOT IMPLEMENTED: Book list");
});

// Display detail page for a specific book.
exports.book_detail = asyncHandler(async (req, res, next) => {
  res.send(`NOT IMPLEMENTED: Book detail: ${req.params.id}`);
});

// Display book create form on GET.
exports.book_create_get = asyncHandler(async (req, res, next) => {
  res.send("NOT IMPLEMENTED: Book create GET");
});

// Handle book create on POST.
exports.book_create_post = asyncHandler(async (req, res, next) => {
  res.send("NOT IMPLEMENTED: Book create POST");
});

// Display book delete form on GET.
exports.book_delete_get = asyncHandler(async (req, res, next) => {
  res.send("NOT IMPLEMENTED: Book delete GET");
});

// Handle book delete on POST.
exports.book_delete_post = asyncHandler(async (req, res, next) => {
  res.send("NOT IMPLEMENTED: Book delete POST");
});

// Display book update form on GET.
exports.book_update_get = asyncHandler(async (req, res, next) => {
  res.send("NOT IMPLEMENTED: Book update GET");
});

// Handle book update on POST.
exports.book_update_post = asyncHandler(async (req, res, next) => {
  res.send("NOT IMPLEMENTED: Book update POST");
});

建立目錄路由模組

接下來,我們為LocalLibrary 網站所需的所有 URL 建立路由,這些路由將呼叫我們在上一節中定義的控制器函式。

骨架已經有一個 ./routes 資料夾,其中包含 indexusers 的路由。在此資料夾中建立另一個路由檔案——catalog.js——如下所示。

/express-locallibrary-tutorial //the project root
  /routes
    index.js
    users.js
    catalog.js

開啟 /routes/catalog.js 並複製以下程式碼

js
const express = require("express");
const router = express.Router();

// Require controller modules.
const book_controller = require("../controllers/bookController");
const author_controller = require("../controllers/authorController");
const genre_controller = require("../controllers/genreController");
const book_instance_controller = require("../controllers/bookinstanceController");

/// BOOK ROUTES ///

// GET catalog home page.
router.get("/", book_controller.index);

// GET request for creating a Book. NOTE This must come before routes that display Book (uses id).
router.get("/book/create", book_controller.book_create_get);

// POST request for creating Book.
router.post("/book/create", book_controller.book_create_post);

// GET request to delete Book.
router.get("/book/:id/delete", book_controller.book_delete_get);

// POST request to delete Book.
router.post("/book/:id/delete", book_controller.book_delete_post);

// GET request to update Book.
router.get("/book/:id/update", book_controller.book_update_get);

// POST request to update Book.
router.post("/book/:id/update", book_controller.book_update_post);

// GET request for one Book.
router.get("/book/:id", book_controller.book_detail);

// GET request for list of all Book items.
router.get("/books", book_controller.book_list);

/// AUTHOR ROUTES ///

// GET request for creating Author. NOTE This must come before route for id (i.e. display author).
router.get("/author/create", author_controller.author_create_get);

// POST request for creating Author.
router.post("/author/create", author_controller.author_create_post);

// GET request to delete Author.
router.get("/author/:id/delete", author_controller.author_delete_get);

// POST request to delete Author.
router.post("/author/:id/delete", author_controller.author_delete_post);

// GET request to update Author.
router.get("/author/:id/update", author_controller.author_update_get);

// POST request to update Author.
router.post("/author/:id/update", author_controller.author_update_post);

// GET request for one Author.
router.get("/author/:id", author_controller.author_detail);

// GET request for list of all Authors.
router.get("/authors", author_controller.author_list);

/// GENRE ROUTES ///

// GET request for creating a Genre. NOTE This must come before route that displays Genre (uses id).
router.get("/genre/create", genre_controller.genre_create_get);

//POST request for creating Genre.
router.post("/genre/create", genre_controller.genre_create_post);

// GET request to delete Genre.
router.get("/genre/:id/delete", genre_controller.genre_delete_get);

// POST request to delete Genre.
router.post("/genre/:id/delete", genre_controller.genre_delete_post);

// GET request to update Genre.
router.get("/genre/:id/update", genre_controller.genre_update_get);

// POST request to update Genre.
router.post("/genre/:id/update", genre_controller.genre_update_post);

// GET request for one Genre.
router.get("/genre/:id", genre_controller.genre_detail);

// GET request for list of all Genre.
router.get("/genres", genre_controller.genre_list);

/// BOOKINSTANCE ROUTES ///

// GET request for creating a BookInstance. NOTE This must come before route that displays BookInstance (uses id).
router.get(
  "/bookinstance/create",
  book_instance_controller.bookinstance_create_get,
);

// POST request for creating BookInstance.
router.post(
  "/bookinstance/create",
  book_instance_controller.bookinstance_create_post,
);

// GET request to delete BookInstance.
router.get(
  "/bookinstance/:id/delete",
  book_instance_controller.bookinstance_delete_get,
);

// POST request to delete BookInstance.
router.post(
  "/bookinstance/:id/delete",
  book_instance_controller.bookinstance_delete_post,
);

// GET request to update BookInstance.
router.get(
  "/bookinstance/:id/update",
  book_instance_controller.bookinstance_update_get,
);

// POST request to update BookInstance.
router.post(
  "/bookinstance/:id/update",
  book_instance_controller.bookinstance_update_post,
);

// GET request for one BookInstance.
router.get("/bookinstance/:id", book_instance_controller.bookinstance_detail);

// GET request for list of all BookInstance.
router.get("/bookinstances", book_instance_controller.bookinstance_list);

module.exports = router;

該模組需要 Express,然後使用它來建立一個 Router 物件。所有路由都在路由器上設定,然後匯出。

路由使用路由器物件上的 .get().post() 方法定義。所有路徑都使用字串定義(我們不使用字串模式或正則表示式)。對某些特定資源(例如書籍)進行操作的路由使用路徑引數從 URL 中獲取物件 ID。

處理程式函式全部從我們在上一節中建立的控制器模組匯入。

更新索引路由模組

我們已經設定了所有新的路由,但我們仍然有一條到原始頁面的路由。讓我們改為將其重定向到我們在路徑 '/catalog' 下建立的新索引頁面。

開啟 /routes/index.js 並將現有路由替換為以下函式。

js
// GET home page.
router.get("/", function (req, res) {
  res.redirect("/catalog");
});

注意:這是我們第一次使用redirect() 響應方法。這將重定向到指定的頁面,預設情況下發送 HTTP 狀態程式碼“302 Found”。如果需要,您可以更改返回的狀態程式碼,並提供絕對或相對路徑。

更新 app.js

最後一步是將路由新增到中介軟體鏈中。我們在 app.js 中執行此操作。

開啟 app.js 並在其他路由下方需要 catalog 路由(新增下面顯示的第三行,在檔案中原有的另外兩行下方)

js
var indexRouter = require("./routes/index");
var usersRouter = require("./routes/users");
const catalogRouter = require("./routes/catalog"); //Import routes for "catalog" area of site

接下來,將 catalog 路由新增到其他路由下方的中介軟體堆疊中(新增下面顯示的第三行,在檔案中原有的另外兩行下方)

js
app.use("/", indexRouter);
app.use("/users", usersRouter);
app.use("/catalog", catalogRouter); // Add catalog routes to middleware chain.

注意:我們在路徑 '/catalog' 下添加了我們的 catalog 模組。這將預先新增到 catalog 模組中定義的所有路徑之前。例如,要訪問書籍列表,URL 將是:/catalog/books/

就是這樣。我們現在應該為 LocalLibrary 網站上我們最終將支援的所有 URL 啟用了路由和骨架函式。

測試路由

要測試路由,首先使用您通常的方法啟動網站

  • 預設方法
    bash
    # Windows
    SET DEBUG=express-locallibrary-tutorial:* & npm start
    
    # macOS or Linux
    DEBUG=express-locallibrary-tutorial:* npm start
    
  • 如果您之前設定了nodemon,則可以使用
    bash
    npm run serverstart
    

然後導航到多個 LocalLibrary URL,並驗證您是否沒有收到錯誤頁面(HTTP 404)。為方便起見,下面列出了一些 URL

  • https://:3000/
  • https://:3000/catalog
  • https://:3000/catalog/books
  • https://:3000/catalog/bookinstances/
  • https://:3000/catalog/authors/
  • https://:3000/catalog/genres/
  • https://:3000/catalog/book/5846437593935e2f8c2aa226
  • https://:3000/catalog/book/create

總結

我們現在為我們的網站建立了所有路由,以及我們可以用完整實現填充的虛擬控制器函式,這些函式將在以後的文章中介紹。在此過程中,我們瞭解了許多關於 Express 路由、處理異常以及構建路由和控制器的某些方法的基本資訊。

在下一篇文章中,我們將使用檢視(模板)和儲存在模型中的資訊為站點建立一個合適的歡迎頁面。

另請參閱