JavaScript-全栈教程-三-

109 阅读14分钟

JavaScript 全栈教程(三)

原文:Full Stack JavaScript

协议:CC BY-NC-SA 4.0

五、Backbone.js 和 Parse.com

Java 对于 JavaScript 就像汽车对于地毯一样。— 克里斯·海尔曼

在这一章中,我们将探索为一个 Backbone.js 应用利用 Parse.com 的实际方面。这一章将说明 Backbone.js 在修改后的留言板应用上与 Parse.com 及其 JavaScript SDK 一起使用。

如果您已经编写了一些复杂的客户端应用,您可能会发现维护 JavaScript 回调和 UI 事件的复杂代码很有挑战性。js 提供了一种轻量级但功能强大的方法来将您的逻辑组织成模型-视图-控制器(MVC)类型的结构。它还有一些不错的特性,比如 URL 路由、REST API 支持、事件监听器和触发器。有关从头构建 Backbone.js 应用的更多信息和分步示例,请参考“Backbone.js 简介”一章

带有 Parse.com 的留言板:JavaScript SDK 和 Backbone.js 版本

引导您完成实施并演示项目的补充视频: http://bit.ly/1QnqsQC

很容易看出,如果我们不断添加越来越多的按钮,如“删除”、“更新”和其他功能,我们的异步回调系统将变得更加复杂。我们必须知道何时根据数据是否有变化来更新视图(即消息列表)。js 模型-视图-控制器(MVC)框架可用于使复杂的应用更易于管理和维护。

如果您对前面的例子感到满意,让我们使用 Backbone.js 框架来构建它。在这里,我们将一步一步地使用 Backbone.js 和 Parse.com JavaScript SDK 创建一个留言板应用。如果你对它足够熟悉,你可以在 github.com/azat-co/super-simple-backbone-starter-kit 下载这个超级简单的主干初学者工具包。与 Backbone.js 的集成将允许通过将用户动作绑定到集合的异步更新来直接实现用户动作。

该应用可从 https://github.com/azat-co/fullstack-javascript/tree/master/06-board-backbone-parse-sdk 获得,但同样鼓励您从头开始,并尝试使用示例编写自己的代码,仅供参考。

下图显示了 Parse.com、JavaScript SDK 和 Backbone.js 版本的留言板结构:

/06-board-backbone-parse-sdk

-index.html

-home.html

-footer.html

-header.html

-app.js

/css

-bootstrap.css

-bootstrap.min.css

/js

-backbone.js

-jquery.js

-underscore.js

/libs

-require.min.js

-text.js

创建一个文件夹;在文件夹中创建一个具有以下内容框架的index.html文件:

<!DOCTYPE html>

<html``lang="en"

<head>

...

</head>

<body>

...

</body>

</html>

从 Google API 下载必要的库或热链接它们。现在,在 head 元素中加入 JavaScript 库和 Twitter 引导样式表,以及其他重要但不是必需的 meta 元素。

<head>

<meta``charset="utf-8"

<title>``Message Board

<meta``name="author" content="Azat Mardan"

我们需要这种响应行为:

<meta name="viewport"

content="width=device-width, initial-scale=1.0" />

从本地文件链接 jQuery v2.1.4:

<script``src="js/jquery.js"

对下划线版本 1.8.3 和主干版本 1.2.3 进行同样的操作:

<script``src="js/underscore.js"

<script``src="js/backbone.js"

解析 JavaScript SDK v1.5.0 热链接自 Parse.com CDN。请注意版本号,因为旧版本可能无法在此示例中正常工作:

<script``src="//www.parsecdn.com/js/parse-1.5.0.min.js"``></script

Twitter 引导 CSS 包含:

<link``type="text/css" rel="stylesheet" href="css/bootstrap.css"

我们需要 RequireJS v2.1.22 来加载依赖项:

<script``type="text/javascript" src="libs/require.js"

这里是我们的 JS 应用包含:

<script``type="text/javascript" src="app.js"

</head>

用 Twitter Bootstrap 脚手架填充<body>元素(在“基础知识”一章中有更多相关信息):

<body>

<div``class="container-fluid"

<div``class="row-fluid"

<div``class="span12"

<div``id="header"

</div>

</div>

</div>

<div``class="row-fluid"

<div``class="span12"

<div``id="content"

</div>

</div>

</div>

<div``class="row-fluid"

<div``class="span12"

<div``id="footer"

</div>

</div>

</div>

</div>

</body>

创建一个app.js文件并将 Backbone.js 视图放入其中:

  • headerView:菜单和 app-常用信息
  • footerView:版权和联系链接
  • homeView:首页内容

我们对 HTML 模板使用 Require.js 语法和 shim 插件:

require([

’libs/text!header.html’,

’libs/text!home.html’,

’libs/text!footer.html’], function (

headerTpl,

homeTpl,

footerTpl) {

具有单一索引路由的应用路由:

var ApplicationRouter = Backbone.Router.extend({

routes: {

"": "home",

"*actions": "home"

},

在我们做任何事情之前,我们可以初始化将在整个应用中使用的视图:

initialize: function() {

this.headerView = new HeaderView()

this.headerView.render()

this.footerView = new FooterView()

this.footerView.render()

},

这个代码负责回家的路线:

home: function() {

this.homeView = new HomeView()

this.homeView.render()

}

})

header Backbone 视图附加到#header元素,并使用headerTpl模板:

HeaderView = Backbone.View.extend({

el: ’#header’,

templateFileName: ’header.html’,

template: headerTpl,

initialize: function() {

},

render: function() {

console.log(this.template)

$(this.el).html(_.template(this.template))

}

})

为了呈现 HTML,我们使用了jQuery.html()函数:

FooterView = Backbone.View.extend({

el: ’#footer’,

template: footerTpl,

render: function() {

this.$el.html(_.template(this.template))

}

})

家庭主干视图定义使用了#content DOM 元素:

HomeView = Backbone.View.extend({

el: ’#content’,

template: homeTpl,

initialize: function() {

},

render: function() {

$(this.el).html(_.template(this.template))

}

})

要启动一个应用,我们创建一个新实例并调用Backbone.history.start():

app = new ApplicationRouter()

Backbone.history.start()

})

app.js文件的完整代码:

require([

’libs/text!header.html’,

// Example of a shim plugin use

’libs/text!home.html’,

’libs/text!footer.html’],

function (

headerTpl,

homeTpl,

footerTpl) {

var ApplicationRouter = Backbone.Router.extend({

routes: {

’’: ’home’,

’*actions’: ’home’

},

initialize: function() {

this.headerView = new HeaderView()

this.headerView.render()

this.footerView = new FooterView()

this.footerView.render()

},

home: function() {

this.homeView = new HomeView()

this.homeView.render()

}

})

HeaderView = Backbone.View.extend({

el: ’#header’,

templateFileName: ’header.html’,

template: headerTpl,

initialize: function() {

},

render: function() {

console.log(this.template)

$(this.el).html(_.template(this.template))

}

})

FooterView = Backbone.View.extend({

el: ’#footer’,

template: footerTpl,

render: function() {

this.$el.html(_.template(this.template))

}

})

HomeView = Backbone.View.extend({

el: ’#content’,

template: homeTpl,

initialize: function() {

},

render: function() {

$(this.el).html(_.template(this.template))

}

})

app = new ApplicationRouter()

Backbone.history.start()

})

