Express 教學 4: 路由與控制器

在本教程中,我們將為最終在 本地圖書館 網站中需要的所有資源端點,搭配 "空殼" 處理函式來配置路由 (URL handling code) 。完成後,我們的路由處理源碼將會有模組化結構,在接下來的文章中,我們可以用真實的處理函式加以擴充。我們也會對如何使用 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中間件,因為它允許我們將站點的特定部分的路由處理程序組合在一起,並使用通用的路由前綴訪問它們。我們會將所有與圖書館有關的路由,保存在「目錄」模塊中,如果我們添加路由來處理用戶帳戶或其他功能,我們可以將它們分開保存。

備註: 我們在Express 簡介>創建路由處理程序中,簡要討論了 Express 應用程序路由。除了為模塊化提供更好的支持之外(如下面第一小節所述),使用 Router 非常類似於直接在 Express 應用程序對像上定義路由。

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

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

下面的代碼提供了一個具體示例,說明我們如何創建路由模塊,然後在 Express 應用程序中使用它。首先,我們在一個名為wiki.js的模塊中創建一個 wiki 的路由。代碼首先導入 Express 應用程序對象,使用它獲取一個

Router對象,然後使用get()方法向其添加一對路由。所有模塊的最後一個導出路由器Router對象。

js
// wiki.js - Wiki route module.

var express = require("express");
var 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(),將路由器添加到中間件處理路徑,並指定一個'wiki'的 URL 路徑。

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

然後可以從/wiki//wiki/about/,訪問我們的 wiki 路由模塊中定義的兩個路由。

路由函數

我們上面的模塊,定義了幾個典型的路由功能。使用Router.get()方法定義「about」路由(在下面),該方法僅響應 HTTP GET 請求。此方法的第一個參數是 URL 路徑,而第二個參數是一個回調函數,如果收到帶有路徑的 HTTP GET 請求,將會調用該函數。

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

回調函數接受三個參數(通常如下所示命名:req, res, next),它將包含 HTTP 請求對象,HTTP 響應,以及中間件鏈中的下一個函數。

備註: 路由器功能是Express 中間件,這意味著它們必須完成(響應)請求或調用鏈中的下一個功能next。在上面的例子中,我們使用send()完成了請求,所以下一個參數next沒有被使用(我們選擇不指定它)。

上面的路由器函數只需要一次回調,但你可以根據需要指定任意數量的回調參數,或一組回調函數。每個函數都是中間件鏈的一部分,並且將按照添加到鏈中的順序調用(除非前面的函數完成請求)。

這裡的回調函數,在響應中調用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 請求。

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

路由路徑

路由路徑定義可以進行請求的端點。我們到目前為止看到的例子,都是字符串,並且完全按照字符串的寫法使用:'/','/ about','/ book','/any-random.path'。

