Express 教程 第 4 部分:路由和控制器
在本教程中,我們將為最終在LocalLibrary網站中需要的所有資源端點設定路由(URL 處理程式碼)以及“虛擬”處理程式函式。完成後,我們將擁有一個模組化的路由處理程式碼結構,我們可以在後續文章中使用真實的處理程式函式對其進行擴充套件。我們還將深入瞭解如何使用 Express 建立模組化路由!
| 先決條件 | 閱讀Express/Node 簡介。完成之前的教程主題(包括Express 教程第 3 部分:使用資料庫(使用 Mongoose))。 |
|---|---|
| 目標 | 瞭解如何建立簡單的路由。設定我們所有的 URL 端點。 |
概述
在上一篇教程文章中,我們定義了Mongoose模型來與資料庫互動,並使用(獨立的)指令碼建立了一些初始的庫記錄。現在我們可以編寫程式碼將這些資訊呈現給使用者。我們需要做的第一件事是確定我們希望在頁面中顯示哪些資訊,然後為返回這些資源定義相應的 URL。然後,我們需要建立路由(URL 處理程式)和檢視(模板)來顯示這些頁面。
下圖作為處理 HTTP 請求/響應時資料的主要流程和需要實現的事項的提醒。除了檢視和路由之外,該圖還顯示了“控制器”——用於將路由請求的程式碼與實際處理請求的程式碼分開的函式。
由於我們已經建立了模型,因此我們需要建立的主要內容是
- “路由”,用於將支援的請求(以及請求 URL 中編碼的任何資訊)轉發到相應的控制器函式。
- 控制器函式,用於從模型中獲取請求的資料,建立顯示資料的 HTML 頁面,並將其返回給使用者在瀏覽器中檢視。
- 控制器用來渲染資料的檢視(模板)。
最終,我們可能會有頁面來顯示書籍、流派、作者和書籍例項的列表和詳細資訊,以及建立、更新和刪除記錄的頁面。在一篇文章中記錄這麼多內容工作量很大。因此,本文的大部分內容將集中在設定我們的路由和控制器以返回“虛擬”內容。我們將在後續文章中擴充套件控制器方法以使用模型資料。
下面的第一部分簡要介紹瞭如何使用 Express Router 中介軟體。然後,我們在設定 LocalLibrary 路由時將在後續部分使用這些知識。
路由入門
路由是 Express 程式碼的一部分,它將 HTTP 動詞(GET、POST、PUT、DELETE 等)、URL 路徑/模式和用於處理該模式的函式關聯起來。
建立路由的方法有很多。在本教程中,我們將使用express.Router中介軟體,因為它允許我們將特定站點部分的路由處理程式組合在一起,並使用公共路由字首訪問它們。我們將把所有與庫相關的路由儲存在“catalog”模組中,如果我們新增用於處理使用者帳戶或其他功能的路由,我們可以將它們分別分組。
注意:我們在Express 簡介 > 建立路由處理程式中簡要討論了 Express 應用程式路由。除了為模組化提供更好的支援(如下面的第一小節中所述)之外,使用Router與直接在Express 應用程式物件上定義路由非常相似。
本節的其餘部分概述瞭如何使用Router定義路由。
定義和使用單獨的路由模組
以下程式碼提供了一個具體的示例,說明我們如何建立路由模組並在Express應用程式中使用它。
首先,我們在名為wiki.js的模組中為維基建立路由。程式碼首先匯入 Express 應用程式物件,使用它獲取Router物件,然後使用get()方法向其中新增幾個路由。最後,模組匯出Router物件。
// 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”。
const wiki = require("./wiki.js");
// …
app.use("/wiki", wiki);
然後可以透過/wiki/和/wiki/about/訪問在我們的 wiki 路由模組中定義的兩個路由。
路由函式
我們上面的模組定義了幾個典型的路由函式。“about”路由(如下所示)使用Router.get()方法定義,該方法僅響應 HTTP GET 請求。此方法的第一個引數是 URL 路徑,第二個引數是在收到具有該路徑的 HTTP GET 請求時將呼叫的回撥函式。
router.get("/about", function (req, res) {
res.send("About this wiki");
});
回撥函式接受三個引數(通常命名為所示:req、res、next),它們將包含 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 請求。
router.post("/about", (req, res) => {
res.send("About this wiki");
});
路由路徑
路由路徑定義了可以發出請求的端點。我們到目前為止看到的示例只是一些字串,並且按原樣使用:'/'、'/about'、'/book'、'/any-random.path'。
路由路徑也可以是字串模式。字串模式使用正則表示式語法的一種形式來定義將匹配的端點模式。語法如下所示(請注意,連字元(-)和點(.)在基於字串的路徑中按字面意思解釋)
?:端點必須有 0 個或 1 個前面的字元(或組),例如,路由路徑'/ab?cd'將匹配端點acd或abcd。+:端點必須有 1 個或多個前面的字元(或組),例如,路由路徑'/ab+cd'將匹配端點abcd、abbcd、abbbcd等。*:端點可以在放置*字元的位置具有任意字串。例如,路由路徑'/ab*cd'將匹配端點abcd、abXcd、abSOMErandomTEXTcd等。():對一組字元進行分組匹配以對其執行其他操作,例如,'/ab(cd)?e'將對組(cd)執行?匹配——它將匹配abe和abcde。
路由路徑也可以是 JavaScript 正則表示式。例如,下面的路由路徑將匹配catfish和dogfish,但不會匹配catflap、catfishhead等。請注意,正則表示式的路徑使用正則表示式語法(它不是像以前的情況那樣是帶引號的字串)。
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。我們可以如下所示提取這些資訊,其中userId和bookId是路徑引數
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 設定路由和控制器。
處理路由函式中的錯誤
前面顯示的路由函式都具有req和res引數,它們分別表示請求和響應。路由函式也使用第三個引數next呼叫,該引數可用於將錯誤傳遞給 Express 中介軟體鏈。
以下程式碼展示了它是如何工作的,使用資料庫查詢的示例,該查詢採用回撥函式,並返回錯誤err或一些結果。如果返回err,則使用err作為其第一個引數的值呼叫next(最終錯誤會傳播到我們的全域性錯誤處理程式碼)。成功時,返回所需資料,然後在響應中使用。
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 程式碼塊中編寫路由函式,如下所示
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 程式碼塊和轉發錯誤的程式碼。同一個示例現在非常簡單,因為我們只需要編寫假設成功情況下的程式碼即可
// 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 建立路由處理程式回撥函式和路由程式碼。
建立路由處理程式回撥函式
在定義路由之前,我們將首先建立所有它們將呼叫的虛擬/骨架回調函式。回撥函式將儲存在 Book、BookInstance、Genre 和 Author 的單獨“控制器”模組中(您可以使用任何檔案/模組結構,但這對於本專案來說似乎是一個合適的粒度)。
首先為專案根目錄中的控制器建立一個資料夾 (/controllers),然後為每個模型建立單獨的控制器檔案/模組
/express-locallibrary-tutorial //the project root
/controllers
authorController.js
bookController.js
bookinstanceController.js
genreController.js
控制器將使用 express-async-handler 模組,因此在繼續之前,請使用 npm 將其安裝到庫中
npm install express-async-handler
作者控制器
開啟 /controllers/authorController.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 控制器模組相同的模式)
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 檔案並複製以下文字(這遵循與 Author 和 BookInstance 檔案相同的模式)
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() 函式
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 資料夾,其中包含 index 和 users 的路由。在此資料夾中建立另一個路由檔案——catalog.js——如下所示。
/express-locallibrary-tutorial //the project root
/routes
index.js
users.js
catalog.js
開啟 /routes/catalog.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 並將現有路由替換為以下函式。
// GET home page.
router.get("/", function (req, res) {
res.redirect("/catalog");
});
注意:這是我們第一次使用redirect() 響應方法。這將重定向到指定的頁面,預設情況下發送 HTTP 狀態程式碼“302 Found”。如果需要,您可以更改返回的狀態程式碼,並提供絕對或相對路徑。
更新 app.js
最後一步是將路由新增到中介軟體鏈中。我們在 app.js 中執行此操作。
開啟 app.js 並在其他路由下方需要 catalog 路由(新增下面顯示的第三行,在檔案中原有的另外兩行下方)
var indexRouter = require("./routes/index");
var usersRouter = require("./routes/users");
const catalogRouter = require("./routes/catalog"); //Import routes for "catalog" area of site
接下來,將 catalog 路由新增到其他路由下方的中介軟體堆疊中(新增下面顯示的第三行,在檔案中原有的另外兩行下方)
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/cataloghttps://:3000/catalog/bookshttps://:3000/catalog/bookinstances/https://:3000/catalog/authors/https://:3000/catalog/genres/https://:3000/catalog/book/5846437593935e2f8c2aa226https://:3000/catalog/book/create
總結
我們現在為我們的網站建立了所有路由,以及我們可以用完整實現填充的虛擬控制器函式,這些函式將在以後的文章中介紹。在此過程中,我們瞭解了許多關於 Express 路由、處理異常以及構建路由和控制器的某些方法的基本資訊。
在下一篇文章中,我們將使用檢視(模板)和儲存在模型中的資訊為站點建立一個合適的歡迎頁面。