Students Save 30%! Learn & create with unlimited courses & creative assets Students Save 30%! Save Now
Advertisement
  1. Code
  2. Express
Code

使用 ExpressJS 構建完整的 MVC 網站

by
Difficulty:IntermediateLength:LongLanguages:

Chinese (Traditional) (中文(繁體)) translation by Fuhuan (you can also view the original English article)

在本文中,我們將構建壹個包含前端客戶端的完整網站,以及用於管理網站內容的控制面板。 正如妳可能猜到的,應用程序的最終工作版本包含許多不同的文件。 我按照開發過程壹步壹步地編寫本教程,但是我沒有包含每個文件,因為這會使這篇文章變得非常冗長乏味。 但是,源代碼可以在 GitHub 上找到,我強烈建議妳看壹下。


介紹

Express 是 Node 的最佳框架之壹。 它有強大的支持和壹些有用的功能。 那裏有很多很棒的文章,涵蓋了所有的基礎知識。 但是,這次我想深入挖掘並分享我創建完整網站的工作流程。 通常,本文不僅適用於 Express,還適用於與 Node 開發人員可用的其他壹些優秀工具結合使用。

我假設妳熟悉 Nodejs,將它安裝在妳的系統上,並且妳可能已經使用它構建了壹些應用程序。

核心是連接。 這是壹個中間件框架,它帶有很多有用的東西。 如果妳想知道究竟什麽是中間件,這裏有壹個簡單的例子:

中間件基本上是壹個接受請求和響應對象以及下壹個功能的函數。 每個中間件都可以決定使用響應對象進行響應,或者通過調用下壹個回調將流傳遞給下壹個函數。 在上面的示例中,如果在第二個中間件中刪除 next()方法調用,則 hello world 字符串將永遠不會發送到瀏覽器。 壹般來說,這就是 Express 的工作方式。 有壹些預定義的中間件,當然,這可以為妳節省大量時間。 例如,Body 解析器解析請求主體並支持 application / json,application / x-www-form-urlencoded 和 multipart / form-data。 或 Cookie 解析器,它解析 cookie 頭並使用由 cookie 名稱鍵入的對象填充 req.cookies。

Express 實際上包裝了 Connect 並在其周圍添加了壹些新功能。 例如,路由邏輯,這使得流程更加順暢。 以下是處理 GET 請求的示例:


建立

設置 Express 有兩種方法。 第壹個是將它放在妳的 package.json 文件中並運行 npm install(有壹個笑話,npm 意味著沒問題的人 :))。

框架的代碼將放在 node_modules 中,妳將能夠創建它的實例。 但是,我更喜歡使用命令行工具的替代選項。 只需使用 npm install -g express 全局安裝 Express 。 通過這樣做,妳現在擁有了壹個全新的 CLI 工具。 例如,如果妳運行:

Express 將創建壹個應用程序框架,其中包含壹些已為妳配置的內容。 以下是 express(1)命令的用法選項:

正如妳所看到的,只有幾個選項,但對我來說它們已經足夠了。 通常我使用較少的 CSS 預處理器和 hogan 作為模板引擎。 在這個例子中,我們還需要會話支持,所以 --sessions 參數解決了這個問題。 上面的命令完成後,我們的項目如下所示:

如果妳查看 package.json 文件,妳將看到我們需要的所有依賴項都添加到此處。 雖然它們尚未安裝。 為此,只需運行 npm install,然後會彈出 node_modules 文件夾。

我意識到上述方法並不總是合適的。 妳可能希望將路由處理程序放在另壹個目錄或類似的東西中。 但是,正如妳將在接下來的幾章中看到的那樣,我將對已經生成的結構進行更改,這很容易做到。 因此,妳應該將 express(1)命令視為樣板生成器。


敏捷開發

在本教程中,我設計了壹個名為 FastDelivery 的虛假公司的簡單網站。 這是完整設計的屏幕截圖:

site

在本教程結束時,我們將擁有壹個完整的 Web 應用程序,其中包含壹個可用的控制面板。 我們的想法是在不同的限制區域內管理網站的每個部分。 布局是在 Photoshop 中創建的,並切成 CSS(less)和 HTML(hogan)文件。 現在,我不打算介紹切片過程,因為它不是本文的主題,但如果妳對此有任何疑問,請不要猶豫。 切片後,我們有以下文件和 app 結構:

以下是我們要管理的網站元素列表:

  • 主頁(中間的橫幅 - 標題和文字)
  • 博客(添加,刪除和編輯文章)
  • 服務頁面
  • 職業頁面
  • 聯系頁面

組態