上面的代码显示了模板。所有的视图和路由都在里面,这要求模块在我们开始处理模板之前确保它们已经被加载。

下面是home.html的样子:

  • 信息表
  • 下划线. js 逻辑输出表中的行
  • 一种新的消息形式

让我们通过分配row-fluidspan12类来使用 Twitter 引导库结构(及其响应组件):

<div``class="row-fluid"  id="message-board"

<div``class="span12"

<table``class="table table-bordered table-striped"

<caption>``Message Board

<thead>

<tr>

<th``class="span2"``>``Username

<th>``Message

</tr>

</thead>

<tbody>

这部分有 Underscore.js 模板指令,只是一些 js 代码包裹在<%%>标记中。我们马上检查 models 变量是否已定义并且不为空:

< %

if (typeof models != ’undefined’ && models.length > 0) {

_.each()是 UnderscoreJS 库( underscorejs.org/#each )中的一个迭代函数,它确实像它听起来那样——遍历对象/数组的元素:

_.each(models, function (value, key, list) { %>

<tr>

在迭代器函数中,我们有一个模型值。我们可以用model.attributes.attributeName访问主干模型的属性。为了用下划线输出变量,我们用<%= NAME %>代替<% CODE %>:

<td><``%= value.attributes.username %>

<td><``%= value.attributes.message %>

</tr>

< % })

}

但是如果models未定义或者为空呢?在这种情况下,我们打印一条消息,说明还没有消息。它进入else块。我们使用colspan=2将两个单元格合并成一个:

else { %>

<tr>

<td``colspan="2"``>``No messages yet

</tr>

我们关闭表格和其他 HTML 标签:

< %}%>

</tbody>

</table>

</div>

</div>

对于新的消息表单,我们也使用row-fluid类,然后添加<input>元素:

<div``class="row-fluid"  id="new-message"

<div``class="span12"

<form``class="well form-inline"

input 元素必须有名称username,因为这是我们在 JavaScript 代码中找到该元素并获得用户名值的方式:

<input type="text"

name="username"

class="input-small"

placeholder="Username" />

类似于用户名<input>标签,消息文本标签需要有名称。在这种情况下,它是message:

<input type="text" name="message"

class="input-small"

placeholder="Message Text" />

最后,发送按钮必须有 IDsend。这是我们在HomeView类的主干事件属性中使用的:

<a``id="send" class="btn btn-primary"``>``SEND

</form>

</div>

</div>

为了方便起见,下面是home.html模板文件的完整代码:

<div``class="row-fluid"  id="message-board"

<div``class="span12"

<table``class="table table-bordered table-striped"

<caption>``Message Board

<thead>

<tr>

<th``class="span2"``>``Username

<th>``Message

</tr>

</thead>

<tbody>

< % if (typeof models != ’undefined’ && models.length>0) {

_.each(models, function (value, key, list) { %>

<tr>

<td><``%= value.attributes.username %>

<td><``%= value.attributes.message %>

</tr>

< % })

}

else { %>

<tr>

<td``colspan="2"``>``No messages yet

</tr>

< %}%>

</tbody>

</table>

</div>

</div>

<div``class="row-fluid"  id="new-message"

<div``class="span12"

<form``class="well form-inline"

<input type="text"

name="username"

class="input-small"

placeholder="Username" />

<input type="text" name="message"

class="input-small"

placeholder="Message Text" />

<a``id="send" class="btn btn-primary"``>``SEND

</form>

</div>

</div>

现在,我们可以将以下组件添加到:

  • Parse.com 收藏
  • Parse.com 模型
  • 发送/添加消息事件
  • 获取/显示消息功能

来自 Parse.com JS SDK 的主干兼容模型对象/类,具有强制的className属性(这是将出现在 Parse.com 网络界面的数据浏览器中的集合的名称):

Message = Parse.Object.extend({

className: ’MessageBoard’

})

指向模型的 Parse.com JavaScript SDK 的主干兼容集合对象:

MessageBoard = Parse.Collection.extend ({

model: Message

})

主视图需要在“发送”按钮上有一个点击事件监听器:

HomeView = Backbone.View.extend({

el: ’#content’,

template: homeTpl,

events: {

’click #send’: ’saveMessage’

},

当我们创建 homeView 时,我们还要创建一个集合并将事件侦听器附加到它:

initialize: function() {

this.collection = new MessageBoard()

this.collection.bind(’all’, this.render, this)

this.collection.fetch()

this.collection.on(’add’, function(message) {

message.save(null, {

success: function(message) {

console.log(’saved ’ + message)

},

error: function(message) {

console.log(’error’)

}

})

console.log(’saved’ + message)

})

},

saveMessage()的定义调用了“发送”按钮的点击事件:

saveMessage: function(){

首先,我们通过 ID ( #new-message)获取表单对象,因为使用存储的对象比每次都使用 jQuery 选择器更有效,可读性更好。

var newMessageForm = $(’#new-message’)

接下来的两行将获得名为usernamemessage的输入字段的值:

var username = newMessageForm.find(’[name="username"]’).val()

var message = newMessageForm.find(’[name="message"]’).val()

一旦我们有了新消息的值(文本和作者),我们就可以调用this.collection.add:

this.collection.add({

’username’: username,

’message’: message

})

},

最后,我们通过对模板使用_.template输出集合,然后用数据this.collection调用它:

render: function() {

$(this.el).html(_.template(this.template)(this.collection))

}

我们在app. js中操作的最终结果可能是这样的:

require([

’libs/text!header.html’,

’libs/text!home.html’,

’libs/text!footer.html’], function (

headerTpl,

homeTpl,

footerTpl) {

Parse.initialize(’your-parse-app-id’, ’your-parse-js-sdk-key’)

var ApplicationRouter = Backbone.Router.extend({

routes: {

’’: ’home’,

’*actions’: ’home’

},

initialize: function() {

this.headerView = new HeaderView()

this.headerView.render()

this.footerView = new FooterView()

this.footerView.render()

},

home: function() {

this.homeView = new HomeView()

this.homeView.render()

}

})

HeaderView = Backbone.View.``extend

el: ’#header’,

templateFileName: ’header.html’,

template: headerTpl,

initialize: function() {

},

render: function() {

$(this.el).html(_.template(this.template))

}

})

FooterView = Backbone.View.extend({

el: ’#footer’,

template: footerTpl,

render: function() {

this.$el.html(_.template(this.template))

}

})

Message = Parse.Object.extend({

className: ’MessageBoard’

})

MessageBoard = Parse.Collection.extend ({

model: Message

})

HomeView = Backbone.View.extend({

el: ’#content’,

template: homeTpl,

events: {

’click #send’: ’saveMessage’

},

initialize: function() {

this.collection = new MessageBoard()

this.collection.bind(’all’, this.render, this)

this.collection.fetch()

this.collection.on(’add’, function(message) {

message.save(null, {

success: function(message) {

console.log(’saved ’ + message)

},

error: function(message) {

console.log(’error’)

}

})

console.log(’saved’ + message)

})

},

saveMessage: function(){

var newMessageForm = $(’#new-message’)

var username = newMessageForm.find(’[name="username"]’).val()

var message = newMessageForm.find(’[name="message"]’).val()

this.collection.add({

’username’: username,

’message’: message

})

},

render: function() {

$(this.el).html(_.template(this.template)(this.collection))

}

})

app = new``ApplicationRouter

Backbone.history.start()

})

Backbone.js 和 Parse.com 留言板应用的完整源代码可在 https://github.com/azat-co/fullstack-javascript/tree/master/06-board-backbone-parse-sdk 获得。

将留言板带得更远

一旦你对你的前端应用在本地运行良好感到满意,不管有没有像 MAMP 或 XAMPP 这样的本地 HTTP 服务器,把它部署到 Windows Azure 或 Heroku。“jQuery 和 Parse.com”一章中描述了深入的部署说明。

在最后两个例子中,留言板有非常基本的功能。您可以通过添加更多功能来增强应用。

面向intermediate级开发人员的附加功能:

  • 在显示消息列表之前,通过 updateAt 属性对其进行排序。
  • 添加一个“刷新”按钮来更新消息列表。
  • 在运行时内存或会话中保存第一个消息条目后的用户名。
  • 在每封邮件旁边添加一个向上投票按钮,并存储投票。
  • 在每封邮件旁边添加一个向下投票按钮,并存储投票。

面向高级开发人员的附加功能:

  • 添加用户集合。
  • 防止同一用户多次投票。
  • 使用 Parse.com 函数添加用户注册和登录操作。
  • 在用户创建的每封邮件旁边添加一个“删除邮件”按钮。
  • 在用户创建的每条消息旁边添加“编辑消息”按钮。

摘要

这短短的一章给了你另一种只用 JavaScript(当然还有 HTML 和 CSS)构建应用的方法。使用 Parse.com 或类似的后端即服务(BaaS)解决方案,无需编写自己的后端代码就可以直接保存数据。BaaS solutions event 通过允许访问级控制、身份验证、服务器端逻辑和第三方集成而更进一步。

除了 Parse.com,在这一章中我们看到了 Backbone 是如何灵活的,你可以重载它的类来构建你自己的定制类。这是一种使用 Backbone 构建自己的框架的方法。这就是我们在 DocuSign 所做的,我们有基本的主干模型,并为定制用例扩展了它们。我们甚至在服务器和浏览器之间共享主干模型,允许更快的数据加载。说到服务器 JavaScript,下一章我们将探索如何用 Node.js 在服务器上编写 JavaScript。

六、Node.js 简介

任何傻瓜都能写出计算机能理解的代码。优秀的程序员编写人类能够理解的代码。——马丁·福勒

在本章中,我们将介绍以下内容:

  • 在 Node.js 中构建“Hello World”
  • Node.js 核心模块
  • npm Node 程序包管理器
  • 带有 Node.js 的留言板:内存存储版本
  • 单元测试 Node. js

Node.js 是一个用于构建 web 应用的非阻塞平台。它使用 JavaScript,所以它是我们 fullstack JavaScript 开发的核心。我们将从 Hello World 开始,讨论核心模块和 npm。然后,我们将 Hello World 应用部署到云中。

在 Node.js 中构建“Hello World”

引导您完成实施并演示项目的补充视频: http://bit.ly/1QnqFmF

要检查您的计算机上是否安装了 Node.js,请在终端中键入并执行以下命令:

$ node -v

在撰写本文时,最新版本是 5.1.0。如果你没有安装 Node.js,或者你的版本落后,可以在 nodejs.org/#download 下载最新版本。您可以使用这些工具之一进行版本管理(例如,在 Node.js 版本之间切换):

通常,你可以在 https://github.com/azat-co/fullstack-javascript/tree/master/07-hello 复制示例代码,或者从头开始编写自己的程序。如果您想做后者,为您的“Hello World”node . js 应用创建一个文件夹hello。然后创建一个server.js文件,一行一行地输入下面的代码。

这将加载服务器的核心http模块(稍后将详细介绍这些模块):

var http = require(’http’)

我们需要 Node.js 服务器的端口号。要从环境中获取或分配 1337(如果未设置环境),请使用:

var port = process.env.PORT || 1337

这将创建一个服务器,一个回调函数将包含响应处理程序代码:

var server = http.createServer(function (req, res) {

要设置正确的标题和状态代码,请使用:

res.writeHead(200, {’Content-Type’: ’text/plain’})

要输出带有行尾符号的“Hello World ”,请使用:

res.end(’Hello World\n’)

})

要设置端口并显示服务器的地址和端口号,请使用:

server.listen(port, function() {

console.log(’Server is running at %s:%s ’,

server.address().address, server.address().port)

})

从您有server.js的文件夹中,在您的终端中启动以下命令:

$ node server.js

打开 localhost:1337 或 127.0.0.1:1337 或任何其他你在终端中看到的地址作为console.log()功能的结果,你应该在浏览器中看到“Hello World”。要关闭服务器,请按 Control + C。

Note

主文件的名称可以不同于 server.js(例如 index.js 或 app.js)。如果需要启动app.js文件,只需使用$ node app.js即可。

Node.js 核心模块

与其他编程技术不同,Node.js 没有附带沉重的标准库。node.js 的核心模块是最少的,其余的可以通过 Node 包管理器(NPM)注册中心挑选。主要的核心模块、类、方法和事件包括:

这些是最重要的核心模块。让我们逐一介绍。

http

这是负责 Node.js HTTP server 的主要模块。以下是主要方法:

  • http.createServer():返回一个新的 web 服务器对象
  • http.listen():开始接受指定端口和主机名上的连接
  • http.createClient() : node app 可以是客户端,向其他服务器发出请求
  • http.ServerRequest():传入的请求被传递给请求处理程序
    • data:收到一段消息体时发出
    • end:每个请求只发出一次
    • request.method():字符串形式的请求方法
    • request.url():请求 URL 字符串
  • http.ServerResponse():这个对象是由 HTTP 服务器内部创建的——而不是由用户创建的,并被用作请求处理程序的输出
    • response.writeHead():发送请求的响应头
    • response.write():发送响应正文
    • response.end():发送并结束响应体

效用

该模块提供了用于调试的实用程序。一些方法包括:

  • util.inspect():返回一个对象的字符串表示,对调试很有用

查询字串

这个模块提供了处理查询字符串的工具。一些方法包括:

  • querystring.stringify():将对象序列化为查询字符串
  • querystring.parse():将查询字符串反序列化为对象

url

这个模块有 URL 解析和解析的工具。一些方法包括:

  • parse():取一个 URL 字符串,返回一个对象

fs

文件系统处理文件系统操作,例如读写文件。库中有同步和异步方法。一些方法包括:

  • fs.readFile():异步读取文件
  • fs.writeFile():将数据异步写入文件

不需要安装或下载核心模块。要将它们包含在您的应用中,您只需遵循以下语法:

var http = require(’http’)

非核心模块列表可在以下位置找到:

如果你想知道如何编写你自己的模块,看看这篇文章: https://quickleft.com/blog/creating-and-publishing-a-node-js-module/

npm Node 程序包管理器

Node 程序包管理器(或称 NPM)为您管理依赖关系并安装模块。Node.js 安装自带 NPM,其网址为 npmjs.org

包含关于 Node.js 应用的元信息,比如版本号;作者姓名;最重要的是,我们在应用中使用什么依赖关系。所有这些信息都在 JSON 格式的对象中,由 NPM 读取。

如果您想安装package.json中指定的软件包和依赖项,请键入:

$ npm install

典型的package.json文件可能如下所示:

{

"name": "Blerg",

"description": "Blerg blerg blerg.",

"version": "0.0.1",

"author": {

"name" : "John Doe",

"email" : "john.doe@gmail.com"

},

"repository": {

"type": "git",

"url": "http://github.com/johndoe/blerg.git

},

"engines": [

"node >= 0.6.2"

],

"scripts": {

"start": "server.js"

},

"license" : "MIT",

"dependencies": {

"express": ">= 2.5.6",

"mustache": "0.4.0",

"commander": "0.5.2"

},

"bin" : {

"blerg" : "./cli.js"

}

}

虽然上面的package.json示例中的大多数属性,如descriptionname,都是不言自明的,但是其他的属性需要更多的解释。Dependencies 是一个对象,每一项左边有名称,右边有版本号(如“express”:>= 2 . 5 . 6)。版本可以是精确的:例如," express": "2.5.6 ",或者大于,或者通配符,例如," express": "* "(一种在生产中使用新的未经测试的依赖项来放大应用的好方法:因此不推荐)。

bin属性用于命令行实用程序。它告诉系统启动什么文件。而scripts对象有你可以用$ npm run SCRIPT_NAME启动的脚本。start脚本和测试是例外。你可以用$ npm start$ npm test来运行它们。

要将软件包更新到当前最新版本或package.json中定义的版本规范允许的最新版本,请使用:

$ npm update name-of-the-package

或者对于单模块安装:

$ npm install name-of-the-package

本书示例中使用的唯一模块是mongodb,它不属于核心 Node.js 包。我们将在本书的后面安装它。

Heroku 将需要package.json在服务器上运行 NPM。

想了解更多关于 NPM 的信息,可以看看“游 NPM”(http://tobyho.com/2012/02/09/tour-of-npm)这篇文章。

将“Hello World”部署到 PaaS

对于 Heroku 和 Windows Azure 部署,我们需要一个 Git 存储库。要从项目的根目录创建它,请在终端中键入以下命令:

$ git init

Git 将创建一个隐藏的.git文件夹。现在,我们可以添加文件并进行第一次提交:

$ git add .

$ git commit -am "first commit"

提示要查看 Mac OS X Finder 应用上的隐藏文件,请在终端窗口中执行此命令:defaults write com.apple.finder AppleShowAllFiles -bool true。要将标志改回隐藏状态:defaults write com.apple.finder AppleShowAllFiles -bool false

部署到 Windows Azure

为了将我们的“Hello World”应用部署到 Windows Azure,我们必须添加 Git remote。您可以从网站下的 Windows Azure 门户复制 URL,并通过以下命令使用它:

$ git remote add azure yourURL

现在,我们应该能够使用这个命令进行推送了:

$ git push azure master

如果一切顺利,您应该会在终端中看到成功日志,并在 Windows Azure 网站 URL 的浏览器中看到“Hello World”。

要推动更改,只需执行:

$ git add .

$ git commit -m "changing to hello azure"

$ git push azure master

更细致的指导可以在 https://azure.microsoft.com/en-us/documentation/articles/web-sites-nodejs-develop-deploy-mac 教程中找到。

部署到 Heroku

对于 Heroku 部署,我们需要创建两个额外的文件:Procfilepackage.json。你可以从 https://github.com/azat-co/fullstack-javascript/tree/master/07-hello 获得源代码,或者自己写一个。

“Hello World”应用的结构如下所示:

/07-hello

-package.json

-Procfile

-server.js

Procfile 是一种机制,用于声明 Heroku 平台上应用的 dynos 运行哪些命令。基本上,它告诉 Heroku 运行什么进程。在这种情况下,Procfile 只有一行:

web: node server.js

对于这个例子,我们保持package.json简单:

{

"name": "node-example",

"version": "0.0.1",

"dependencies": {

},

"engines": {

"node": ">=0.6.x"

}

}

在项目文件夹中有了所有文件之后,我们可以使用 Git 来部署应用。除了我们需要添加 Git remote,并使用以下命令创建 Cedar 堆栈之外,这些命令与 Windows Azure 非常相似:

$ heroku create

完成后,我们推送并更新:

$ git push heroku master

$ git add .

$ git commit -am "changes :+1:"

$ git push heroku master

如果一切顺利,您应该会在终端中看到成功日志,并在 Heroku 应用 URL 的浏览器中看到“Hello World”。

带有 Node.js 的留言板:内存存储版本

引导您完成实施并演示项目的补充视频: http://bit.ly/1QnqO9P

第一版的留言板后端应用为了亲亲只在运行时内存存储中存储消息( http://en.wikipedia.org/wiki/KISS_principle )。这意味着每次我们启动/重置服务器时,数据都会丢失。

我们将首先从一个简单的测试用例开始,来说明测试驱动的开发方法。完整代码可在 https://github.com/azat-co/fullstack-javascript/tree/master/08-test 获得。

单元测试 Node. js

我们应该有两种方法:

Get all of the messages as an array of JSON objects for the GET /message endpoint using the getMessages() method   Add a new message with properties name and message for POST /messages route via the addMessage() function  

我们将从创建一个空的mb-server.js文件开始。在它出现之后,让我们切换到测试并创建包含以下内容的test.js文件:

var http = require(’http’)

var assert = require(’assert’)

var querystring = require(’querystring’)

var util = require(’util’)

var messageBoard = require(’./mb-server’)

assert.deepEqual(’[{"name":"John","message":"hi"}]’,

messageBoard.getMessages())

assert.deepEqual (’{"name":"Jake","message":"gogo"}’,

messageBoard.addMessage ("name=Jake&message=gogo"))

assert.deepEqual(’[{"name":"John","message":"hi"},{"name":"Jake",message":"gogo"}]’,

messageBoard.getMessages())

请记住,这是一个非常简单的字符串比较,而不是 JavaScript 对象。所以每个空格、引用和大小写都很重要。您可以通过将一个字符串解析成一个 JSON 对象来使比较“更智能”,方法是:

JSON.parse(str)

为了测试我们的假设,我们使用 Node.js 模块的核心断言。它提供了一堆有用的方法,如equal()deepEqual()等。

更高级的库包括 TDD 和/或 BDD 方法的替代接口:

对于更多的测试驱动开发和前沿自动化测试,您可以使用以下库和模块:

您可以暂时将“Hello World”脚本复制到mb-server.js文件中,或者甚至保留为空。如果我们通过终端命令运行test.js:

$ node test.js

我们应该会看到一个错误。大概是这样的:

TypeError: Object #<Object> has no method ’getMessages’

那完全没问题,因为我们还没有写getMessages()方法。因此,让我们这样做,并通过添加两个新方法使我们的应用更有用:获取聊天消息列表和向集合中添加新消息。

带有全局exports对象的mb-server.js文件:

exports.getMessages = function() {

return JSON.stringify(messages)

// Output array of messages as a string/text

}

exports.addMessage = function (data){

messages.push(querystring.parse(data))

// To convert string into JavaScript object we use parse/deserializer

return JSON.stringify(querystring.parse(data))

// Output new message in JSON as a string

}

我们导入依赖关系:

var http = require(’http’)

// Loads http module

var util= require(’util’)

// Usefull functions

var querystring = require(’querystring’)

// Loads querystring module, we’ll need it to serialize and deserialize objects and query strings

并设置端口。如果它是在 env 变量中设置的,我们使用那个值;如果没有设置,我们使用硬编码值 1337:

var port = process.env.PORT || 1337

到目前为止,没什么特别的,对吧?为了存储消息列表,我们将使用一个数组:

var messages=[]

// This array will hold our messages

messages.push({

’name’: ’John’,

’message’: ’hi’

})

// Sample message to test list method

一般来说,像虚拟数据这样的设备属于测试/规范文件,而不属于主要的应用代码库。

我们的服务器代码看起来会稍微有趣一些。为了获得消息列表,根据 REST 方法,我们需要发出一个 GET 请求。对于创建/添加新消息,它应该是 POST 请求。因此,在我们的 createServer 对象中,我们应该添加req.method()req.url()来检查 HTTP 请求类型和 URL 路径。

让我们加载 http 模块:

var http = require(’http’)

我们将需要来自utilquerystring模块的一些方便的函数(来序列化和反序列化对象和查询字符串):

var util= require(’util’)

// Usefull functions

var querystring = require(’querystring’)

// Loads querystring module, we’ll need it to serialize and deserialize objects and query strings

创建一个服务器并将其暴露给外部模块(即test.js):

exports.server=http.createServer(function (req, res) {

// Creates server

在请求处理程序回调中,我们应该检查请求方法是否为 POST,URL 是否为messages/create.json:

if (req.method == ’POST’ && req.url == ’/messages/create.json’) {

// If method is POST and URL is messages/ add message to the array

如果上述条件为真,我们向数组中添加一条消息。但是,data必须在添加之前转换为字符串类型(编码为 UTF-8 ),因为它是一种缓冲区类型:

var message = ’’

req.on(’data’, function(data, msg){

console.log(data.toString(’utf-8’))

message=exports.addMessage(data.toString(’utf-8’))

// Data is type of Buffer and must be converted to string with encoding UTF-8 first

// Adds message to the array

})

这些日志将帮助我们监控终端中的服务器活动:

req.on(’end’, function(){

console.log(’message’, util.inspect(message, true, null))

console.log(’messages:’, util.inspect(messages, true, null))

// Debugging output into the terminal

输出应该是文本格式,状态为 200(正常):

res.writeHead(200, {’Content-Type’: ’text/plain’})

// Sets the right header and status code

我们输出一条带有新创建的对象 ID 的消息:

res.end(message)

// Out put message, should add object id

})

如果方法是 GET,URL 是/messages/list.json,则输出消息列表:

} else

if (req.method == ’GET’ && req.url == ’/messages/list.json’) {

// If method is GET and URL is /messages output list of messages

获取邮件列表:

var body = exports.getMessages()

// Body will hold our output

响应主体将保存我们的输出:

res.writeHead(200, {

’Content-Length’: body.length,

’Content-Type’: ’text/plain’

})

res.end(body)

下一个else是当前面的任何条件都不匹配时。这将设置正确的标题和状态代码:

} else {

res.writeHead(200, {’Content-Type’: ’text/plain’})

// Sets the right header and status code

如果不是上面的两个端点,我们输出一个带有行结束符号的字符串:

res.end(’Hello World\n’)

// Outputs string with line end symbol

}

启动服务器:

}).listen(port)

// Sets port and IP address of the server

现在,我们应该设置服务器的端口和 IP 地址:

console.log(’Server running at``http://127.0.0.1:%s/

我们在test.js (exports 关键字)中公开了单元测试的方法,这个函数以字符串/文本的形式返回一组消息:

exports.getMessages = function() {

return JSON.stringify(messages)

}

addMessage()使用 querystring 中的 parse/deserializer 方法将字符串转换为 JavaScript 对象:

exports.addMessage = function (data){

messages.push(querystring.parse(data))

还以 JSON-as-a-string 格式返回新消息:

return JSON.stringify(querystring.parse(data))

}

下面是mb-server.js减去注释后的完整代码。也可在08-测试中获得:

var http = require(’http’)

// Loads http module

var util= require(’util’)

// Usefull functions

var querystring = require(’querystring’)

// Loads querystring module, we’ll need it to serialize and deserialize objects and query strings

var port = process.env.PORT || 1337

var messages=[]

// This array will hold our messages

messages.push({

’name’: ’John’,

’message’: ’hi’

})

// Sample message to test list method

exports.server=http.createServer(function (req, res) {

// Creates server

if (req.method == ’POST’ && req.url == ’/messages/create.json’) {

// If method is POST and URL is messages/ add message to the array

var message = ’’

req.on(’data’, function(data, msg){

console.log(data.toString(’utf-8’))

message=exports.addMessage(data.toString(’utf-8’))

// Data is type of Buffer and must be converted to string with encoding UTF-8 first

// Adds message to the array

})

req.on(’end’, function(){

console.log(’message’, util.inspect(message, true, null))

console.log(’messages:’, util.inspect(messages, true, null))

// Debugging output into the terminal

res.writeHead(200, {’Content-Type’: ’text/plain’})

// Sets the right header and status code

res.end(message)

// Out put message, should add object id

})

} else

if (req.method == ’GET’ && req.url == ’/messages/list.json’) {

// If method is GET and URL is /messages output list of messages

var body = exports.getMessages()

// Body will hold our output

res.writeHead(200, {

’Content-Length’: body.length,

’Content-Type’: ’text/plain’

})

res.end(body)

} else {

res.writeHead(200, {’Content-Type’: ’text/plain’})

// Sets the right header and status code

res.end(’Hello World\n’)

// Outputs string with line end symbol

}

}).listen(port)

// Sets port and IP address of the server

console.log(’Server running at``http://127.0.0.1:%s/

exports.getMessages = function() {

return JSON.stringify(messages)

// Output array of messages as a string/text

}

exports.addMessage = function (data){

messages.push(querystring.parse(data))

// To convert string into JavaScript object we use parse/deserializer

return JSON.stringify(querystring.parse(data))

// Output new message in JSON as a string

}

要查看它,请访问。您应该会看到一条示例消息。

或者,您可以使用终端命令来获取消息:

$ curl http://127.0.0.1:1337/messages/list.json

使用命令行界面发出 POST 请求:

$ curl -d "name=BOB&message=test"http://127.0.0.1:1337/messages/create.json

当您刷新时,您应该在服务器终端窗口中得到输出和一条新消息“test”。不用说,这三项测试都应该通过。

您的应用可能会随着更多的方法、要解析的 URL 路径和条件而变得更大。这就是框架派上用场的地方。它们提供了处理请求的助手和其他好东西,如静态文件支持、会话等。在这个例子中,我们故意没有使用任何类似 Express ( http://expressjs.com/ )或 Restify ( http://mcavage.github.com/node-restify/ )的框架。其他值得注意的 Node.js 框架:

要获得精心挑选的框架列表,请查看(node framework。com 。改进应用的方法:

  • 通过添加对象比较而不是字符串比较来改进现有的测试用例
  • 将种子数据从mb-server.js移动到test.js
  • 添加测试用例来支持您的前端(例如,向上投票、用户登录)
  • 添加方法来支持您的前端(例如,向上投票、用户登录)
  • 为每条消息生成唯一的 id,并将它们存储在哈希中,而不是存储在数组中
  • 安装 Mocha 和 re-factor test.js,以便它使用这个库

到目前为止,我们一直将消息存储在应用内存中,所以每次应用重启时,我们都会丢失消息。要修复它,我们需要添加一个持久性,方法之一就是使用 MongoDB 这样的数据库。

摘要

在这一章中,我们已经讨论了一些重要的主题,这些主题将为我们打下基础。他们展示了 Node.js 中的“Hello World”应用,一些最重要的核心模块列表,NPM 工作流,将 Node.js 应用部署到 Heroku 和 Windows Azure 的详细命令;也是测试驱动开发实践的一个例子。

七、MongoDB 简介

What is Oracle bone inscriptions? A bunch of people. All our products are just the ideas in these people's minds-the ideas that people put into computers and tested, which proved to be the best ideas for databases or programming languages. - Larry Ellision

在本章中,我们将探讨以下主题:

  • 蒙戈布贝壳
  • Node.js 的 MongoDB 本机驱动程序
  • MongoDB on Heroku with MongoLab
  • 留言板:MongoDB 版本

MongoDB 是一个 NoSQL 文档存储数据库。它具有可扩展性和高性能。它没有模式,所以所有的逻辑和关系都在应用层实现。你可以使用像水线或者猫鼬这样的 ODM。MongoDB 使用 JavaScript 接口,完成了浏览器、服务器、数据库层的全栈 JavaScript 栈拼图。有了 MongoDB,我们可以对所有三层使用一种语言。开始使用 MongoDB 最简单的方法是使用它的 shell,也就是 REPL (read-eval-print-loop)。

蒙戈布贝壳

如果您还没有安装,请从 mongodb.org/downloads 安装最新版本的 MongoDB。更多说明,请参考第二章中的数据库:MongoDB 部分。您可能需要按照说明创建一个数据文件夹。

现在,从解压归档文件的文件夹中,使用以下命令启动mongod服务:

$ ./bin/mongod

localhost:28017时,您应该能够在您的终端和浏览器中看到信息。

对于 MongoDB shell,或mongo,在新的终端窗口中启动(重要!),并在同一文件夹中执行以下命令:

$ ./bin/mongo

根据您的 MongoDB shell 版本,您应该会看到类似这样的内容:

MongoDB shell version: 2.0.6

connecting to: test

要测试数据库,使用类似 JavaScript 的界面和命令savefind:

> db.test.save( { a: 1 } )

> db.test.find()

更详细的分步说明可以在数据库中找到:第二章的的 MongoDB 部分。

其他一些有用的 MongoDB shell 命令来自 MongoDB 和 mongose cheat sheet(https://gum.co/mongodb/git-874e6fb4):

  • > show dbs:显示服务器上的数据库
  • > use DB_NAME:选择数据库DB_NAME
  • > show collections:显示所选数据库中的收藏
  • > db.COLLECTION_NAME.find():对名称为 COLLECTION_NAME 的集合执行查找查询,查找任何项目
  • > db.COLLECTION_NAME.find({"_id": ObjectId("549d9a3081d0f07866fdaac6")}):对 COLLECTION_NAME 名称的集合执行查找查询,查找 ID 为 549d9a3081d0f07866fdaac6 的项目
  • > db.COLLECTION_NAME.find({"email": /gmail/}):对 COLLECTION_NAME 名称的集合执行查找查询,查找邮件属性与/gmail匹配的项目
  • > db.COLLECTION_NAME.update(QUERY_OBJECT, SET_OBJECT):对 COLLECTION_NAME 名称的集合执行更新查询,更新 QUERY_OBJECT 与 SET_OBJECT 匹配的项目
  • > db.COLLECTION_NAME.remove(QUERY_OBJECT):对 COLLECTION_NAME 集合中符合 QUERY_OBJECT 条件的项目进行删除查询
  • > db.COLLECTION_NAME.insert(OBJECT):将对象添加到名称为集合名称的集合中

因此,从一个新的 shell 会话开始,您可以执行以下命令来创建、更改和删除文档:

> help

> show dbs

> use board

> show collections

> db.messages.remove();

> var a = db.messages.findOne();

> printjson(a);

> a.message = "hi";

> a.name = "John";

> db.messages.save(a);

> db.messages.find({});

> db.messages.update({name: "John"},{$set: {message: "bye"}});

> db.messages.find({name: "John"});

> db.messages.remove({name: "John"});

你可以下载 MongoDB 和 Mongoose cheatsheet 作为 PDF ( https://gumroad.com/l/mongodb/fsjs-CB07C579 #)或者在 https://github.com/mongodb/node-mongodb-native/#data-types 在线查看。

MongoDB 交互 shell 的完整概述可以在 mongodb.org 上找到:概述——MongoDB 交互 Shell ( https://docs.mongodb.org/manual/tutorial/getting-started-with-the-mongo-shell/ )。

断续器

二进制 JSON 或 BSON 是 MongoDB 使用的一种特殊数据类型。它在符号上类似于 JSON,但是支持更多更复杂的数据类型,比如 buffer 或 date。

关于 BSON 有一点需要注意:MongoDB 中的 ObjectID 相当于 MongoDB Native Node.js 驱动程序中的 ObjectId(即,确保使用正确的大小写)。否则你会得到一个错误。更多关于类型:MongoDB 中的 ObjectId(http://www.mongodb.org/display/DOCS/Object+IDs)vsMongoDB 原生 Node 中的数据类型。js 干燥机 ( https://github.com/mongodb/node-mongodb-native/#data-types )。带有mongodb.ObjectID() : collection.findOne({_id: new ObjectID(idString)}, console.log) // ok的 Node.js 代码示例。另一方面,在 MongoDB shell 中,我们使用了:db.messages.findOne({_id:ObjectId(idStr)});

MongoDB 本地驱动程序

引导您完成实施并演示项目的补充视频: http://bit.ly/1QnqZSk

我们将使用 MongoDB 的 Node.js 本地驱动程序( https://github.com/christkv/node-mongodb-native )从 Node.js 应用访问 MongoDB。完整文档也可在 http://mongodb.github.com/node-mongodb-native/api-generated/db.html 获取。

要为 Node.js 安装 MongoDB 本机驱动程序,请使用:

$ npm install mongodb

更多详情请见 http://www.mongodb.org/display/DOCS/node.JS

不要忘记在package.json文件中包含依赖关系:

{

"name": "node-example",

"version": "0.0.1",

"dependencies": {

"mongodb":"",

...

},

"engines": {

"node": ">=0.6.x"

}

}

或者,对于您自己的开发,您可以使用其他映射器,它们是本地驱动程序的扩展:

这个小例子将测试我们是否可以从 Node.js 脚本连接到本地 MongoDB 实例。

在我们安装了库之后,我们可以在我们的app.js文件中包含mongodb库:

var util = require(’util’)

var mongodb = require (’mongodb’)

这是建立到 MongoDB 服务器的连接的方法之一,其中 DB 变量将保存对指定主机和端口上的数据库的引用:

var Db = mongodb.Db

var Connection = mongodb.Connection

var Server = mongodb.Server

var host = ’127.0.0.1’

var port = 27017

var db=new Db (’test’, new Server(host,port, {}))

要实际打开一个连接:

db.open(function(error, connection){

// Do something with the database here

db.close()

})

为了检查我们是否有连接,我们需要处理error。同样,让我们用db.admin()获取管理对象,用listDatabases()获取数据库列表:

var db=new Db (’test’, new Server(host, port, {}))

db.open(function(error, connection){

console.log(’error: ’, error)

var adminDb = db.admin()

adminDb.listDatabases(function(error, dbs) {

console.log(’error: ’, error)

console.log(’databases: ’, dbs.databases)

db.close()

})

})

这段代码片段可在https://github.com/mongodb/node-mongodb- native/#data-types获得。如果我们运行它,它应该在终端中输出“connected”。当你有疑问并且需要检查一个对象的属性时,在util模块中有一个有用的方法:

console.log(util.inspect(db))

现在,您可能希望在云中设置数据库,并从 Node.js 脚本测试连接。

蒙戈布 on Heroku:蒙戈布

引导您完成实施并演示项目的补充视频: http://bit.ly/1Qnr8Fn

在您使显示“已连接”的应用在本地工作之后,是时候稍微修改它并将其作为服务部署到平台上了(例如 Heroku)。

我们推荐使用 MongoLab 附加组件( https://elements.heroku.com/addons/mongolab )。MongoLab 插件提供了一个基于浏览器的 GUI 来查找和操作数据和集合。更多信息请访问 https://elements.heroku.com/addons/mongolab#docs

请注意,即使您选择了免费版本,也可能需要提供您的信用卡信息才能使用 MongoLab。不过,你不应该被起诉。

为了连接到数据库服务器,有一个数据库连接 URL(又名 MongoLab URL/URI),这是一种传输所有必要信息的方法,以便在一个字符串中连接到数据库。

数据库连接字符串MONGOLAB_URI具有以下格式:

mongodb://user:pass@server_NAME.mongolab.com:PORT/db_name

您可以从 Heroku 网站复制 MongoLab URL 字符串(并对其进行硬编码),或者从 Node.js process.env对象获取字符串:

process.env.MONGOLAB_URI

或者

var connectionUri = url.parse(process.env.MONGOLAB_URI)

全局对象进程通过process.env访问环境变量。这些变量通常用于传递数据库主机名和端口、密码、API 键、端口号以及其他不应该硬编码到主逻辑中的系统信息。

为了让我们的代码在本地和 Heroku 上都能工作,我们可以使用逻辑 OR 操作符||并在环境变量未定义的情况下分配一个本地主机和端口:

var port = process.env.PORT || 1337

var dbConnUrl = process.env.MONGOLAB_URI ||

’mongodb://127.0.0.1:27017/test’

这里是我们更新的跨环境就绪app.js文件( https://github.com/azat-co/fullstack-javascript/tree/master/10-db-connect-heroku )。我添加了一个方法来获取集合列表listCollections,而不是获取数据库列表(我们现在在 MongoLab 中只有一个数据库):

var util = require(’util’)

var url = require(’url’)

var client = require (’mongodb’).MongoClient

var dbConnUrl = process.env.MONGOLAB_URI ||

’mongodb://127.0.0.1:27017/test’

console.log(’db server: ’, dbConnUrl)

client.connect(dbConnUrl, {}, function(error, db){

console.log(’error: ’, error)

db.listCollections().toArray(function(err, collections) {

console.log(’error: ’, error)

console.log(’collections: ’, collections)

db.close()

})

})

通过添加MONGOLAB_URIapp.js进行修改后,我们现在可以初始化 Git 存储库,创建一个 Heroku 应用,向其中添加 MongoLab 附加组件,并使用 Git 部署该应用。

利用与前面示例中相同的步骤创建一个新的 git 存储库:

$ git init

$ git add .

$ git commit -am ’initial commit’

创建雪松栈 Heroku 应用:

$ heroku create

如果一切顺利,你应该可以看到一条消息,告诉你新的 Heroku 应用名称(和网址)以及一条消息,远程已被添加。在您的本地 git 中拥有 remote 是至关重要的;您可以随时通过以下方式查看遥控器列表:

$ git remote show

要在现有的 Heroku 应用上安装免费的 MongoLab(加载项基于每个应用),请使用:

$ heroku addons:create mongolab:sandbox

或者使用您的 Heroku 凭证登录 Heroku ( https://elements.heroku.com/addons/mongolab ),并为特定的 Heroku 应用选择 MongoLab Free(如果您知道该应用的名称)。

项目文件夹需要有Procfilepackage.json。你可以从 https://github.com/azat-co/fullstack-javascript/tree/master/10-db-connect-heroku 中复制它们。

现在,您可以通过以下方式将代码推送到 Heroku:

$ git push heroku master

享受应该告诉您部署成功的日志。现在查看以下命令的输出:

$ heroku logs

结果将是这样的:

2015-12-01T12:34:51.438633+00:00 app[web.1]: db server:  mongodb://heroku_cxgh54g6:9d76gspc45v899i44sm6bn790c@ds035617.mongolab.com:34457/heroku_cxgh54g6

2015-12-01T12:34:53.264530+00:00 app[web.1]: error:  null

2015-12-01T12:34:53.236398+00:00 app[web.1]: error:  null

2015-12-01T12:34:53.271775+00:00 app[web.1]: collections:  [ { name: ’system.indexes’, options: {} },

2015-12-01T12:34:53.271778+00:00 app[web.1]:   { name: ’test’, options: { autoIndexId: true } } ]

如果您让app.js和修改过的app.js文件工作,让我们通过添加一个 HTTP 服务器来增强,这样‘已连接’消息将显示在浏览器中,而不是终端窗口中。为此,我们将把服务器对象实例化封装在一个数据库连接回调中(file 11-d b-server/app . js athttps://github.com/azat-co/fullstack-javascript/blob/master/11-db/app.js)。

引导您完成实施并演示项目的补充视频: http://bit.ly/1Qnrmwr

var util = require(’util’)

var url = require(’url’)

var http = require(’http’)

var mongodb = require (’mongodb’)

var client = require (’mongodb’).MongoClient

var port = process.env.PORT || 1337

var dbConnUrl = process.env.MONGOLAB_URI || ’mongodb://@127.0.0.1:27017/test’

client.connect(dbConnUrl, {}, function(error, db) {

console.log(’error: ’, error)

db.listCollections().toArray(function(error, collections) {

console.log(’error: ’, error)

console.log(’collections: ’, collections)

var server = http.createServer(function (request, response) { // Creates server

response.writeHead(200, {’Content-Type’: ’text/plain’})   // Sets the right header and status code

response.end(util.inspect(collections))  // Outputs string with line end symbol

})

server.listen(port, function() {

console.log(’Server is running at %s:%s ’, server.address().address, server.address().port) // Sets port and IP address of the server

})

db.close()

})

})

最终 Heroku 部署就绪项目位于 https://github.com/azat-co/fullstack-javascript/tree/master/11-db-serverunder

部署完成后,您应该能够打开 Heroku 提供的 URL 并查看收藏列表。如果它是一个新创建的应用,数据库为空,则不会有收藏。您可以使用 Heroku 中的 MongoLab web 界面创建一个集合。

关于原生 MongoDB 驱动程序的更多信息,请查看 http://mongodb.github.io/node-mongodb-native/api-articles/nodekoarticle1.html

留言板:MongoDB 版本

引导您完成实施并演示项目的补充视频: http://bit.ly/1QnsfoE .

我们应该已经为编写 Node.js 应用做好了一切准备,它既可以在本地运行,也可以在 Heroku 上运行。源代码可在 https://github.com/azat-co/fullstack-javascript/tree/master/12-board-api-mongonder 获得。应用的结构很简单:

/12-board-api-mongo

-web.js

-Procfile

-package.json

这就是web.js的样子;首先,我们包括我们的图书馆:

var http = require(’http’)

var util = require(’util’)

var querystring = require(’querystring’)

var client = require(’mongodb’).MongoClient

然后输出一个连接 MongoDB 的神奇字符串:

var uri = process.env.MONGOLAB_URI || ’mongodb://@127.0.0.1:27017/messages’

注意,URI/URL 格式包含可选的数据库名称,我们的集合将存储在其中。请随意将其更改为其他名称:例如,“rpjs”或“test”。

我们将所有逻辑以回调函数的形式放在开放连接中:

client.connect(uri, function(error, db) {

if (error) return console.error(error)

我们用下面的语句获取集合:

var collection = db.collection(’messages’)

现在,我们可以实例化服务器并设置逻辑来处理我们的端点/路由。我们需要在 GET /messages/list.json上获取文档:

var app = http.createServer( function (request, response) {

if (request.method === ’GET’ && request.url === ’/messages/list.json’) {

collection.find().toArray(function(error,results) {

response.writeHead(200,{ ’Content-Type’: ’text/plain’})

console.dir(results)

response.end(JSON.stringify(results))

})

在帖子/messages/create.json上,我们插入了文档:

} else if (request.method === ’POST’ && request.url === ’/messages/create.json’) {

request.on(’data’, function(data) {

collection.insert(querystring.parse(data.toString(’utf-8’)), {safe:true}, function(error, obj) {

if (error) throw error

response.end(JSON.stringify(obj))

})

})

} else {

如果客户端请求与上述任何条件都不匹配,就会显示这一信息。当我们试图去http://localhost:1337而不是http://localhost:1337/messages/list.json时,这是一个很好的提醒:

response.end(’Supported endpoints: \n/messages/list.json\n/messages/create.json’)

}

})

var port = process.env.PORT || 1337

app.listen(port)

})

Note

我们不必在集合/实体名称后使用额外的单词;也就是说,对于所有的 HTTP 方法,比如 GET、POST、PUT、DELETE,只使用/messages,而不是/messages/list.json/messages/create.json,是非常好的。如果您在应用代码中更改它们,请确保使用更新的 CURL 命令和前端代码。

要通过 CURL 终端命令运行测试:

$ curl http://localhost:5000/messages/list.json

或者在http://locahost:1337/messages/list.json位置打开浏览器。

它应该会给你一个空数组:[],没问题。然后发布一条新消息:

$ curl  -d "username=BOB&message=test" http://localhost:5000/messages/create.json

现在我们必须看到一个包含新创建元素的 ObjectID 的响应,例如:[{"username":"BOB","message":"test","_id":"51edcad45862430000000001"}]。您的 ObjectId 可能有所不同。

如果在本地一切正常,尝试将其部署到 Heroku。

要在 Heroku 上测试应用,您可以使用相同的 CURL 命令( http://curl.haxx.se/docs/manpage.html ),用您独特的 Heroku 应用的主机/URL 替换http://localhost/ or “ http://127.0.0.1 “:

$ curlhttp://your-app-name.herokuapp.com/messages/list.json

$ curl -d "username=BOB&message=test"

http://your-app-name.herokuapp.com/messages/create.json

也可以通过 Mongo shell: $ mongo终端命令,然后是use twitter-clonedb.messages.find()来仔细检查数据库;或通过 MongoHub ( https://github.com/bububa/MongoHub-Mac )、mongoui ( https://github.com/azat-co/mongoui )、mongo-express( https://github.com/andzdroid/mongo-express )或在 MongoLab 的情况下通过其可在 heroku.com 网站访问的网络界面。

如果你想用另一个域名代替 http://your-app-name.herokuapp.com ,你需要做两件事:

Tell Heroku your domain name: $ heroku domains:add www.your-domain-name.com   Add the CNAME DNS record in your DNS manager to point to http://your-app-name.herokuapp.com .  

有关自定义域名的更多信息,请访问 devcenter.heroku.com/articles/custom-domains

提示为了更有效地开发,我们应该尽可能地自动化;也就是说,使用测试代替 CURL 命令。在奖励章节中有一篇关于 Mocha 库的文章,与superagentrequest库一起,为这类任务节省了时间。

摘要

在本章中,我们已经介绍了 MongoDB 数据库及其 shell。MongoDB 使用 JSON 的扩展版本,称为 BSON。然后我们用原生 MongoDB 驱动切换到 Node.js。许多其他 MongoDB Node.js 库依赖于原生驱动程序,并在其上构建。正因如此,知道就好。为了在 Heroku 上使用 MongoDB,我们使用了 MongoLab 插件(神奇的MONGOLAB_URI)。最后,我们使用获得的知识为留言板应用添加持久性。