Node和Express没有一个相对严格的文件或者是文件夹结构,因此你可以按照自己的想法来构建你的web项目,特别是对一些小的项目来说,他很容易学习。

然而当你的项目变得越来越大,所面临的情况越来越复杂的时候,你的代码将变得很混乱。特别是当你的团队变大的时候,将会很难基于以前的代码工作,你必须要经常处理代码之间的冲突。

为了能够添加一些新的特性,处理一些新的场景,你就需要改变你的代码结构了。更重要的是,现在有需要方式来组织你的文件和你的代码,但是很难选择那种结果适合你。

你想要有一个项目结构:不同的文件或者是文件夹负责不同的任务。你想要你的项目在多人使用时变得简单,相互之间有尽量少的代码冲突,你想要你的代码看上去清洁优雅,而且想要你的项目能够很容易的添加一些新的特性。

同样的问题我们也遇到了,现在这些都可以解决了,下面有一个很好的方式来构建你的项目,这种结构能够改变你的现状解决上述遇到的问题。

我们的这个结构以 Model-View-Controller (MVC) 这种设计模式为基础,这种模式能够很好的分离项目不同模块的职责,使得你的代码干净,可维护。

下面就让我们来看一下,我们是怎样通过Express这个web框架来扩展上面说的MCV模式的。我们不会讨论MVC模式的优点,我们将会集中精力基于Express来实现这个模式,同时我们也会看到的其他很好的实践。

我们已经把这些模式应用在了许多的项目上,无论是大项目还是小项目,都表现的很好。

Example

让我们来看下面的例子:在这个例子中用户能够登录,注册并且给所有的人留言。下面是这个项目的文件结构:

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

这什么鬼,看上去这么混乱与臃肿,但是别担心哥们,当你完全读完这篇文章之后,你就会完全理解他们每部分的功能了,事实上是很简单的。

让我们来看一下在项目的根目录下,每一个文件或者是文件夹是关于什么的简要解释:

  • controllers/ :定义你的项目的路由以及他的逻辑
  • helpers/ :在项目中不同模块中共享的代码或者是功能
  • middlewares/ :Express的中间件:对一个请求传给路由之前的处理
  • models/:代表数据处理,扩展的业务逻辑以及数据的存储
  • public/:包含所有的静态文件,包括图片,CSS样式,javascript代码
  • views/ :提供了被路由渲染的模版
  • tests/:测试,用来测是其他文件夹里的代码
  • app.js:初始并连接整个程序
  • package.json:记住你的程序中使用的所有倚赖包和他们的版本

把他放在中间来说,他的重要性不仅因为他是如何构建你的项目结构的,更主要是了解每个文件的职责,提供该外界的功能。

Models

模型是一个与你的数据库互动的模块,他提供了所有的方法和功能来处理你的数据。他们不仅提供了创建,读取,更新,删除的方法,而且包含了额外的业务逻辑。例如,你有一个汽车的模型,你就应该有一个换轮胎的方法。在你的数据库中对于不同的数据你应该创建至少一个文件。在我们的例子中,我们有用户,评论,因此我们有用户模型与评论模型。有时一个文件太大了,我们需要根据他的内部逻辑来拆分成不同的文件。

你应该尽可能的使你的模型独立与外部其他内容,他不需要知道其他模型的逻辑,而且也不要具有包含,引用关系。他也不需要关心是那个控制器使用了他。他应该永远也得不到请求和响应对象。他也应该永远不要返回http错误,但是他应该返回模型中指定的错误。

所有的这些将会使你的模型更加容易维护。由于他和其他的内容具有很少的依赖所以也很容易测试。如果有需要也可以很好的迁移,可以被任何其他人使用。改变这个模型将不会对其他内容产生影响。

让我们来看一下我们例子中的模型,在基于上面讨论的每个点上他是怎么做扩展的。下面是评论模型:

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)
}

用户模型没有被包含,我们关心的唯一的事情就是谁使用了这个模块中关于用户登录验证的功能,他有可能使用用户id,用户名,或者是其他的东西。评论模块是不关心这些的,他只关心自己的数据处理。

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)
})
}

除了创建与管理用户的功能之外,应该也会有用户的识别,密码的管理等方法。同样,这个模块也不会知道其他模块,控制器或者是程序的其他部分的存在。

