资讯专栏INFORMATION COLUMN

Express 项目结构最佳实践(下)

Pikachu / 2924人阅读

摘要:写好一个模板的最佳实践是避免在模板中做任何处理。一个最好的实践是应该永远不会直接访问数据库。中间件的目的是为了提取常见的代码,它将会在多个请求中执行,并且通常会修改请求响应对象。它的目的是加载发出请求的用户。

Models 是你与你的数据库交互的一些文件。它们包含了你处理你的数据的所有方法和功能。它们不仅仅包含了创建、读取、更新和删除的方法,还包含了业务逻辑。例如,如果你有一个 car model,你可以有一个 mountTyres 方法。

在你的数据库中,针对每种类型的数据,你应该创建至少一个文件。在我们的例子中,我们有 users 和 comments,因此我们有 user model 和 comment model。有时候,当一个 model 文件很大,更好的做法是基于内部的逻辑将这个 model 文件分成好几个文件。

你应该让你的 models 独立于外部。models 之间不应该相互引用。它们不需要知道哪个 controller 调用它们。它们永远不要接收 request 或 reponse 对象,它们永远不要返回 http 的错误,但是它们应该返回 model 的错误。

所有的这些将会使你的 models 更好维护。因为它们是独立的,所以可以很好地测试它们。Models 可以移动到任何需要用到它的地方。改变一个 model,不会应该其他的东西,因为它是独立的。

基于上面提的的点,让我们来看看如何实现我们例子中的 model。下面是 comment model。

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 不关心它是什么,它仅仅关心它怎么存储。

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 或者应用的其他部分。

Views

这个文件夹包含了你应用所有需要渲染的模板。通常,团队中的设计师会在这里工作。

你想每一个 controllers 所对应的模板都有一个子文件夹。这样的话,你将会为相同的任务组合模板。

选择一个模板语言会让人困惑,因为有很多的选择。我们最喜欢的模板语言,是 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

如你所见,在渲染这个模板时,数据预计已经被处理好了。

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

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/95630.html

相关文章

  • [译]Express应用结构最佳实践

    摘要:为应用增加新的特性和处理新的情况可能都会改变文件的结构。写一个模板的最佳实践是,不要在模板中处理数据。在上面这四个文件夹中,主要的测试代码将是单元测试,这意味着你需要将被测试的代码与应用分离开来。 前言 Node和Express并不严格要求它的应用的文件结构。你可以以任意的结构来组织你的web应用。这对于小应用来说,通常是不错的,十分易于学习和实验。 但是,当你的应用在体积和复杂性上都...

    dreamans 评论0 收藏0
  • 笔记: node最佳实践1 - 项目工程最佳实践

    摘要:原文阅读工程结构最佳实践组件化按照功能划分按照组件划分层次化不要在中写太多业务逻辑,专注层业务层要单独抽出数据库层单独抽出化把常用组件做成包分离的和配置化环境感知根据不同环境使用不同配置 showImg(https://segmentfault.com/img/bVYQsC?w=2558&h=817); 原文阅读: nodebestpractices 1 工程结构最佳实践 1.1 组件...

    APICloud 评论0 收藏0
  • [译]Express在生产环境最佳实践 - 安全性

    摘要:前言这将是一个分为两部分,内容是关于在生产环境下,跑应用的最佳实践。潜在的攻击者可以通过它们进行针对性的攻击。 前言 这将是一个分为两部分,内容是关于在生产环境下,跑Express应用的最佳实践。第一部分会关注安全性,第二部分最会关注性能和可靠性。当你读这篇文章时,假设你已经对Node.js和web开发有所了解,并且对生产环境有了概念。 概览 生产环境,指的是软件生命循环中的某个阶段。...

    Forelax 评论0 收藏0
  • [译]Express在生产环境最佳实践 - 性能和可靠性

    摘要:前言这将是一个分为两部分,内容是关于在生产环境下,跑应用的最佳实践。第一部分会关注安全性,第二部分则会关注性能和可靠性。关于第一部分,请参阅在生产环境下的最佳实践安全性。 前言 这将是一个分为两部分,内容是关于在生产环境下,跑Express应用的最佳实践。第一部分会关注安全性,第二部分则会关注性能和可靠性。当你读这篇文章时,会假设你已经对Node.js和web开发有所了解,并且对生产环...

    Luosunce 评论0 收藏0
  • 前端资源系列(4)-前端学习资源分享&前端面试资源汇总

    摘要:特意对前端学习资源做一个汇总,方便自己学习查阅参考,和好友们共同进步。 特意对前端学习资源做一个汇总,方便自己学习查阅参考,和好友们共同进步。 本以为自己收藏的站点多,可以很快搞定,没想到一入汇总深似海。还有很多不足&遗漏的地方,欢迎补充。有错误的地方,还请斧正... 托管: welcome to git,欢迎交流,感谢star 有好友反应和斧正,会及时更新,平时业务工作时也会不定期更...

    princekin 评论0 收藏0

发表评论

0条评论

Pikachu

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<