Express 教程第四部分:路由和控制器
在本教程中,我們將為 LocalLibrary 網站中最終需要的所有資源端點設定路由(URL 處理程式碼)和“虛擬”處理函式。完成後,我們將為路由處理程式碼提供一個模組化結構,我們可以在後續文章中用實際的處理函式來擴充套件它。我們還將對如何使用 Express 建立模組化路由有一個很好的理解!
| 預備知識 | 閱讀 Express/Node 簡介。完成之前的教程主題(包括 Express 教程第三部分:使用資料庫(Mongoose))。 |
|---|---|
| 目標 | 瞭解如何建立簡單路由。設定我們所有的 URL 端點。 |
概述
在上一篇教程文章中,我們定義了 Mongoose 模型來與資料庫互動,並使用一個(獨立的)指令碼來建立一些初始的庫記錄。現在我們可以編寫程式碼來向用戶展示這些資訊。我們需要做的第一件事是確定我們希望能夠在頁面中顯示哪些資訊,然後定義用於返回這些資源的相應 URL。然後我們需要建立路由(URL 處理程式)和檢視(模板)來顯示這些頁面。
下圖提供了 HTTP 請求/響應處理時需要實現的資料流和主要事項的提醒。除了檢視和路由之外,該圖還顯示了“控制器”——將路由請求的程式碼與實際處理請求的程式碼分離開來的函式。
由於我們已經建立了模型,因此我們主要需要建立的是
- “路由”將支援的請求(以及請求 URL 中編碼的任何資訊)轉發到適當的控制器函式。
- 控制器函式,用於從模型中獲取請求的資料,建立顯示資料的 HTML 頁面,並將其返回給使用者在瀏覽器中檢視。
- 控制器用於渲染資料的檢視(模板)。