Views

这个文件里面包含所有的被程序渲染的模版,这通常是你们team中设计师工作的场所了。你的模版文件应该有相应的子文件夹来和每一个控制器相对应。这样可以把相同任务的模版进行分组。。。

选择一个合适的模板语言是很难的,因为我们的选择太多了。我们喜欢和一直在用的模板语言是jadeMustache, 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

就像你看到的那样,我们假定数据已经在控制层处理过了,然后传给需要渲染的模板。

Controllers

在这个文件里面将会定义你的程序提供的服务的所有路由。你的Controllers将会处理web请求,为用户提供模板,和你的models层在数据处理和获取数据上做交互。他就像胶水一样用来连接和控制你的 app

在你的程序中通常至少有一个文件来处理你的每一个逻辑部分,例如,一个文件用来处理评论的动作(comments action),其他的文件用来处理关于用户的请求等等。来自同一个控制(Controller)下的所有的路由使用同一个前缀,这是一个很好的实践。例如:/comments/all 和 /comments/new

有时候很难确定什么应该运行在控制器(Controller)里,什么应该在模型(model)里做操作。一个很好的实践就是,控制器应该不会直接操作数据库,他不会使用像"write","update","fatch"等这些数据库提供的方法。例如,如果你有一个car模型,你想要添加4个轮子给这个汽车模型,控制器将不会调用db.update(id, {wheels: 4}),但是他将会调用像这样的一个接口: car.mountWheels(id, 4)

下面是一个用于对评论响应的控制器。

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

在控制器文件中还有一个index.js文件。这个文件提供加载其他的控制器,可能会定义一些路径,这些路径像主页路由一样没有具体的统一前缀。例如:

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

这个路由文件,保存着所有的路由。在你的程序启动的时候这是唯一必须加载的路由。

Middlewares

在这个文件里面保存着所有的Express使用到的中间件,中间件的目的就是扩展统一控制的代码,他应该运行在多个请求中,通常修改请求或者是响应对象。 就像控制器一样,中间件也不要之间操作数据库,同样你应该是使用模型(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()
}
}

这个中间件使用用户模型(model),并且从不会直接操作数据库。

下面,授权中间件,它用来当你在某些路由上想要保护不被授权许可。

odule.exports = function(req, res, next) {
if (req.user) {
next()
} else {
res.status(401).end()
}
}

他没有任何额外的依赖,如果你看了上面的控制机器文件,你就会知道他是怎么提供服务的。

Helpers

这个文件里面包括一些工具代码,这些代码被用在多个模型,中间件,控制器中。通常你会有不同的文件来处理不同的任务,一个例子就是helper文件,他提供了一些处理时间与日期的方法。

Public

这个文件下只是用来存放静态文件,通常他会有一些子文件例如:css,libs,img等,用来存储CSS样式,图片和一些JavaScript库比如jquery。一个很好的实践就是这个文件不仅给应用程序提供服务,而且为Nginx和Apache提供服务。

Tests

每一个项目都需要测试。你需要所有的测试一起运行。为了更好的管理他们,你将会把他们分到一些字文件中。

Other files

我们这个结构中的最后几个文件是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.json文件主要是用来记录你在程序中使用的依赖库以及他们的版本,他还有一些其他的功能,它可以允许你使用npm start来运行你的app,测试你的app使用npm test。

What’s next?

所有上面提到的可能是在Express下构建程序的做好实践方式,但是在新项目设置他们是一个繁琐的任务,并且这些项都很容易遗忘。为了帮助你构建,我们在github上,创建了一个仓库,这个仓库包含了上面我们所说的所有内容。你可以fork他们,克隆他们,立刻把他们应用到你的新项目中。更重要的是我们会对这个项目的依赖进行更新,希望你的程序一直使用最好的最新的模块,点击下面的连接关注: https://github.com/terlici/base-express

原文:https://www.terlici.com/2014/08/25/best-practices-express-structure.html