在開始真正的實施之前,我們必須做壹些事情。 配置設置就是其中之壹。 讓我們假設我們的小站點應該部署到三個不同的位置 - 本地服務器,登臺服務器和生產服務器。 當然,每個環境的設置都不同,我們應該實現壹個足夠靈活的機制。 如妳所知,每個節點腳本都作為控制臺程序運行。 因此,我們可以輕松發送將定義當前環境的命令行參數。 我將該部分包裝在壹個單獨的模塊中,以便稍後為其編寫測試。 這是 /config/index.js 文件:

目前只有兩種設置 - 模式和端口。 正如妳可能猜到的,應用程序為不同的服務器使用不同的端口。 這就是為什麽我們必須在 app.js 中更新站點的入口點。

要在配置之間切換,只需在最後添加環境。 例如:

會產生:

現在我們將所有設置放在壹個地方,並且它們易於管理。


測試

我是 TDD 的忠實粉絲。 我將嘗試涵蓋本文中使用的所有基類。 當然,對絕對壹切進行測試會使寫作時間過長,但總的來說,這就是在創建自己的應用程序時應該如何進行。 我最喜歡的測試框架之壹是茉莉花。 當然它在 npm 註冊表中可用:

讓我們創建壹個測試目錄來保存我們的測試。 我們要檢查的第壹件事是配置設置 spec 文件必須以.spec.js 結尾,因此該文件應該被稱為 config.spec.js。

運行 jasmine-node ./tests 妳應該看到以下內容:

這壹次,我首先編寫了實現,然後編寫了第二個測試。 這並不是 TDD 的做事方式,但在接下來的幾章中我會做相反的事情。

我強烈建議花大量時間編寫測試。 沒有比完全測試的應用程序更好的了。

幾年前,我意識到壹些非常重要的東西,它可以幫助妳制作更好的程序。 每次開始編寫新類,新模塊或新邏輯時,請問自己:

我該怎麽測試呢?

我該怎麽測試呢?這個問題的答案將幫助妳更有效地編寫代碼,創建更好的 API,並將所有內容放入分離良好的塊中。 妳不能為意大利代碼編寫測試。 例如,在上面的配置文件中(/config/index.js) 我添加了在模塊的構造函數中發送模式的可能性。 妳可能想知道,當主要想法是從命令行參數獲取模式時,為什麽要這樣做? 這很簡單...... 因為我需要測試它。 讓我們假設壹個月後我需要檢查生產配置中的某些內容,但是節點腳本是使用 staging 參數運行的。 沒有那麽小的改進,我將無法做出這種改變。 之前的壹個小步驟現在實際上可以防止將來出現問題。


數據庫

由於我們正在構建壹個動態網站,我們需要壹個數據庫來存儲我們的數據。 我選擇使用 mongodb 作為本教程。 Mongo 是壹個 NoSQL 文檔數據庫。 可以在此處找到安裝說明,因為我是 Windows 用戶,所以我遵循 Windows 安裝。 完成安裝後,運行 MongoDB 守護程序,默認情況下偵聽端口 27017。 因此,理論上,我們應該能夠連接到此端口並與 mongodb 服務器通信。 要從節點腳本執行此操作,我們需要壹個 mongodb 模塊 / 驅動程序。 如果妳下載了本教程的源文件,則該模塊已添加到 package.json 文件中。 如果沒有,只需將 “mongodb”:“1.3.10” 添加到妳的依賴項並運行 npm install。

接下來,我們將編寫壹個測試,檢查是否有 mongodb 服務器在運行。 /tests/mongodb.spec.js 文件:

mongodb 客戶端的.connect 方法中的回調接收 db 對象。 我們稍後將使用它來管理我們的數據,這意味著我們需要在模型中訪問它。 每當我們必須向數據庫發出請求時,創建壹個新的 MongoClient 對象並不是壹個好主意。 這就是為什麽我在 connect 函數的回調中移動了快速服務器的運行:

更好的是,由於我們有配置設置,最好將 mongodb 主機和端口放在那裏,然後將連接 URL 更改為:

密切關註中間件:attachDB,我在調用 http.createServer 函數之前添加了它。 由於這壹點添加,我們將填充請求對象的.db 屬性。 好消息是我們可以在路徑定義期間附加多個功能。 例如:

因此,Express 會事先調用 attachDB 來到達我們的路由處理程序。 壹旦發生這種情況,請求對象將具有.db 屬性,我們可以使用它來訪問數據庫。


MVC

我們都知道 MVC 模式。 問題是這如何適用於 Express。 或多或少,這是壹個解釋問題。 在接下來的幾章中,我將創建模塊,它們充當模型,視圖和控制器。

