摘要:為應(yīng)用增加新的特性和處理新的情況可能都會改變文件的結(jié)構(gòu)。寫一個模板的最佳實踐是,不要在模板中處理數(shù)據(jù)。在上面這四個文件夾中,主要的測試代碼將是單元測試,這意味著你需要將被測試的代碼與應(yīng)用分離開來。
前言
Node和Express并不嚴(yán)格要求它的應(yīng)用的文件結(jié)構(gòu)。你可以以任意的結(jié)構(gòu)來組織你的web應(yīng)用。這對于小應(yīng)用來說,通常是不錯的,十分易于學(xué)習(xí)和實驗。
但是,當(dāng)你的應(yīng)用在體積和復(fù)雜性上都變得越來越高時,情況就變得復(fù)雜了。你的代碼可能會變得凌亂。當(dāng)你的團隊人數(shù)增加時,向在同一個代碼庫內(nèi)寫代碼變得愈發(fā)困難,每次合并代碼時都可能會出現(xiàn)各種各樣的沖突。為應(yīng)用增加新的特性和處理新的情況可能都會改變文件的結(jié)構(gòu)。
一個好的文件結(jié)構(gòu),應(yīng)該是每一個不同的文件或文件夾,都分別負(fù)責(zé)處理不同的任務(wù)。這樣,在添加新特性時才會變得不會有沖突。
最佳實踐這里所推薦的結(jié)構(gòu)是基于MVC設(shè)計模式的。這個模式在職責(zé)分離方面做得非常好,所以讓你的代碼更具有可維護性。在這里我們不會去過多地討論MVC的優(yōu)點,而是更多地討論如果使用它來建立你的Express應(yīng)用的文件結(jié)構(gòu)。
例子:
讓我們來看下面這個例子。這是一個用戶可以登錄,注冊,留下評論的應(yīng)用。以下是他的文件結(jié)構(gòu)。
project/ controllers/ comments.js index.js users.js helpers/ dates.js middlewares/ auth.js users.js models/ comment.js user.js public/ libs/ css/ img/ views/ comments/ comment.jade users/ index.jade tests/ controllers/ models/ comment.js middlewares/ integration/ ui/ .gitignore app.js package.json
這看上去可能有點復(fù)雜,但不要擔(dān)心。在讀完這篇文章之后,你將會完完全全地理解它。它本質(zhì)上是十分簡單的。
以下是對這個應(yīng)用中的根文件(夾)的作用的簡介:
controllers/ – 定義你應(yīng)用的路由和它們的邏輯
helpers/ – 可以被應(yīng)用的其他部分所共享的代碼和功能
middlewares/ – 處理請求的Express中間件
models/ – 代表了實現(xiàn)了業(yè)務(wù)邏輯的數(shù)據(jù)
public/ – 包含了如圖片,樣式,javascript這樣的靜態(tài)文件
views/ – 提供了供路由渲染的頁面模板
tests/ – 用于測試其他文件夾的代碼
app.js – 初始化你的應(yīng)用,并將所以部分聯(lián)接在一起
package.json – 記錄你的應(yīng)用的依賴庫以及它們的版本
需要提醒的是,除了文件的結(jié)構(gòu)本身,它們所代表的職責(zé)也是十分重要的。
Models你應(yīng)該在modules中處理與數(shù)據(jù)庫的交互,里面的文件包含了處理數(shù)據(jù)所有方法。它們不僅提供了對數(shù)據(jù)的增,刪,改,查方法,還提供了額外的業(yè)務(wù)邏輯。例如,如果你有一個汽車model,它也應(yīng)該包含一個mountTyres方法。
對于數(shù)據(jù)庫中的每一類數(shù)據(jù),你都至少應(yīng)該創(chuàng)建一個對應(yīng)的文件。對應(yīng)到我們的例子里,有用戶以及評論,所以我們至少要有一個user model和一個comment model。當(dāng)一個model變得過于臃腫時,基于它的內(nèi)部邏輯將它拆分成多個不同的文件通常是一個更好的做法。
你應(yīng)該保持你的各個model之間保持相對獨立,它們應(yīng)相互不知道對方的存在,也不應(yīng)引用對方。它們也不需要知道controllers的存在,也永遠(yuǎn)不要接受HTTP請求和響應(yīng)對象,和返回HTTP錯誤,但是,我們可以返回特定的model錯誤。
這些都會使你的model變得更容易維護。由于它們之間相互沒有依賴,所以也容易進(jìn)行測試,對其中一個model進(jìn)行改變也不會影響到其他model。
以下是我們的評論model:
var db = require("../db") // Create new comment in your database and return its id exports.create = function(user, text, cb) { var comment = { user: user, text: text, date: new Date().toString() } db.save(comment, cb) } // Get a particular comment exports.get = function(id, cb) { db.fetch({id:id}, function(err, docs) { if (err) return cb(err) cb(null, docs[0]) }) } // Get all comments exports.all = function(cb) { db.fetch({}, cb) } // Get all comments by a particular user exports.allByUser = function(user, cb) { db.fetch({user: user}, cb) }
它并不引用用戶model。它僅僅需要一個提供用戶信息的user參數(shù),可能包含用戶ID或用戶名。評論model并不關(guān)心它到底是什么,它只關(guān)心這可以被存儲。
var db = require("../db") , crypto = require("crypto") hash = function(password) { return crypto.createHash("sha1").update(password).digest("base64") } exports.create = function(name, email, password, cb) { var user = { name: name, email: email, password: hash(password), } db.save(user, cb) } exports.get = function(id, cb) { db.fetch({id:id}, function(err, docs) { if (err) return cb(err) cb(null, docs[0]) }) } exports.authenticate = function(email, password) { db.fetch({email:email}, function(err, docs) { if (err) return cb(err) if (docs.length === 0) return cb() user = docs[0] if (user.password === hash(password)) { cb(null, docs[0]) } else { cb() } }) } exports.changePassword = function(id, password, cb) { db.update({id:id}, {password: hash(password)}, function(err, affected) { if (err) return cb(err) cb(null, affected > 0) }) }
除了創(chuàng)建和管理用戶的方法,用戶的model還應(yīng)該提供身份驗證和密碼管理的方法。再次重申,這些model之間必須相互不知道對方的存在。
Views這個文件夾內(nèi)包含了所有你的應(yīng)用需要渲染的模板,通常都是由你團隊內(nèi)的設(shè)計師設(shè)計的。
當(dāng)選擇模板語言時,你可能會有些困難,因為當(dāng)下可選擇的模板語言太多了。我最喜歡的兩個模板語言是Jade和Mustache。Jade非常擅于生成HTML,它相對于HTML更簡短以及更可讀。它對JavaScript的條件和迭代語法也有強大的支持。Mustache則完全相反,它更專注于模板渲染,而不是很關(guān)心邏輯操作。
寫一個模板的最佳實踐是,不要在模板中處理數(shù)據(jù)。如果你需要在模板中展示處理后的數(shù)據(jù),你應(yīng)該在controller處理它們。同樣地,多余的邏輯操作也應(yīng)該被移到controller中。
doctype html html head title Your comment web app body h1 Welcome and leave your comment each comment in comments article.Comment .Comment-date= comment.date .Comment-text= comment.textControllers
在這個文件夾里的是所有你定義的路由。它們處理web請求,處理數(shù)據(jù),渲染模板,然后將其返回給用戶。它們是你的應(yīng)用中的其他部分的粘合劑。
通常情況下,你應(yīng)該至少為你應(yīng)用的每一個邏輯部分寫一個路由。例如,一個路由來處理評論,另一個路由來處理用戶,等等。同一類的路由最好有相同的前綴,如/comments/all,/comments/new。
有時,什么代碼該寫進(jìn)controller,什么代碼該寫進(jìn)model是容易混淆的。最佳的實踐是,永遠(yuǎn)不要在controller里直接調(diào)用數(shù)據(jù)庫,應(yīng)該使用model提供方法來代替之。例如,如果你有一個汽車model,然后想要在某輛車上安上四個輪子,你不應(yīng)該直接調(diào)用db.update(id, {wheels: 4}),而是應(yīng)該調(diào)用類似car.mountWheels(id, 4)這樣的model方法。
以下是關(guān)于評論的controller代碼:
var express = require("express") , router = express.Router() , Comment = require("../models/comment") , auth = require("../middlewares/auth") router.post("/", auth, function(req, res) { user = req.user.id text = req.body.text Comment.create(user, text, function (err, comment) { res.redirect("/") }) }) router.get("/:id", function(req, res) { Comment.get(req.params.id, function (err, comment) { res.render("comments/comment", {comment: comment}) }) }) module.exports = router
通常在controller文件夾中,有一個index.js。它用來加載其他的controller,并且定義一些沒有常規(guī)前綴的路由,如首頁路由:
var express = require("express") , router = express.Router() , Comment = require("../models/comment") router.use("/comments", require("./comments")) router.use("/users", require("./users")) router.get("/", function(req, res) { Comments.all(function(err, comments) { res.render("index", {comments: comments}) }) }) module.exports = router
這個文件的router加載了你的所有路由。所以你的應(yīng)用在啟動時,只需要引用它既可。
Middlewares所有的Express中間件都會保存在這個文件夾中。中間件存在的目的,就是提取出一些controller中,處理請求和響應(yīng)對象的共有的代碼。
和controller一樣,一個middleware也不應(yīng)該直接調(diào)用數(shù)據(jù)庫方法。而應(yīng)使用model方法。
以下例子是middlewares/users.js,它用來在請求時加載用戶信息。
User = require("../models/user") module.exports = function(req, res, next) { if (req.session && req.session.user) { User.get(req.session.user, function(err, user) { if (user) { req.user = user } else { delete req.user delete req.session.user } next() }) } else { next() } }
這個middleware使用了用戶model,而不是直接操作數(shù)據(jù)庫。
下面,是一個身份認(rèn)證中間件。通過它來阻止未認(rèn)證的用戶進(jìn)入某些路由:
module.exports = function(req, res, next) { if (req.user) { next() } else { res.status(401).end() } }
它沒有任何外部依賴。如果你看了上文controller中的例子,你一定已經(jīng)明白它是如何運作的。
Helpers這個文件夾中包含一些實用的工具方法,被用于model,middleware或controller中。通常對于不同的任務(wù),這些工具方法會保存在不同的文件中。
Public這個文件只用于存放靜態(tài)文件。通常是css, 圖片,JS庫(如jQuery)。提供靜態(tài)文件的最佳實踐是使用Nginx或Apache作為靜態(tài)文件服務(wù)器,在這方面,它們通常比Node更出色。
Tests所有的項目都需要有測試,你需要將所有的測試代碼放在一個地方。為了方便管理,你可能需要將這些測試代碼放于幾個不同的子文件夾中。
controllers
helpers
models
middleware
integration
ui
controllers,helpers,models和middlewares都十分清晰。這些文件夾里的文件都應(yīng)該與源被測試的文件夾中的文件一一對應(yīng),且名字相同。這樣更易于維護。
在上面這四個文件夾中,主要的測試代碼將是單元測試,這意味著你需要將被測試的代碼與應(yīng)用分離開來。但是,integration文件夾內(nèi)的代碼則主要用來測試你的各部分代碼是否被正確得粘合。例如,是否在正確的地方,調(diào)用了正確的中間件。這些代碼通常會比單元測試更慢一些。
ui文件夾內(nèi)包含了則是UI測試,它與integration文件夾內(nèi)的測試代碼相似,因為它們的目標(biāo)都是保證各個部分被正確地粘合。但是,UI測試通常運行在瀏覽器上,并且還需要模擬用戶的操作。所以,通常它比集成測試更慢。
在前四個文件夾的測試代碼,通常都需要盡量多包含各種邊際情況。而集成測試則不必那么細(xì)致,你只需保證功能的正確性。UI測試也一樣,它也只需要保證每一個UI組件都正確工作即可。
Other files還剩下app.js和package.json這兩個文件。
app.js是你應(yīng)用的起始點。它加載其他的一切,然后開始接收用戶的請求。
var express = require("express") , app = express() app.engine("jade", require("jade").__express) app.set("view engine", "jade") app.use(express.static(__dirname + "/public")) app.use(require("./middlewares/users")) app.use(require("./controllers")) app.listen(3000, function() { console.log("Listening on port 3000...") })
package.son文件的主要目的,則是記錄你的應(yīng)用的各個依賴庫,以及它們的版本號。
{ "name": "Comments App", "version": "1.0.0", "description": "Comments for everyone.", "main": "app.js", "scripts": { "start": "node app.js", "test": "node_modules/.bin/mocha tests/**" }, "keywords": [ "comments", "users", "node", "express", "structure" ], "author": "Terlici Ltd.", "license": "MIT", "dependencies": { "express": "^4.9.5", "jade": "^1.7.0" }, "devDependencies": { "mocha": "^1.21.4", "should": "^4.0.4" } }
你的應(yīng)用也可以通過正確地配置package.json,然后使用npm start和nam test來啟動和測試你的代碼。詳情參閱:http://browsenpm.org/package.json
最后原文鏈接:https://www.terlici.com/2014/08/25/best-practices-express-structure.html
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/86240.html
摘要:前言這將是一個分為兩部分,內(nèi)容是關(guān)于在生產(chǎn)環(huán)境下,跑應(yīng)用的最佳實踐。潛在的攻擊者可以通過它們進(jìn)行針對性的攻擊。 前言 這將是一個分為兩部分,內(nèi)容是關(guān)于在生產(chǎn)環(huán)境下,跑Express應(yīng)用的最佳實踐。第一部分會關(guān)注安全性,第二部分最會關(guān)注性能和可靠性。當(dāng)你讀這篇文章時,假設(shè)你已經(jīng)對Node.js和web開發(fā)有所了解,并且對生產(chǎn)環(huán)境有了概念。 概覽 生產(chǎn)環(huán)境,指的是軟件生命循環(huán)中的某個階段。...
摘要:前言這將是一個分為兩部分,內(nèi)容是關(guān)于在生產(chǎn)環(huán)境下,跑應(yīng)用的最佳實踐。第一部分會關(guān)注安全性,第二部分則會關(guān)注性能和可靠性。關(guān)于第一部分,請參閱在生產(chǎn)環(huán)境下的最佳實踐安全性。 前言 這將是一個分為兩部分,內(nèi)容是關(guān)于在生產(chǎn)環(huán)境下,跑Express應(yīng)用的最佳實踐。第一部分會關(guān)注安全性,第二部分則會關(guān)注性能和可靠性。當(dāng)你讀這篇文章時,會假設(shè)你已經(jīng)對Node.js和web開發(fā)有所了解,并且對生產(chǎn)環(huán)...
摘要:寫好一個模板的最佳實踐是避免在模板中做任何處理。一個最好的實踐是應(yīng)該永遠(yuǎn)不會直接訪問數(shù)據(jù)庫。中間件的目的是為了提取常見的代碼,它將會在多個請求中執(zhí)行,并且通常會修改請求響應(yīng)對象。它的目的是加載發(fā)出請求的用戶。 Models 是你與你的數(shù)據(jù)庫交互的一些文件。它們包含了你處理你的數(shù)據(jù)的所有方法和功能。它們不僅僅包含了創(chuàng)建、讀取、更新和刪除的方法,還包含了業(yè)務(wù)邏輯。例如,如果你有一個 car...
摘要:特意對前端學(xué)習(xí)資源做一個匯總,方便自己學(xué)習(xí)查閱參考,和好友們共同進(jìn)步。 特意對前端學(xué)習(xí)資源做一個匯總,方便自己學(xué)習(xí)查閱參考,和好友們共同進(jìn)步。 本以為自己收藏的站點多,可以很快搞定,沒想到一入?yún)R總深似海。還有很多不足&遺漏的地方,歡迎補充。有錯誤的地方,還請斧正... 托管: welcome to git,歡迎交流,感謝star 有好友反應(yīng)和斧正,會及時更新,平時業(yè)務(wù)工作時也會不定期更...
閱讀 982·2023-04-26 02:56
閱讀 9581·2021-11-23 09:51
閱讀 1889·2021-09-26 10:14
閱讀 2990·2019-08-29 13:09
閱讀 2161·2019-08-26 13:29
閱讀 578·2019-08-26 12:02
閱讀 3573·2019-08-26 10:42
閱讀 3012·2019-08-23 18:18