路由路徑也可以是字符串模式。字符串模式使用正則表達式語法的子集,來定義將匹配的端點模式。下面列出了子集(請注意,連字符(-)和點(.)由字符串路徑字面解釋):

  • ? :端點在?號前面的那個字符,必須為 0 個或 1 個。例如。'/ab?cd'的路徑路徑將匹配端點acdabcd
  • + :端點在+號前面的那個字符,必須為 1 個或多個。例如,'/ab+cd'的路徑路徑將與端點abcdabbcdabbbcd等匹配。
  • * :端點在放置*字符的地方,可以代換為任意字符串。例如。'ab\*cd'的路由路徑,將匹配端點abcd, abXcd, abSOMErandomTEXTcd等。
  • () :將一組字符進行匹配,以執行上面三個操作。例如。'/ab(cd)?e',表示以?號對(cd)進行匹配-它會匹配abeabcde。(譯註:即(cd)必須為 0 個或 1 個。若為 0,匹配abe。若為 1,匹配abcde

路由路徑也可以是JavaScript 正則表達式。例如,下面的路由路徑將與鯰魚catfish 和角鯊魚dogfish相匹配,但不包括鯰魚catflap、鯰魚頭catfishhead等。請注意,正則表達式的路徑使用正則表達式語法(它不像以前那樣,是帶引號的字符串)。

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

備註: LocalLibrary 的大部分路由,都只使用字符串,而不是字符串模式和正則表達式。我們還將使用下一節中討論的路由參數。

路由參數

路徑參數是命名的 URL 段,用於捕獲在 URL 中的位置指定的值。命名段以冒號為前綴,然後是名稱(例如。捕獲的值,使用參數名稱作為鍵,存在對像中(例如)。/:your_parameter_name/ req.params req.params.your_parameter_name

例如,考慮一個編碼的 URL,其中包含有關用戶和書本的信息:http://localhost:3000/users/34/books/8989。我們可以使用userIdbookId路徑參數,提取如下所示的信息:

app.get('/users/:userId/books/:bookId', function (req, res) {
  // Access userId via: req.params.userId
  // Access bookId via: req.params.bookId
  res.send(req.params);
})

路由參數的名稱,必須由「單詞字符」(AZ,az,0-9 和_)組成。

備註: URL /book/create將與/book/:bookId 之類的路由匹配(它將提取要創建' create'的「bookId」值)。將使用與傳入 URL 匹配的第一個路由,因此,如果要單獨處理/book/createURL,則必須在/book/:bookId路由之前,先定義其路由處理程序。

這就是你開始使用路由所需的全部內容-如果需要,你可以在 Express 文檔中找到更多信息:基本路由路由指南。以下部分顯示了我們如何為 LocalLibrary 設置路由和控制器。

本地圖書館需要的路由

下面列出了我們最終需要用於頁面的 URL,其中 object 被替換為每個模型的名稱(book,bookinstance,genre,author),objects 是對象的複數,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 中(如上面的<id>)。

我們將使用路徑參數,來提取編碼信息,並將其傳遞給路由處理程序(在稍後的文章中,我們將使用它來動態確定從數據庫獲取的信息)。通過對我們的 URL 中的信息進行編碼,我們只需要一個路由,用於特定類型的每個資源(例如,一個路由來處理每個書本項目的顯示)。

備註: Express 允許你以任何方式構建 URL -你可以在 URL 正文中編碼信息,就像上面一樣,或使用 URL GET參數(例如/book/?id=6)。無論你使用哪種方法,URL 都應保持乾淨,合理且可讀(請在此處查看W3C 建議)。

接下來,我們為所有上述 URL,創建路由處理程序回調函數和路由代碼。

創建路由-handler 回調函式

在我們定義路由之前,我們將首先創建它們將調用的所有虛擬/骨架回調函數。回調將存在 Books,BookInstances,Genres 和 Authors 的單獨「控制器」 模塊中(你可以使用任何文件/模塊結構,但這似乎是該項目的適當粒度)。

首先在項目根目錄(/controllers)中,為我們的控制器創建一個文件夾,然後創建單獨的控制器文件/模塊,來處理每個模型:

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

作者控制器

打開 /controllers/authorController.js 文件,並複制以下代碼:

js
var Author = require("../models/author");

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

// Display detail page for a specific Author.
exports.author_detail = function (req, res) {
  res.send("NOT IMPLEMENTED: Author detail: " + req.params.id);
};

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

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

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

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

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

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

該模塊首先導入我們稍後將使用的模型,來訪問和更新我們的數據。然後它為我們希望處理的每個 URL,導出函數(創建,更新和刪除操作使用表單,因此還有其他方法,來處理表單發布請求——我們將在稍後的「表單文章」中討論這些方法) 。

所有函數都具有 Express 中間件函數的標準形式,如果方法沒有完成請求週期,則會調用請求,響應和 next 下一個函數的參數(在所有這些情況下,它都會執行!)。這些方法只返回一個字符串,表明尚未創建關聯的頁面。如果期望控制器函數接收路徑參數,則在消息字符串中,輸出這些參數(參見上面的 req.params.id)。

書本實例控制器

打開 /controllers/bookinstanceController.js 文件,並將其複製到以下代碼中(它遵循與 Author 控制器模塊相同的模式):

js
var BookInstance = require("../models/bookinstance");

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

// Display detail page for a specific BookInstance.
exports.bookinstance_detail = function (req, res) {
  res.send("NOT IMPLEMENTED: BookInstance detail: " + req.params.id);
};

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

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

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

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

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

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

種類控制器

打開 /controllers/genreController.js 文件,並複制以下文本(這與 AuthorBookInstance 文件的模式相同):

js
var Genre = require("../models/genre");

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

// Display detail page for a specific Genre.
exports.genre_detail = function (req, res) {
  res.send("NOT IMPLEMENTED: Genre detail: " + req.params.id);
};

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

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

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

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

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

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

書本控制器

打開 /controllers/bookController.js 文件,並複制以下代碼。它遵循與其他控制器模塊相同的模式,但另外還有一個 index() 函數,用於顯示站點歡迎頁面:

js
var Book = require("../models/book");

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

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

// Display detail page for a specific book.
exports.book_detail = function (req, res) {
  res.send("NOT IMPLEMENTED: Book detail: " + req.params.id);
};

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

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

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

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

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

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

創建目錄路由模組

接下來,我們為 LocalLibrary 網站,創建所需全部 URL 的路由,這將調用我們在上一節中定義的控制器功能。

骨架網站已經有一個 ./routes 文件夾,其中包含索引和用戶的路由。在此文件夾中,創建另一個路徑文件——catalog.js——如下圖所示。

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

打開 /routes/catalog.js,複製下面的代碼:

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

// Require controller modules.
var book_controller = require("../controllers/bookController");
var author_controller = require("../controllers/authorController");
var genre_controller = require("../controllers/genreController");
var 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。

處理程序函數,都是從我們在上一節中,創建的控制器模塊導入的。

更新 index 路由模組

我們已經設置了所有新路由,但我們仍然有一個到原始頁面的路由。讓我們將其重定向,到我們在路徑『/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,並要求其他路由下方的目錄路由(添加下面顯示的第三行,在其他兩個路由下面):

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

接下來,將目錄路由,添加到其他路由下面的中間件堆棧(添加下面顯示的第三行,在其他兩行下面):

js
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,則可以使用:
    bash
    // Windows
    SET DEBUG=express-locallibrary-tutorial:* & npm run devstart
    
    // macOS or Linux
    DEBUG=express-locallibrary-tutorial:* npm run devstart
    

然後瀏覽一些上面的 LocalLibrary URL,並驗證你沒有收到錯誤頁面(HTTP 404)。為方便起見,下面列出了一小組網址:

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

總結

我們現在為網站創建了所有的路由,在稍後的教程,我們可以將實作完成的代碼,填入到空殼控制器函式。以這樣的方式,我們學到了許多關於 Express 路由的基本信息,以及一些組織路由和控制器的方式。

下一篇文章,我們將使用視圖(模板)和存在模型裡的信息,為網站創建一個合適的歡迎頁面。

參閱