模型

該模型將處理我們的應用程序中的數據。 它應該可以訪問 MongoClient 返回的 db 對象。 我們的模型還應該有壹個擴展它的方法,因為我們可能想要創建不同類型的模型。 例如,我們可能需要 BlogModel 或 ContactsModel。 所以我們需要編寫壹個新的規範:/tests/base.model.spec.js,以便測試這兩個模型的功能。 請記住,通過在開始編寫實現之前定義這些功能,我們可以保證我們的模塊將只執行我們希望它執行的操作。

我決定傳遞壹個模型對象,而不是壹個真正的 db 對象。 那是因為稍後,我可能想要測試壹些具體的東西,這取決於來自數據庫的信息。 手動定義這些數據會容易得多。

extend 方法的實現有點棘手,因為我們必須更改 module.exports 的原型,但仍保留原始構造函數。 值得慶幸的是,我們已經編寫了壹個很好的測試,證明我們的代碼可以工作。 通過上述內容的版本如下所示:

這裏有兩種輔助方法。 db 對象的 setter 和數據庫集合的 getter 。

視圖

視圖將向屏幕呈現信息。 本質上,視圖是壹個向瀏覽器發送響應的類。 Express 提供了壹種簡短的方法:

響應對象是壹個包裝器,它有壹個很好的 API,使我們的生活更輕松。 但是,我更願意創建壹個封裝此功能的模塊。 默認視圖目錄將更改為模板,並將創建壹個新模板,它將承載基本視圖類。 這壹小改變現在需要另壹個改變。 我們應該通知 Express 我們的模板文件現在放在另壹個目錄中:

首先,我將定義我需要的內容,編寫測試,然後編寫實現。 我們需要壹個符合以下規則的模塊:

  • 其構造函數應該接收響應對象和模板名稱。
  • 它應該有壹個接受數據對象的 render 方法。
  • 它應該是可擴展的。

妳可能想知道我為什麽要擴展 View 類。 是不是只是調用 response.render 方法? 實際上,在某些情況下,妳需要發送不同的標頭或以某種方式操縱響應對象。 例如,提供 JSON 數據:

不要每次都這樣做,最好有壹個 HTMLView 類和壹個 JSONView 類。 甚至是用於將 XML 數據發送到瀏覽器的 XMLView 類。 如果妳建立壹個大型網站,那就更好了,包裝這些功能,而不是壹遍又壹遍地復制粘貼相同的代碼。

這是 /views/Base.js 的規範:

為了測試渲染,我不得不創建壹個模型。 在這種情況下,我創建了壹個模仿 Express 的響應對象的對象。 在測試的第二部分中,我創建了另壹個 View 類,它繼承了基類並應用了自定義 render 方法。 這是 /views/Base.js 類。

現在我們在 tests 目錄中有三個規範,如果妳運行 jasmine-node ./tests,結果應該是:

調節器

還記得路線以及它們的定義方式嗎?

在 “/” 後途徑,其在上面的例子中,實際上是控制器。 它只是壹個接受請求,響應和下壹個的中間件功能。

以上是妳的控制器應該在 Express 的上下文中的外觀。 該快遞(1)命令行工具創建壹個新的目錄路徑,但在我們的情況下,更好地為它被命名為控制器,所以我改變,以反映這壹命名方案。

因為我們不只是構建壹個非常小的應用程序,所以如果我們創建壹個基類,我們可以擴展它將是明智的。 如果我們需要將某種功能傳遞給所有控制器,那麽這個基類將是最佳選擇。 再次,我將首先編寫測試,所以讓我們定義我們需要的東西:

  • 它應該有壹個 extend 方法,它接受壹個對象並返回壹個新的子實例
  • 子實例應該有壹個 run 方法,這是舊的中間件函數
  • 應該有壹個 name 屬性,用於標識控制器
  • 我們應該能夠基於類創建獨立的對象

所以現在只是壹些事情,但我們可能會在以後添加更多功能。 測試看起來像這樣:

這是 /controllers/Base.js 的實現:

當然,每個子類都應該定義自己的 run 方法以及它自己的邏輯。


FastDelivery 網站

好的,我們為 MVC 架構提供了壹套很好的類,我們已經用測試覆蓋了新創建的模塊。 現在我們準備繼續使用我們的假公司 FastDelivery 的網站。 讓我們假設該網站有兩個部分 - 前端和管理面板。 前端將用於向最終用戶顯示數據庫中寫入的信息。 管理面板將用於管理該數據。 讓我們從我們的管理(控制)面板開始。

控制面板