Best practices for Express app structure的更多相关文章

  1. [MEAN Stack] First API -- 4. Organize app structure

    The app structure: Front-end: app.js /** * Created by Answer1215 on 12/9/2014. */ 'use strict'; func ...

  2. Express app.listen 函数了解

    最近一直在学习如何用原生的 Node.js 来做一个网站.在写的同时也在学习 Express 源码. 一直觉得 Express 开启服务器的方法挺有趣的,就看了一下. 在 Express 运行的时候会 ...

  3. Android Wear - App Structure for Android Wear(应用结构)

    ---------------------------------------------------------------------------------------------------- ...

  4. Express细节探究(1)——app.use(express.static)

    express相信是很多人用nodejs搭建服务器的首选框架,相关教程有很多,也教会了大家来如何使用.如果你想更深的了解他的细节,不妨和我一起来研究一下. 先来看一个每个人都用到的方法app.use( ...

  5. Express框架 --router/app.use

    翻看去年自己记录的印象笔记,准备把笔记上的一些内容也同时更新到博客上,方便自己查看. 1.app.use和app.get的区别及解析 app.use(path,callback)中的callback既 ...

  6. nodeJS(2)深了解: nodeJS 项目架构详解(app.js + Express + Http)

    简略了解:nodeJS 深了解(1): Node.js + Express 构建网站预备知识 环境: 环境: win7 + nodeJS 版本(node): 新建 nodeJS 项目: 名称为: te ...

  7. 89.[NodeJS] Express 模板传值对象app.locals、res.locals

    转自:https://blog.csdn.net/Elliott_Yoho/article/details/53537437 locals是Express应用中 Application(app)对象和 ...

  8. Express中app.use()用法 详解

    app.use(path,callback)中的callback既可以是router对象又可以是函数 app.get(path,callback)中的callback只能是函数 当一个路由有好多个子路 ...

  9. express中app和router的区别

      var app = express(); var router = express.Router(); 以上二者的区别是什么,什么时候用哪个最合适? 区别看下面的例子: app.js var ex ...

随机推荐

  1. Java并发容器——CopyOnWriteArrayList

    CopyOnWriteArrayList是“读写分离”的容器,在写的时候是先将底层源数组复制到新数组中,然后在新数组中写,写完后更新源数组.而读只是在源数组上读.也就是,读和写是分离的.由于,写的时候 ...

  2. 基于git 客户端使用shell工具

    1 定义全局启动 命令别名 C:\Program Files\Git\etc\profile.d\aliases.sh alias ls='ls -F --color=auto --show-cont ...

  3. Heroku第三方服务接入指南(二)

    上文我们讲了第三方服务.Heroku.用户三者的关系,这一篇进入正题,了解第三方厂商(下文简称厂商)怎样为Heroku开发服务.这里仅仅做简介,了解heroku大致是怎么做的.假设你的平台.希望接入第 ...

  4. SG 函数初步 HDU 1536 && HDU 1944

    题目链接:http://acm.hdu.edu.cn/showproblem.php? pid=1944 pid=1536"> http://acm.hdu.edu.cn/showpr ...

  5. uitableview做九宫格

    1:创建实体 #import <Foundation/Foundation.h> @interface Shop : NSObject @property (nonatomic, copy ...

  6. springmvc是如何和前端页面联系起来的

    springmvc的使用,在controller中通过注解的形式,获取从前端jsp页面传过来的action参数. 方法/步骤   使用springmvc必须在web.xml中配置(Dispatcher ...

  7. Spring异常解决 java.lang.NullPointerException,配置spring管理hibernate时出错

    @Repository public class SysUerCDAO { @Autowired private Hibernate_Credit hibernate_credit; /** * 根据 ...

  8. 获取可用的处理器(CPU)核数【转】

    linux下获取cpu核数,sysconf(_SC_NPROCESSORS_CONF),,, from:红黑联盟,https://www.2cto.com/kf/201210/164480.html ...

  9. Agile Development敏捷软件开发之何为敏捷开发

    敏捷软件开发之何为敏捷开发 敏捷开发,Agile Development,就是指能够在需求迅速变化的情况下快速开发软件.我们接触最多敏捷实践方式有:极限编程(XP).结对编程.测试驱动开发(TDD)等 ...

  10. 问题 “No mapping found for HTTP request with URI [/fileupload/upload.do]” 的解决

    是因为自己springmvc的配置文件里面不小心删除掉了 <!-- 注解扫描 扫描该包下的注解--> <context:component-scan base-package=&qu ...