Express 項目結構最佳實踐(下)

Models 是你與你的數據庫交互的一些文件。它們包含了你處理你的數據的全部方法和功能。它們不只僅包含了建立、讀取、更新和刪除的方法,還包含了業務邏輯。例如,若是你有一個 car model,你能夠有一個 mountTyres 方法。css

在你的數據庫中,針對每種類型的數據,你應該建立至少一個文件。在咱們的例子中,咱們有 users 和 comments,所以咱們有 user model 和 comment model。有時候,當一個 model 文件很大,更好的作法是基於內部的邏輯將這個 model 文件分紅好幾個文件。html

你應該讓你的 models 獨立於外部。models 之間不該該相互引用。它們不須要知道哪一個 controller 調用它們。它們永遠不要接收 request 或 reponse 對象,它們永遠不要返回 http 的錯誤,可是它們應該返回 model 的錯誤。web

全部的這些將會使你的 models 更好維護。由於它們是獨立的,因此能夠很好地測試它們。Models 能夠移動到任何須要用到它的地方。改變一個 model,不會應該其餘的東西,由於它是獨立的。數據庫

基於上面提的的點,讓咱們來看看如何實現咱們例子中的 model。下面是 comment model。express

var db = require('../db')

// Create new comment in your database and return its id
// 在你的數據庫中建立一條新的 comment 
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)
}

user model 沒有包含進來。comment model 不關心它是什麼,它僅僅關心它怎麼存儲。session

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、controller 或者應用的其餘部分。app

Views

這個文件夾包含了你應用全部須要渲染的模板。一般,團隊中的設計師會在這裏工做。post

你想每個 controllers 所對應的模板都有一個子文件夾。這樣的話,你將會爲相同的任務組合模板。測試

選擇一個模板語言會讓人困惑,由於有不少的選擇。咱們最喜歡的模板語言,是 Jade 和 Mustache,咱們一直在用。Jade 很適合生成 html 頁面。它使得寫 html 標籤更短和更加可讀。針對於條件和迭代,它也可使用 JavaScript。Mustache 在另一方面,專一於渲染各類各樣的模板,它提供了儘量少的邏輯運算符而且處理數據的方法不多。這使得它很是適合編寫很是乾淨的模板,這些模板專一於顯示你的數據而不是處理數據。fetch

寫好一個模板的最佳實踐是避免在模板中作任何處理。若是你的數據須要在顯示以前進行處理,在你的 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 進行交互,以處理和檢索數據。這是膠水,可以鏈接和控制你的 web 應用。

一般,對於你應用中的每個邏輯部分,你至少會有一個文件。例如,一個文件處理評論,另一個文件處理關於用戶的請求等等。來自同一個 controller 的全部路由都有相同的前綴,這是一個好的實踐。例如 /comments/all 和 /comments/new。

有時候很難決定什麼應該進入 controller,什麼應該進入 model。一個最好的實踐是應該永遠不會直接訪問數據庫。它永遠不該該調用 write,update,fetch 這些數據庫提供的方法,而應該依靠 model 中的方法。例如若是你有一個 car model,你想要把 4 個輪子安裝到這個 car 上,controller 不會調用 db.update(id, { wheels: 4 }),而是會調用像 car.mountwheels(id, 4) 這樣的方法。

下面是負責評論的 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 文件夾。它的目的是加載全部其餘的 controllers,和可能定義一些沒有相同前綴的路徑,例如 home 頁面路由。

var express = require('express')
  , router = express.Router()
  , Comment = require('../models/comment')

router.use('/comments', require('./comments'))
router.use('/users', require('./users'))

// 與 comments 和 users 不一樣的是,home 頁面不須要前綴(comments 或 users)
router.get('/', function(req, res) {
  Comments.all(function(err, comments) {
    res.render('index', {comments: comments})
  })
})

module.exports = router

這個文件將會處理你全部的路由。你的應用在啓動的必須加載的惟一的路由器。

Middlewares

在這個文件夾中,你將會存儲全部你 Express 的中間件。中間件的目的是爲了提取常見的 controller 代碼,它將會在多個請求中執行,而且一般會修改 請求/響應 對象。

就像一個 controller,一箇中間件永遠不該該訪問數據庫,相反,對於它要完成的每一項任務,它應該使用你的 models。

下面是一個 users 中間件,來自 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()
  }
}

這個中間件使用 user model,而且它沒有直接訪問數據庫。

下一步,authorization 中間件,當你想要阻止沒有權限訪問相同路由的時候,能夠用到這個中間件。

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

它沒有任何外部的依賴。若是你看看上面的 controllers 文件,你能夠看看如何它是如何應用的。

Helpers

這個文件夾包含實用的代碼,這些代碼被用在多個 models,middlewares 或者 controllers 中,可是 helpers 不屬於 models,middlewares 或 controllers 的範疇。一般,你對不一樣的常見任務,會有不一樣的文件。

一個例子就是 helper 文件提供一些方法來管理日期和時間。

Public

這個文件件只是提供靜態文件。一般,它會有子文件夾,像 css,libs,img 用於 css 樣式,圖片和 JavaScript 庫就像 jQuery。這個文件夾可以提供服務的最好實踐不是經過你的應用,而是經過一個 Nginx 或者 Apache 服務,它們比起 Node 在靜態文件的服務更好。

Tests

每一個項目都須要測試,而且你須要將全部的測試彙集到一塊兒。爲了幫助管理它們,你將它們分離在不一樣的子文件中。

  • controllers
  • helpers
  • models
  • middlewares
  • integration
  • ui
相關文章
相關標籤/搜索