讓我們首先創建壹個簡單的控制器,它將作為管理頁面。 /controllers/Admin.js 文件:

通過為控制器和視圖使用預先編寫的基類,我們可以輕松地為控制面板創建入口點 該視圖類接受壹個模板文件的名稱。 根據上面的代碼,該文件應該被稱為 admin.hjs,並且應該放在 / templates 中。 內容看起來像這樣:

(為了使本教程保持相當簡短且易於閱讀的格式,我不打算展示每個視圖模板。 我強烈建議妳從 GitHub 下載源代碼。)

現在要使控制器可見,我們必須在 app.js 中添加壹個路徑:

請註意,我們不是直接將 Admin.run 方法作為中間件發送。 那是因為我們想要保持上下文。 如果我們這樣做:

單詞本中管理員將指向別的東西。

保護管理面板

每個以 / admin 開頭的頁面都應受到保護。 為實現這壹目標,我們將使用 Express 的中間件:Sessions。 它只是將壹個對象附加到名為 session 的請求上。 我們現在應該更改管理控制器以執行另外兩項操作:

  • 它應檢查是否有可用的會話。 如果沒有,則顯示登錄表單。
  • 它應接受登錄表單發送的數據,並在用戶名和密碼匹配時授權用戶。

這是壹個我們可以用來完成這個的小輔助函數:

首先,我們有壹個聲明嘗試通過會話對象識別用戶。 其次,我們檢查表格是否已提交。 如果是這樣,表單中的數據在 request.body 對象中可用,該對象由 bodyParser 中間件填充。 然後我們只檢查用戶名和密碼是否匹配。

現在這裏是控制器的 run 方法,它使用我們的新助手。 我們檢查用戶是否被授權,顯示控制面板本身,否則我們顯示登錄頁面:

管理內容

正如我在本文開頭所指出的那樣,我們有很多東西需要管理。 為簡化過程,我們將所有數據保存在壹個集合中。 每條記錄都有標題,文字,圖片和類型屬性。 該類型屬性將決定記錄的所有者。 例如,“聯系人” 頁面只需要壹個類型為 “聯系人” 的記錄,而 “博客” 頁面則需要更多記錄。 因此,我們需要三個新頁面來添加,編輯和顯示記錄。 在我們開始創建新模板,樣式和將新內容放入控制器之前,我們應該編寫我們的模型類,它位於 MongoDB 服務器和我們的應用程序之間,當然還提供了壹個有意義的 API。

該模型負責為每條記錄生成唯壹的 ID。 我們將需要它以便稍後更新信息。

如果我們想為 “聯系人” 頁面添加新記錄,我們可以簡單地使用:

因此,我們有壹個很好的 API 來管理我們的 mongodb 集合中的數據。 現在我們準備編寫用於使用此功能的 UI。 對於此部分,Admin 控制器需要進行相當多的更改。 為了簡化任務,我決定將添加的記錄列表與添加 / 編輯它們的表單結合起來。 正如妳在下面的屏幕截圖中看到的那樣,頁面左側部分是為列表保留的,右側部分是為表單保留的。

control-panel

將所有內容放在壹個頁面上意味著我們必須關註呈現頁面的部分,或者更具體地說明我們發送到模板的數據。 這就是為什麽我創建了幾個組合的輔助函數,如下所示:

它看起來有點難看,但它可以按我的意願工作。 第壹個幫助器是 del 方法,它檢查當前的 GET 參數,如果找到 action = delete&id = [記錄的 id],它將從集合中刪除數據。 第二個函數稱為表單,它主要負責顯示頁面右側的表單。 它檢查表單是否已提交並正確更新或在數據庫中創建記錄。 最後,list 方法獲取信息並準備壹個 HTML 表,稍後將其發送到模板。 這三個助手的實現可以在本教程的源代碼中找到。

在這裏,我決定向妳展示處理文件上傳的功能:

如果提交了文件,請求對象的節點腳本.files 屬性將填充數據。 在我們的例子中,我們有以下 HTML 元素:

這意味著我們可以通過 req.files.picture 訪問提交的數據。 在上面的代碼片段中,req.files.picture.path 用於獲取文件的原始內容。 稍後,相同的數據將寫入新創建的目錄中,最後會返回正確的 URL。 所有這些操作都是同步的,但使用 readFileSync,mkdirSync 和 writeFileSync 的異步版本是壹個好習慣。

前端

努力工作現已完成。 管理面板正在運行,我們有壹個 ContentModel 類,它允許我們訪問存儲在數據庫中的信息。 我們現在要做的是編寫前端控制器並將它們綁定到保存的內容。

這是主頁的控制器 - /controllers/Home.js

