Node
和Express
并不严格要求它的应用的文件结构。你能够以任意的结构来组织你的web应用。这对于小应用来讲,一般是不错的,十分易于学习和实验。javascript
可是,当你的应用在体积和复杂性上都变得愈来愈高时,状况就变得复杂了。你的代码可能会变得凌乱。当你的团队人数增长时,向在同一个代码库内写代码变得愈发困难,每次合并代码时均可能会出现各类各样的冲突。为应用增长新的特性和处理新的状况可能都会改变文件的结构。css
一个好的文件结构,应该是每个不一样的文件或文件夹,都分别负责处理不一样的任务。这样,在添加新特性时才会变得不会有冲突。html
这里所推荐的结构是基于MVC设计模式的。这个模式在职责分离方面作得很是好,因此让你的代码更具备可维护性。在这里咱们不会去过多地讨论MVC的优势,而是更多地讨论若是使用它来创建你的Express
应用的文件结构。java
例子:node
让咱们来看下面这个例子。这是一个用户能够登陆,注册,留下评论的应用。如下是他的文件结构。git
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
这看上去可能有点复杂,但不要担忧。在读完这篇文章以后,你将会完彻底全地理解它。它本质上是十分简单的。web
如下是对这个应用中的根文件(夹)的做用的简介:数据库
controllers/ – 定义你应用的路由和它们的逻辑express
helpers/ – 能够被应用的其余部分所共享的代码和功能npm
middlewares/ – 处理请求的Express
中间件
models/ – 表明了实现了业务逻辑的数据
public/ – 包含了如图片,样式,javascript
这样的静态文件
views/ – 提供了供路由渲染的页面模板
tests/ – 用于测试其余文件夹的代码
app.js – 初始化你的应用,并将因此部分联接在一块儿
package.json – 记录你的应用的依赖库以及它们的版本
须要提醒的是,除了文件的结构自己,它们所表明的职责也是十分重要的。
你应该在modules
中处理与数据库的交互,里面的文件包含了处理数据全部方法。它们不只提供了对数据的增,删,改,查方法,还提供了额外的业务逻辑。例如,若是你有一个汽车model
,它也应该包含一个mountTyres
方法。
对于数据库中的每一类数据,你都至少应该建立一个对应的文件。对应到咱们的例子里,有用户以及评论,因此咱们至少要有一个user model
和一个comment model
。当一个model
变得过于臃肿时,基于它的内部逻辑将它拆分红多个不一样的文件一般是一个更好的作法。
你应该保持你的各个model
之间保持相对独立,它们应相互不知道对方的存在,也不该引用对方。它们也不须要知道controllers
的存在,也永远不要接受HTTP请求和响应对象,和返回HTTP错误,可是,咱们能够返回特定的model
错误。
这些都会使你的model
变得更容易维护。因为它们之间相互没有依赖,因此也容易进行测试,对其中一个model
进行改变也不会影响到其余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
参数,可能包含用户ID或用户名。评论model
并不关心它究竟是什么,它只关心这能够被存储。
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) }) }
除了建立和管理用户的方法,用户的model
还应该提供身份验证和密码管理的方法。再次重申,这些model
之间必须相互不知道对方的存在。
这个文件夹内包含了全部你的应用须要渲染的模板,一般都是由你团队内的设计师设计的。
当选择模板语言时,你可能会有些困难,由于当下可选择的模板语言太多了。我最喜欢的两个模板语言是Jade
和Mustache
。Jade
很是擅于生成HTML
,它相对于HTML
更简短以及更可读。它对JavaScript
的条件和迭代语法也有强大的支持。Mustache
则彻底相反,它更专一于模板渲染,而不是很关心逻辑操做。
写一个模板的最佳实践是,不要在模板中处理数据。若是你须要在模板中展现处理后的数据,你应该在controller
处理它们。一样地,多余的逻辑操做也应该被移到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.text
在这个文件夹里的是全部你定义的路由。它们处理web请求,处理数据,渲染模板,而后将其返回给用户。它们是你的应用中的其余部分的粘合剂。
一般状况下,你应该至少为你应用的每个逻辑部分写一个路由。例如,一个路由来处理评论,另外一个路由来处理用户,等等。同一类的路由最好有相同的前缀,如/comments/all
,/comments/new
。
有时,什么代码该写进controller
,什么代码该写进model
是容易混淆的。最佳的实践是,永远不要在controller
里直接调用数据库,应该使用model
提供方法来代替之。例如,若是你有一个汽车model
,而后想要在某辆车上安上四个轮子,你不该该直接调用db.update(id, {wheels: 4})
,而是应该调用相似car.mountWheels(id, 4)
这样的model
方法。
如下是关于评论的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
,而且定义一些没有常规前缀的路由,如首页路由:
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
加载了你的全部路由。因此你的应用在启动时,只须要引用它既可。
全部的Express
中间件都会保存在这个文件夹中。中间件存在的目的,就是提取出一些controller
中,处理请求和响应对象的共有的代码。
和controller
同样,一个middleware
也不该该直接调用数据库方法。而应使用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
,而不是直接操做数据库。
下面,是一个身份认证中间件。经过它来阻止未认证的用户进入某些路由:
module.exports = function(req, res, next) { if (req.user) { next() } else { res.status(401).end() } }
它没有任何外部依赖。若是你看了上文controller
中的例子,你必定已经明白它是如何运做的。
这个文件夹中包含一些实用的工具方法,被用于model
,middleware
或controller
中。一般对于不一样的任务,这些工具方法会保存在不一样的文件中。
这个文件只用于存放静态文件。一般是css
, 图片,JS库(如jQuery
)。提供静态文件的最佳实践是使用Nginx
或Apache
做为静态文件服务器,在这方面,它们一般比Node
更出色。
全部的项目都须要有测试,你须要将全部的测试代码放在一个地方。为了方便管理,你可能须要将这些测试代码放于几个不一样的子文件夹中。
controllers
helpers
models
middleware
integration
ui
controllers
,helpers
,models
和middlewares
都十分清晰。这些文件夹里的文件都应该与源被测试的文件夹中的文件一一对应,且名字相同。这样更易于维护。
在上面这四个文件夹中,主要的测试代码将是单元测试,这意味着你须要将被测试的代码与应用分离开来。可是,integration
文件夹内的代码则主要用来测试你的各部分代码是否被正确得粘合。例如,是否在正确的地方,调用了正确的中间件。这些代码一般会比单元测试更慢一些。
ui
文件夹内包含了则是UI测试,它与integration
文件夹内的测试代码类似,由于它们的目标都是保证各个部分被正确地粘合。可是,UI测试一般运行在浏览器上,而且还须要模拟用户的操做。因此,一般它比集成测试更慢。
在前四个文件夹的测试代码,一般都须要尽可能多包含各类边际状况。而集成测试则没必要那么细致,你只需保证功能的正确性。UI测试也同样,它也只须要保证每个UI组件都正确工做便可。
还剩下app.js
和package.json
这两个文件。
app.js
是你应用的起始点。它加载其余的一切,而后开始接收用户的请求。
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
文件的主要目的,则是记录你的应用的各个依赖库,以及它们的版本号。
{ "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" } }
你的应用也能够经过正确地配置package.json
,而后使用npm start
和nam test
来启动和测试你的代码。详情参阅:http://browsenpm.org/package.json
原文连接:https://www.terlici.com/2014/08/25/best-practices-express-structure.html