最終我們可能會有頁面來顯示圖書、型別、作者和圖書例項的列表和詳細資訊,以及建立、更新和刪除記錄的頁面。在一篇文章中記錄這麼多內容是很困難的。因此,本文大部分內容將集中在設定我們的路由和控制器以返回“虛擬”內容上。我們將在後續文章中擴充套件控制器方法以處理模型資料。
下面的第一部分簡要介紹瞭如何使用 Express 的 Router 中介軟體。然後我們將在以下部分設定 LocalLibrary 路由時使用這些知識。
路由入門
路由是 Express 程式碼的一個部分,它將 HTTP 動詞(GET、POST、PUT、DELETE 等)、URL 路徑/模式以及一個用於處理該模式的函式關聯起來。
有多種方法可以建立路由。在本教程中,我們將使用 express.Router 中介軟體,因為它允許我們將網站特定部分的路由處理程式分組在一起,並使用一個通用的路由字首來訪問它們。我們將所有與庫相關的路由儲存在“目錄”模組中,如果新增處理使用者帳戶或其他功能的路由,我們可以將它們單獨分組。
注意:我們在Express 簡介 > 建立路由處理程式中簡要討論了 Express 應用程式路由。除了為模組化提供更好的支援(如下面第一小節所述)之外,使用 Router 與直接在 Express 應用程式物件上定義路由非常相似。
本節的其餘部分概述瞭如何使用 Router 定義路由。
定義和使用單獨的路由模組
以下程式碼提供了一個具體的示例,說明我們如何建立路由模組,然後將其用於 Express 應用程式。
首先,我們建立了一個名為 wiki.js 的模組中的 wiki 路由。程式碼首先匯入 Express 應用程式物件,然後使用它獲取一個 Router 物件,並使用 get() 方法向其添加了幾個路由。最後,該模組匯出了 Router 物件。
// wiki.js - Wiki route module.
const express = require("express");
const router = express.Router();
// Home page route.
router.get("/", (req, res) => {
res.send("Wiki home page");
});
// About page route.
router.get("/about", (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/ 和 /wiki/about/ 訪問。
路由函式
我們上面的模組定義了幾個典型的路由函式。“about”路由(複製如下)是使用 Router.get() 方法定義的,該方法僅響應 HTTP GET 請求。此方法的第一個引數是 URL 路徑,第二個引數是接收到帶有該路徑的 HTTP GET 請求時將呼叫的回撥函式。
router.get("/about", (req, res) => {
res.send("About this wiki");
});
回撥函式接受三個引數(通常按所示命名:req、res、next),它們將包含 HTTP 請求物件、HTTP 響應以及中介軟體鏈中的 next 函式。
注意:路由函式是Express 中介軟體,這意味著它們必須完成(響應)請求或呼叫鏈中的 next 函式。在上面的情況下,我們使用 send() 完成請求,因此 next 引數未使用(我們選擇不指定它)。
上面的路由函式只接受一個回撥,但您可以指定任意數量的回撥引數,或者一個回撥函式陣列。每個函式都是中介軟體鏈的一部分,並將按照新增到鏈中的順序被呼叫(除非前面的函式完成了請求)。
此處的 callback 函式在響應上呼叫 send(),以在接收到路徑為 (/about) 的 GET 請求時返回字串“About this wiki”。還有許多其他響應方法用於結束請求/響應週期。例如,您可以呼叫 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'。
路由路徑也可以是字串模式。字串模式使用正則表示式語法的一種形式來定義將匹配的端點“模式”。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);
});
注意:URL /book/create 將由諸如 /book/:bookId 的路由匹配(因為 :bookId 是 任何 字串的佔位符,因此 create 匹配)。將使用與傳入 URL 匹配的第一個路由,因此如果您想專門處理 /book/create URL,則其路由處理程式必須在您的 /book/:bookId 路由之前定義。
路由引數名稱(例如上面的 bookId)可以是任何以字母、_ 或 $ 開頭的有效 JavaScript 識別符號。您可以在第一個字元之後包含數字,但不能包含連字元和空格。您還可以使用不是有效 JavaScript 識別符號的名稱,包括空格、連字元、表情符號或任何其他字元,但您需要使用帶引號的字串定義它們,並使用括號表示法訪問它們。例如
app.get('/users/:"user id"/books/:"book-id"', (req, res) => {
// Access quoted param using bracket notation
const user = req.params["user id"];
const book = req.params["book-id"];
res.send({ user, book });
});
萬用字元
萬用字元引數匹配多個段中的一個或多個字元,將每個段作為陣列中的一個值返回。它們的定義方式與常規引數相同,但以星號為字首。
因此,例如,考慮 URL https://:3000/users/34/books/8989,我們可以使用 example 萬用字元提取 users/ 之後的所有資訊
app.get("/users/*example", (req, res) => {
// req.params would contain { "example": ["34", "books", "8989"]}
res.send(req.params);
});
可選部分
花括號可用於定義路徑中的可選部分。例如,下面我們匹配帶有任何副檔名(或沒有副檔名)的檔名。
app.get("/file/:filename{.:ext}", (req, res) => {
// Given URL: https://:3000/file/somefile.md`
// req.params would contain { "filename": "somefile", "ext": "md"}
res.send(req.params);
});
保留字元
以下字元是保留的:(()[]?+!)。如果您想使用它們,必須用反斜槓 (\) 對它們進行轉義。
您也不能在正則表示式中使用管道符 (|)。
這就是開始使用路由所需的全部內容。如果需要,您可以在 Express 文件中找到更多資訊:基本路由和路由指南。以下部分將展示我們如何為 LocalLibrary 設定路由和控制器。
處理路由函式中的錯誤和異常
前面顯示的路由函式都帶有 req 和 res 引數,分別代表請求和響應。路由函式還會傳遞第三個引數 next,其中包含一個回撥函式,可以呼叫該函式將任何錯誤或異常傳遞給 Express 中介軟體鏈,最終它們將傳播到您的全域性錯誤處理程式碼。
從 Express 5 開始,如果路由處理程式返回的 Promise 隨後被拒絕,則會自動使用拒絕值呼叫 next;因此,在使用 Promise 時,路由函式中不需要錯誤處理程式碼。在使用非同步基於 Promise 的 API 時,這會導致非常緊湊的程式碼,尤其是在使用 async 和 await 時。
例如,以下程式碼使用 find() 方法查詢資料庫,然後渲染結果。
exports.get("/about", async (req, res, next) => {
const successfulResult = await About.find({}).exec();
res.render("about_view", { title: "About", list: successfulResult });
});
以下程式碼展示了使用 Promise 鏈的相同示例。請注意,如果您願意,您可以 catch() 錯誤並實現自己的自定義處理。
exports.get(
"/about",
// Removed 'async'
(req, res, next) =>
About.find({})
.exec()
.then((successfulResult) => {
res.render("about_view", { title: "About", list: successfulResult });
})
.catch((err) => {
next(err);
}),
);
注意:大多數現代 API 都是非同步且基於 Promise 的,因此錯誤處理通常就是這麼簡單。當然,這只是您在本教程中真正需要瞭解的錯誤處理!
Express 5 自動捕獲並轉發在同步程式碼中丟擲的異常
app.get("/", (req, res) => {
// Express will catch this
throw new Error("SynchronousException");
});
但是,您必須 catch() 路由處理程式或中介軟體呼叫的非同步程式碼中發生的異常。這些將不會被預設程式碼捕獲
app.get("/", (req, res, next) => {
setTimeout(() => {
try {
// You must catch and propagate this error yourself
throw new Error("AsynchronousException");
} catch (err) {
next(err);
}
}, 100);
});
最後,如果您使用的是舊式的非同步方法,這些方法在回撥函式中返回錯誤或結果,那麼您需要自己傳播錯誤。以下示例展示瞭如何操作。
router.get("/about", (req, res, next) => {
About.find({}).exec((err, queryResults) => {
if (err) {
// Propagate the error
return next(err);
}
// Successful, so render
res.render("about_view", { title: "About", list: queryResults });
});
});
更多資訊請參見錯誤處理。
LocalLibrary 所需的路由
我們將最終用於頁面的 URL 列在下面,其中 object 替換為每個模型(書、圖書例項、型別、作者)的名稱,objects 是 object 的複數形式,而 id 是每個 Mongoose 模型例項預設賦予的唯一例項欄位 (_id)。
catalog/— 主頁/索引頁。catalog/<objects>/— 所有書籍、圖書例項、型別或作者的列表(例如,/catalog/books/、/catalog/genres/等)catalog/<object>/<id>— 具有給定_id欄位值的特定圖書、圖書例項、型別或作者的詳細資訊頁面(例如,/catalog/book/584493c1f4887f06c0e67d37)。catalog/<object>/create— 建立新圖書、圖書例項、型別或作者的表單(例如,/catalog/book/create)。catalog/<object>/<id>/update— 用於更新具有給定_id欄位值的特定圖書、圖書例項、型別或作者的表單(例如,/catalog/book/584493c1f4887f06c0e67d37/update)。catalog/<object>/<id>/delete— 用於刪除具有給定_id欄位值的特定圖書、圖書例項、型別或作者的表單(例如,/catalog/book/584493c1f4887f06c0e67d37/delete)。
第一個主頁和列表頁不編碼任何額外資訊。雖然返回的結果將取決於模型型別和資料庫中的內容,但執行獲取資訊的查詢將始終相同(同樣,用於物件建立的程式碼也將始終相似)。
相比之下,其他 URL 用於對特定文件/模型例項進行操作——這些 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
作者控制器
開啟 /controllers/authorController.js 檔案並輸入以下程式碼
const Author = require("../models/author");
// Display list of all Authors.
exports.author_list = async (req, res, next) => {
res.send("NOT IMPLEMENTED: Author list");
};
// Display detail page for a specific Author.
exports.author_detail = async (req, res, next) => {
res.send(`NOT IMPLEMENTED: Author detail: ${req.params.id}`);
};
// Display Author create form on GET.
exports.author_create_get = async (req, res, next) => {
res.send("NOT IMPLEMENTED: Author create GET");
};
// Handle Author create on POST.
exports.author_create_post = async (req, res, next) => {
res.send("NOT IMPLEMENTED: Author create POST");
};
// Display Author delete form on GET.
exports.author_delete_get = async (req, res, next) => {
res.send("NOT IMPLEMENTED: Author delete GET");
};
// Handle Author delete on POST.
exports.author_delete_post = async (req, res, next) => {
res.send("NOT IMPLEMENTED: Author delete POST");
};
// Display Author update form on GET.
exports.author_update_get = async (req, res, next) => {
res.send("NOT IMPLEMENTED: Author update GET");
};
// Handle Author update on POST.
exports.author_update_post = async (req, res, next) => {
res.send("NOT IMPLEMENTED: Author update POST");
};
該模組首先需要我們將用於訪問和更新資料的 Author 模型。然後它匯出了我們將要處理的每個 URL 的函式。請注意,建立、更新和刪除操作使用表單,因此也有用於處理表單 POST 請求的附加方法——我們將在後面的“表單文章”中討論這些方法。
這些函式返回一個字串,指示相關頁面尚未建立。如果控制器函式預計會接收路徑引數,這些引數將輸出到訊息字串中(參見上方的 req.params.id)。
圖書例項控制器
開啟 /controllers/bookinstanceController.js 檔案並複製以下程式碼(這與 Author 控制器模組遵循相同的模式)
const BookInstance = require("../models/bookinstance");
// Display list of all BookInstances.
exports.bookinstance_list = async (req, res, next) => {
res.send("NOT IMPLEMENTED: BookInstance list");
};
// Display detail page for a specific BookInstance.
exports.bookinstance_detail = async (req, res, next) => {
res.send(`NOT IMPLEMENTED: BookInstance detail: ${req.params.id}`);
};
// Display BookInstance create form on GET.
exports.bookinstance_create_get = async (req, res, next) => {
res.send("NOT IMPLEMENTED: BookInstance create GET");
};
// Handle BookInstance create on POST.
exports.bookinstance_create_post = async (req, res, next) => {
res.send("NOT IMPLEMENTED: BookInstance create POST");
};
// Display BookInstance delete form on GET.
exports.bookinstance_delete_get = async (req, res, next) => {
res.send("NOT IMPLEMENTED: BookInstance delete GET");
};
// Handle BookInstance delete on POST.
exports.bookinstance_delete_post = async (req, res, next) => {
res.send("NOT IMPLEMENTED: BookInstance delete POST");
};
// Display BookInstance update form on GET.
exports.bookinstance_update_get = async (req, res, next) => {
res.send("NOT IMPLEMENTED: BookInstance update GET");
};
// Handle bookinstance update on POST.
exports.bookinstance_update_post = async (req, res, next) => {
res.send("NOT IMPLEMENTED: BookInstance update POST");
};
型別控制器
開啟 /controllers/genreController.js 檔案並複製以下文字(這與 Author 和 BookInstance 檔案遵循相同的模式)
const Genre = require("../models/genre");
// Display list of all Genre.
exports.genre_list = async (req, res, next) => {
res.send("NOT IMPLEMENTED: Genre list");
};
// Display detail page for a specific Genre.
exports.genre_detail = async (req, res, next) => {
res.send(`NOT IMPLEMENTED: Genre detail: ${req.params.id}`);
};
// Display Genre create form on GET.
exports.genre_create_get = async (req, res, next) => {
res.send("NOT IMPLEMENTED: Genre create GET");
};
// Handle Genre create on POST.
exports.genre_create_post = async (req, res, next) => {
res.send("NOT IMPLEMENTED: Genre create POST");
};
// Display Genre delete form on GET.
exports.genre_delete_get = async (req, res, next) => {
res.send("NOT IMPLEMENTED: Genre delete GET");
};
// Handle Genre delete on POST.
exports.genre_delete_post = async (req, res, next) => {
res.send("NOT IMPLEMENTED: Genre delete POST");
};
// Display Genre update form on GET.
exports.genre_update_get = async (req, res, next) => {
res.send("NOT IMPLEMENTED: Genre update GET");
};
// Handle Genre update on POST.
exports.genre_update_post = async (req, res, next) => {
res.send("NOT IMPLEMENTED: Genre update POST");
};
圖書控制器
開啟 /controllers/bookController.js 檔案並複製以下程式碼。這與其他的控制器模組遵循相同的模式,但額外有一個用於顯示站點歡迎頁面的 index() 函式
const Book = require("../models/book");
exports.index = async (req, res, next) => {
res.send("NOT IMPLEMENTED: Site Home Page");
};
// Display list of all books.
exports.book_list = async (req, res, next) => {
res.send("NOT IMPLEMENTED: Book list");
};
// Display detail page for a specific book.
exports.book_detail = async (req, res, next) => {
res.send(`NOT IMPLEMENTED: Book detail: ${req.params.id}`);
};
// Display book create form on GET.
exports.book_create_get = async (req, res, next) => {
res.send("NOT IMPLEMENTED: Book create GET");
};
// Handle book create on POST.
exports.book_create_post = async (req, res, next) => {
res.send("NOT IMPLEMENTED: Book create POST");
};
// Display book delete form on GET.
exports.book_delete_get = async (req, res, next) => {
res.send("NOT IMPLEMENTED: Book delete GET");
};
// Handle book delete on POST.
exports.book_delete_post = async (req, res, next) => {
res.send("NOT IMPLEMENTED: Book delete POST");
};
// Display book update form on GET.
exports.book_update_get = async (req, res, next) => {
res.send("NOT IMPLEMENTED: Book update GET");
};
// Handle book update on POST.
exports.book_update_post = 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");
// 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");
const router = express.Router();
/// 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("/", (req, res) => {
res.redirect("/catalog");
});
注意:這是我們第一次使用 redirect() 響應方法。此方法重定向到指定的頁面,預設情況下發送 HTTP 狀態碼“302 Found”。如果需要,您可以更改返回的狀態碼,並提供絕對或相對路徑。
更新 app.js
最後一步是將路由新增到中介軟體鏈中。我們在 app.js 中完成此操作。
開啟 app.js 並在其他路由下方引入目錄路由(新增下面顯示的第三行,在檔案中已有的另外兩行下方)
const indexRouter = require("./routes/index");
const usersRouter = require("./routes/users");
const catalogRouter = require("./routes/catalog"); // Import routes for "catalog" area of site
接下來,將目錄路由新增到其他路由下方的中介軟體堆疊中(新增下面顯示的第三行,在檔案中已有的另外兩行下方)
app.use("/", indexRouter);
app.use("/users", usersRouter);
app.use("/catalog", catalogRouter); // Add catalog routes to middleware chain.
注意:我們已在路徑 /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,則可以使用
bashnpm 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 路由、異常處理以及構建路由和控制器的一些方法的基本資訊。
在下一篇文章中,我們將使用檢視(模板)和模型中儲存的資訊為網站建立一個合適的歡迎頁面。