主頁需要壹個具有主頁類型的記錄和四個具有博客類型的記錄。 控制器完成後,我們只需要在 app.js 中添加壹個路徑:

同樣,我們將 db 對象附加到請求。 幾乎與管理面板中使用的工作流程相同。

我們的前端(客戶端)的其他頁面幾乎完全相同,因為它們都有壹個控制器,它通過使用模型類獲取數據,當然還有定義的路徑。 有兩個有趣的情況我想更詳細地解釋壹下。 第壹個與博客頁面相關。 它應該能夠顯示所有文章,但也只能呈現壹個。 所以,我們必須註冊兩條路線:

它們都使用相同的控制器:Blog,但調用不同的運行方法。 註意 / blog /:id 字符串。 這條路線將匹配像 URL / 博客 / 4e3455635b4a6f6dccfaa1e50ee71f1cde75222b 和長哈希將可 req.params.id。 換句話說,我們能夠定義動態參數。 在我們的例子中,這是記錄的 ID。 獲得此信息後,我們就可以為每篇文章創建壹個唯壹的頁面。

第二個有趣的部分是我如何構建服務,職業和聯系人頁面。 很明顯,他們只使用數據庫中的壹條記錄。 如果我們必須為每個頁面創建壹個不同的控制器,那麽我們必須復制 / 粘貼相同的代碼,只需更改類型字段。 有壹種更好的方法來實現這壹點,只有壹個控制器,它接受其 run 方法中的類型。 以下是路線:

控制器看起來像這樣:


部署

部署基於 Express 的網站實際上與部署任何其他 Node.js 應用程序相同:

  • 這些文件放在服務器上。
  • 應該停止節點進程(如果它正在運行)。
  • 壹個 NPM 安裝命令應以安裝新的依賴(如果有的話)來運行。
  • 然後應該再次運行主腳本。

請記住,Node 仍然相當年輕,所以並非所有內容都可以按預期工作,但總是會有改進。 例如,永遠保證妳的 Nodejs 程序將連續運行。 妳可以通過發出以下命令來執行此操作:

這也是我在服務器上使用的內容。 這是壹個不錯的小工具,但它解決了壹個大問題。 如果妳只使用節點 yourapp.js 運行妳的應用程序,壹旦妳的腳本意外退出,服務器就會關閉。 只需重新啟動應用程序。

現在我不是系統管理員,但我想分享我將節點應用程序與 Apache 或 Nginx 集成的經驗,因為我認為這在某種程度上是開發工作流程的壹部分。

如妳所知,Apache 通常在端口 80 上運行,這意味著如果妳打開 http:// localhost 或 http:// localhost:80,妳將看到 Apache 服務器提供的頁面,並且很可能妳的節點腳本正在偵聽不同的港口。 因此,妳需要添加壹個接受請求的虛擬主機並將它們發送到正確的端口。 例如,假設我想在 expresscompletewebsite.dev 地址下的本地 Apache 服務器上托管我們剛剛構建的站點。 我們要做的第壹件事是將我們的域添加到 hosts 文件中。

之後,我們必須編輯 Apache 配置目錄下的 httpd-vhosts.conf 文件並添加

服務器仍然接受端口 80 上的請求,但是將它們轉發到節點正在偵聽的端口 3000。

Nginx 設置要容易得多,說實話,它是托管基於 Nodejs 的應用程序的更好選擇。 妳仍然需要在 hosts 文件中添加域名。 之後,只需在 Nginx 安裝下的 / sites-enabled 目錄中創建壹個新文件。 該文件的內容如下所示:

請記住,妳無法使用上述主機設置同時運行 Apache 和 Nginx。 這是因為它們都需要端口 80. 此外,如果妳計劃在生產環境中使用上述代碼段,妳可能需要對更好的服務器配置進行壹些額外的研究。 正如我所說,我不是這方面的專家。


結論

Express 是壹個很棒的框架,它為妳提供了壹個開始構建應用程序的良好起點。 正如妳所看到的,關於如何擴展它以及妳將使用它構建的內容,這是壹個選擇問題。 它通過使用壹些很棒的中間件簡化了枯燥的任務,並為開發人員留下了有趣的部分。

源代碼

我們構建的此示例站點的源代碼可在 GitHub 上獲得 - https://github.com/tutsplus/build-complete-website-expressjs。 可以隨意保存並修改它。 以下是運行網站的步驟。

  • 下載源代碼
  • 轉到 app 目錄
  • 運行 npm install
  • 運行 mongodb 守護程序
  • 運行節點 app.js
Envato qr branded
关注我们的公众号
Advertisement
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.