[译]Express运用构造的最好实践

媒介

NodeExpress并不严格要求它的运用的文件构造。你能够以恣意的构造来构造你的web运用。这关于小运用来讲,一般是不错的,异常易于进修和试验。

然则,当你的运用在体积和庞杂性上都变得越来越高时,状况就变得庞杂了。你的代码能够会变得缭乱。当你的团队人数增添时,向在统一个代码库内写代码变得愈发难题,每次兼并代码时都能够会涌现林林总总的争执。为运用增添新的特征和处置惩罚新的状况能够都邑转变文件的构造。

一个好的文件构造,应当是每个差别的文件或文件夹,都离别负责处置惩罚差别的使命。如许,在增加新特征时才会变得不会有争执。

最好实践

这里所引荐的构造是基于MVC设想形式的。这个形式在职责星散方面做得异常好,所以让你的代码更具有可保护性。在这里我们不会去过量地议论MVC的长处,而是更多地议论假如运用它来竖立你的Express运用的文件构造。

例子:

让我们来看下面这个例子。这是一个用户能够登录,注册,留下批评的运用。以下是他的文件构造。

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/ – 包括了如图片,款式,javascript如许的静态文件

  • views/ – 供应了供路由衬着的页面模板

  • tests/ – 用于测试其他文件夹的代码

  • app.js – 初始化你的运用,并将所以部份连接在一起

  • package.json – 纪录你的运用的依靠库以及它们的版本

须要提示的是,除了文件的构造自身,它们所代表的职责也是异常重要的。

Models

你应当在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之间必需互相不知道对方的存在。

Views

这个文件夹内包括了统统你的运用须要衬着的模板,一般都是由你团队内的设想师设想的。

当挑选模板言语时,你能够会有些难题,由于当下可挑选的模板言语太多了。我最喜欢的两个模板言语是JadeMustacheJade异常擅于天生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

在这个文件夹里的是统统你定义的路由。它们处置惩罚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加载了你的统统路由。所以你的运用在启动时,只须要援用它既可。

Middlewares

统统的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中的例子,你肯定已邃晓它是怎样运作的。

Helpers

这个文件夹中包括一些有用的东西要领,被用于model,middlewarecontroller中。一般关于差别的使命,这些东西要领会保留在差别的文件中。

Public

这个文件只用于寄存静态文件。一般是css, 图片,JS库(如jQuery)。供应静态文件的最好实践是运用NginxApache作为静态文件服务器,在这方面,它们一般比Node更精彩。

Tests

统统的项目都须要有测试,你须要将统统的测试代码放在一个处所。为了轻易治理,你能够须要将这些测试代码放于几个差别的子文件夹中。

  • controllers

  • helpers

  • models

  • middleware

  • integration

  • ui

controllershelpersmodelsmiddlewares都异常清楚。这些文件夹里的文件都应当与源被测试的文件夹中的文件一一对应,且名字雷同。如许更易于保护。

在上面这四个文件夹中,重要的测试代码将是单元测试,这意味着你须要将被测试的代码与运用星散开来。然则,integration文件夹内的代码则重要用来测试你的各部份代码是不是被准确得粘合。比方,是不是在准确的处所,挪用了准确的中间件。这些代码一般会比单元测试更慢一些。

ui文件夹内包括了则是UI测试,它与integration文件夹内的测试代码类似,由于它们的目的都是保证各个部份被准确地粘合。然则,UI测试一般运行在浏览器上,而且还须要模仿用户的操纵。所以,一般它比集成测试更慢。

在前四个文件夹的测试代码,一般都须要只管多包括种种边际状况。而集成测试则没必要那末仔细,你只需保证功用的准确性。UI测试也一样,它也只须要保证每个UI组件都准确事情即可。

Other files

还剩下app.jspackage.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 startnam test来启动和测试你的代码。概况参阅:http://browsenpm.org/package.json

末了

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

    原文作者:菜菜蔡伟
    原文地址: https://segmentfault.com/a/1190000004019233
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