所有文章搬运自我的个人主页:sheilasun.me
《NodeJS开发指南》这本书用来NodeJS入门真是太好了,而且书的附录部分还讲到了闭包、this等JavaScript常用特性。第一遍看的时候很多地方没看明白,再看一遍的时候就清晰多了。跟着书上第五章完成了微博实例Microblog,在此总结一下开发中遇到的问题。这本书出得较早(2012年出版),因此书中的express框架部分与新的Express 4.x也有些不同,下文都将会提到,并给出新的写法。
Microblog是一个出于学习目的设计的简单微博系统,核心功能是发表信息,其他功能包括用户的注册及登入登出等。
安装
着手开发之前先搭建一下环境吧!
安装 NodeJS
去NodeJS官网下载页面上根据自己的操作系统下载对应的安装包并安装之后,我们就有了NodeJS和npm环境。npm是Node的包管理工具,会在安装NodeJS时一并安装。可以用以下命令查看版本号验证我们的安装成功与否:
sunjingdeMacBook-Pro:microblog sunjing$ node -v
v0.12.2
sunjingdeMacBook-Pro:microblog sunjing$ npm -v
2.7.4
安装 express-generator
我们使用express作为开发框架,与书上不同的是,新版的express已经不可以使用:
express -t ejs microblog
来快速建立网站结构,而是要额外安装express-generator来生成网站框架。
npm install -g express-generator
'-g' 参数表示全局安装,这样我们就可以直接在命令行上使用了。
创建项目
创建框架
在要存放项目的目录打开终端或命令行,使用以下命令建立网站框架:
express -e microblog
'-e' 参数表示使用ejs作为模板引擎,如不加会默认使用jade。
执行命令后会看到如下结果:
sunjingdeMacBook-Pro:~ sunjing$ express -e microblog
create : microblog
create : microblog/package.json
create : microblog/app.js
create : microblog/public
create : microblog/public/javascripts
create : microblog/public/images
create : microblog/public/stylesheets
create : microblog/public/stylesheets/style.css
create : microblog/routes
create : microblog/routes/index.js
create : microblog/routes/users.js
create : microblog/views
create : microblog/views/index.ejs
create : microblog/views/error.ejs
create : microblog/bin
create : microblog/bin/www
install dependencies:
$ cd microblog && npm install
run the app:
$ DEBUG=microblog:* ./bin/www
编辑依赖项
打开项目目录,找到package.json文件,将其中的依赖项修改如下,在默认的依赖项里添加上我们后面将会用到的一些模块。
"dependencies": {
"body-parser": "~1.12.0",
"cookie-parser": "~1.3.4",
"debug": "~2.1.1",
"ejs": "~2.3.1",
"express": "~4.12.2",
"express-session":"*",
"morgan": "~1.5.1",
"serve-favicon": "~2.2.0",
"mongodb":"*",
"connect-mongo":"*",
"connect-flash":"*",
"log4js":"*"
}
关于依赖项版本号的书写规则可以查看Difference between tilde(~) and caret(^) in package.json,需要注意的是虽然前面我们已经全局安装了express-generator,这里我们仍然需要本地安装express模块。
安装依赖项
好啦,现在可以安装我们上面列出的依赖项了,在项目目录里运行如下命令,即会多出来一个node_modules目录,存放了各依赖模块。
sunjingdeMacBook-Pro:microblog sunjing$ npm install
运行项目
在新的express框架下,书上用的
node app.js
启动命令已经不再适用,要改用
sunjingdeMacBook-Pro:microblog sunjing$ npm start
> microblog@0.0.0 start /Users/sunjing/microblog
> node ./bin/www
为什么呢?因为在package.json中有这样配置
"scripts": {
"start": "node ./bin/www"
}
所以用 npm start 命令等同于:
sunjingdeMacBook-Pro:microblog sunjing$ node ./bin/www
我们后面使用forever管理进程的时候还会提到这里。
这时候我们已经可以在浏览器里用3000端口http://localhost:3000/查看我们的网站页面了。
编写代码
app.js
首先偷偷看一下根目录下的app.js都有些什么。
var express = require('express');
var path = require('path');
var favicon = require('serve-favicon');
var logger = require('morgan');
var cookieParser = require('cookie-parser');
var bodyParser = require('body-parser');
var routes = require('./routes/index');
var users = require('./routes/users');
var app = express();
...
NodeJS采用的是CommonJS规范,用require方法加载模块,模块对应一个js或json文件等。这里用该方法返回各模块文件内部的exports对象,并用一系列变量接收。
// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');
// uncomment after placing your favicon in /public
//app.use(favicon(__dirname + '/public/favicon.ico'));
app.use(logger('dev'));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));
前两行告诉程序该去哪里找视图模板,并将模板引擎设置为ejs。后面用app.use加载了一系列中间件,当http请求到来时,会依次被括号里这些中间件函数处理,可想而知,这些函数内部一定是有
next()
语句的,不然流程就被截断了。
app.use(express.static(path.join(__dirname, 'public')));
这句提供了静态文件支持,将public文件夹“放”到根目录下,比如bootstrap.min.css明明存放在以下目录:
/Users/sunjing/microblog/public/stylesheets
我们却可以用
http://localhost:3000/stylesheets/bootstrap.min.css
访问到该文件。
当然如果用nginx服务器的话,也可以通过修改nginx的conf文件由它来提供静态服务,具体的规则可以查看http://www.open-open.com/lib/view/open1344524391624.html。这时候,可以把代码中的static中间件加载删去,“可以节省NodeJS的开销”。
app.js中还分别为development和production模式配置了不同的错误处理函数,主要的区别在于前一种模式下打印出更多的错误信息,便于开发调试,而后一种则并不向用户透露错误的栈信息。
var routes = require('./routes/index');
...
app.use('/', routes);
这两句告诉程序该用哪个路由文件。接下来我们就来看看这个路由文件都有什么内容吧。
routes/index.js
var express = require('express');
var router = express.Router();
/* GET home page. */
router.get('/', function(req, res, next) {
res.render('index', { title: 'Express' });
});
module.exports = router;
这是express自动为我们生成的内容,它告诉程序,当访问网站根目录时候,就根据传入的title变量用模板引擎渲染views下面的index.ejs文件生成html文件并返回给浏览器。
根据微博系统的功能,我们还需要添加其他路由:
//首页
router.get('/', function(req, res, next) {});
//用户个人页
router.get('/u/:user', function(req, res, next) {});
//发言页
router.post('/post', function(req, res, next) {});
//注册页
router.get('/reg', function(req, res, next) {});
//提交注册
router.post('/reg', function(req, res, next) {});
//登录页
router.get('/login', function(req, res, next) {});
//提交登录
router.post('/login', function(req, res, next) {});
//登出页
router.get('/logout', function(req, res, next) {});
app.get和app.post分别处理get和post请求,还有一种app.all形式,对于post和get请求都做处理。
模板
所有模板ejs文件都放在views文件夹下,与书中不同的是,express 4.x已经不支持layout.ejs写法,如要使用需额外安装express-partials模块,或者用include方法,如首页的模板文件主要结构如下:
<%- include header %>
<%if(!user){%>
<div class="container">
<div class="jumbotron">
<h1>欢迎来到MicroBlog</h1>
<p>MicroBlog是一个基于 Node.js 的试验站点</p>
<p>
<a class="btn btn-primary btn-lg" href="/login">登录</a>
<a class="btn btn-lg btn-default" href="/reg">立即注册</a>
</p>
</div>
</div>
<%} else{%>
<%- include say %>
<%}%>
<%- include posts %>
<script src="/javascripts/jquery-2.1.4.min.js"></script>
<script src="/javascripts/bootstrap.min.js"></script>
<%- include footer %>
</body>
</html>
其中header、say、posts、footer对应的是四个ejs模板文件,user则是传入的变量,这里面的逻辑是如果用户已登录,即if(!user)返回false,则显示发言模块,否则显示欢迎模块。
另一个与书上不同的是,bootstrap的类名写法也有些变化,如栅格布局中占四列的写法不再是“span4”,而是“col-md-4”、“col-sm-4“或者”col-lg-4"(媒体查询,对应不同的屏幕尺寸)。
数据库
网站采用mongodb作为后台数据库,首先去MongoDB官网下载安装mongodb。
使用以下命令指定数据位置,启动mongo服务。
sudo mongod --dbpath=/Users/sunjing/mongodata
如果能看到终端输出
2015-08-09T16:49:58.019+0800 I NETWORK [initandlisten] waiting for connections on port 27017
说明服务已经启动成功,可以在程序中访问数据库了。需要注意的是当代码中有访问mongodb数据库时,在程序启动前需要先启动mongo服务,否则程序会报错。
在代码中访问数据库
首先我们在项目根目录下新建一个文件Settings.js存储数据库配置,文件内容如下:
module.exports={
cookieSecret:'microblogbysheila',
db:'mongodata', //数据库名
host:'localhost' //数据库地址
};
再在models目录下新建一个数据库连接模块db.js,文件内容如下:
/** 数据库连接配置模块 **/
var settings=require('../Settings');
var Db=require('mongodb').Db;
var Server=require('mongodb').Server;
/** 输出创建好的数据库连接 **/
module.exports=new Db(settings.db,new Server(settings.host,27017,{}));
其中27017指的是mongod服务默认的端口号,这样我们就可以在其他模块中加载这个模块获得数据库连接进行增删改查数据的操作,比如User模块(对应models下的user.js文件)需要一项查找某用户名是否已存在的功能,代码如下:
var mongodb=require('./db');
//查询数据库中是否已存在用户
User.get=function get(username,callback){
mongodb.open(function(err,db){
if(err){ //连接打开失败
return callback(err);
}
db.collection('users',function(err,collection){
if(err){
mongodb.close();
return callback(err);
}
collection.findOne({name:username},function(err,doc){
mongodb.close();
if(doc){
var user=new User(doc);
callback(err,user);
}else{
callback(err,null);
}
})
});
})
}
这段代码看起来稍微复杂一些,其实只是因为异步的回调函数书写让它看起来复杂了,其实实现的功能很简单,打开网站所用的数据库,在'users'集合中查找name键值等于传入的username的单个文档,不管连接打开成功与否,查到文档与否,均要调用传入的回调函数,只是如果查到了会将结果作为参数传入回调函数。
基本的东西了解了以后,就可以根据具体的业务逻辑去实现各项功能了,这里就不写出来了,按照书上写的跟着做就可以了,至于新旧版本api不一样的问题,可以戳这篇博客→《nodejs开发指南》微博实例express4.x版,写得非常详细,我在开发的过程中就参考了很多。
需要注意的是,开发过程中,如果不是用supervisor命令来启动程序的话,每次对js文件做了修改,都需要重新运行一下才能生效。
部署
开发完网站本地测试没问题之后,我们把它部署到生产环境。安装好后nodejs及MongoDB后,将项目文件夹复制到服务器上,修改一下环境变量NODE_ENV为production即可。执行如下命令:
NODE_ENV=production npm start
或者:
NODE_ENV=production node ./bin/www
但是仅仅这样的话,如果别人访问过程中触发了代码某处隐含的一个bug,进程就结束了,网站就挂了,所以我们引入forever来管理进程。forever的详细介绍可以戳这篇文→Nodejs服务器管理模块forever,文中是这样介绍forever的:
forever是一个简单的命令式nodejs的守护进程,能够启动,停止,重启App应用。forever完全基于命令行操作,在forever进程之下,创建node的子进程,通过monitor监控node子进程的运行情况,一旦文件更新,或者进程挂掉,forever会自动重启node服务器,确保应用正常运行。
我们在服务器上安装上forever:
npm install -g forever
改用如下命令运行程序:
NODE_ENV=production forever start ./bin/www
这样就不怕网站崩溃啦。
完整的代码可以戳我的Github→sheila1227/NodeJS-Microblog
运行实例可以戳→Microblog