說明:該學習筆記參考《Node.js開發(fā)指南》,但是選用的模板和書中不同,添加了自己的理解和適當?shù)难a充!僅供參考!
我們打算從零開始用Node.js實現(xiàn)一個微博系統(tǒng),功能包括路由控制、頁面模板、數(shù)據(jù)庫訪問、用戶注冊、登錄、用戶會話等內(nèi)容。在這里我們會使用Express框架、MVC設(shè)計模式、Jkig模板和MongoDB數(shù)據(jù)庫的操作。
構(gòu)建項目
1 Express 應(yīng)用生成器
通過應(yīng)用生成器工具 express 可以快速創(chuàng)建一個應(yīng)用的骨架。執(zhí)行一下命令進行安裝到全局環(huán)境中
npm install express-generator -g

我們可以看到同時安裝了一些依賴。在當前目錄下創(chuàng)建blog的應(yīng)用。
express blog
創(chuàng)建完成后進入應(yīng)用。應(yīng)用目錄如下:

然后安裝所有依賴包。
npm install
啟動項目。
(windows下)
set DEBUG=blog & npm start
(Linux平臺下)
DEBUG=blog npm start
打開瀏覽器,訪問http://127.0.0.1:3000即可看到應(yīng)用。
選定swig模板引擎
express構(gòu)建的應(yīng)用默認使用ejs模板引擎,本人覺得這種奇怪的東東暫時難以接受,之前做過django的項目,最后選定swig,也比較看好它!
1 安裝swig
npm install swig
2 在app.js中配置如下:
var swig = require('swig');
var swig = new swig.Swig();
app.engine('html', swig.readerFile);
app.set('view engine', 'html');
3 模板編寫
移除views下的*.jade,創(chuàng)建同名的*.html。
編寫index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>index</title>
</head>
<body>
Welcome, {{ title }}
</body>
</html>
啟動服務(wù)
set DEBUG=blog &npm start
打開瀏覽器訪問http://127.0.0.1:3000,能夠看到:Welcome, Express
使用Bootstrap和界面設(shè)計
我們選定Bootstrap開始設(shè)計我們的界面。首先下載,解壓之后,將文件夾改名為bootstrap-dist并放在public/中,同時下載最新的jquery,放在public/javascripts/中。

這里補充一下Swig的使用參考。
修改layout.html中的代碼:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>{% block title %}My Blog{% endblock %}</title>
{% block head %}
<link rel="stylesheet" href="/bootstrap-dist/css/bootstrap.min.css">
{% endblock %}
</head>
<body>
{% block content %}{% endblock %}
<script src="/javascripts/jquery-3.2.1.js"></script>
<script src="/bootstrap-dist/js/bootstrap.js"></script>
</body>
</html>
在views/下創(chuàng)建login.html,并編寫代碼如下:
{% extends 'layout.html' %}
{% block title %}用戶登錄{% endblock %}
{% block content %}
<div class="container" style="margin-top: 30px;">
<div class="row clearfix">
<div class="col-md-12 column">
<form class="form-horizontal" role="form" method="post" action="/users/login">
<div class="form-group">
<label for="inputEmail3" class="col-sm-2 control-label">用戶名</label>
<div class="col-sm-10">
<input type="text" class="form-control" id="inputText3" />
</div>
</div>
<div class="form-group">
<label for="inputPassword3" class="col-sm-2 control-label">密碼</label>
<div class="col-sm-10">
<input type="password" class="form-control" id="inputPassword3" />
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<div class="checkbox">
<label><input type="checkbox" />記住我</label>
</div>
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<button type="submit" class="btn btn-default">登陸</button>
</div>
</div>
</form>
</div>
</div>
</div>
{% endblock %}
目前登錄的界面已經(jīng)創(chuàng)建完成,接下來就是添加路由了。修改routes/users.js文件,添加login GET視圖后文件中代碼如下:
var express = require('express');
var router = express.Router();
/* GET users listing. */
router.get('/', function(req, res, next) {
res.send('respond with a resource');
});
router.get('/login', function(req, res){
res.render('login');
})
module.exports = router;
重新啟動服務(wù),瀏覽器訪問http://127.0.0.1:3000/users/login,即可以看到登錄輸入框,則成功!

數(shù)據(jù)持久化——MongoDB
從官網(wǎng)下載并安裝。
連接數(shù)據(jù)庫
打開工
程目錄中的package.json,在dependencies屬性中添加以下代碼代碼:
"connect-mongo": ">= 0.1.7",
"mongodb": ">= 0.9.9"
然后運行npm install更新依賴的模塊。接下來在工程的目錄中創(chuàng)建settings.js文件,這個文件用于保存數(shù)據(jù)庫的連接信息。
將用到的數(shù)據(jù)庫起名為blog:
module.exports = {
cookieSecret: "blogid",
db: "blog",
host: "localhost",
port: 27017
};
db是數(shù)據(jù)庫的名稱,host是數(shù)據(jù)庫的地址,port是數(shù)據(jù)庫的端口。cookieSecret用于Cookie加密與數(shù)據(jù)庫無關(guān),我們留作后用。
接下來在項目中創(chuàng)建modules目錄,然后在其下中創(chuàng)建db.js:
var settings = require("../settings");
var mongodb = require("mongodb");
var Db = mongodb.Db;
var Server = mongodb.Server;
module.exports = new Db(settings.db, new Server(settings.host, settings.port, {}));
以上代碼通過module.exports輸出了創(chuàng)建的數(shù)據(jù)庫連接。
會話支持
打開app.js添加以下內(nèi)容:
var session = require('express-session');
var settings = require('./settings');
var MongoStore = require('connect-mongo')(session);
app.use(session({
secret: settings.cookieSecret,
store: new MongoStore({
db: settings.db,
url: 'mongodb://localhost/blog'
})
}));
其中express.cookieParser()是Cookie解析的中間件。express.session()則提供會話支持,設(shè)置它的store參數(shù)為MongoStore實例,把會話信息存儲到數(shù)據(jù)庫中,以避免丟失。
這里需要注意的是在Express4.X版本中,session已經(jīng)分離出來,所以這里需要去手動下載:
npm install express-session
還需要注意的是store中的url是需要填上去的!這些地方由于書中所用版本較低的原因,這些都是“坑”,就是改這些東西,和查資料折騰了一下午!
注冊功能的實現(xiàn)
1 注冊界面
設(shè)計注冊界面在views中添加registered.html:
{% extends 'layout.html' %}
{% block title %}用戶注冊{% endblock %}
{% block content %}
<div class="container" style="margin-top: 30px;width: 500px;">
{% if success %}
<div class="alert alert-success alert-dismissable">
<button type="button" class="close" data-dismiss="alert" aria-hidden="true">×</button>
<h4>
{{success}}
</h4>
</div>
{% endif %}
{% if error %}
<div class="alert alert-danger alert-dismissable">
<button type="button" class="close" data-dismiss="alert" aria-hidden="true">×</button>
<h4>
{{error[0]}}
</h4>
</div>
{% endif %}
<div class="row clearfix">
<div class="col-md-12 column">
<form class="form-horizontal" role="form" method="post" action="/users/registered">
<div class="form-group">
<label for="inputText3" class="col-sm-2 control-label">用戶名</label>
<div class="col-sm-10">
<input type="text" class="form-control" id="inputText3" name="username"/>
</div>
</div>
<div class="form-group">
<label for="inputPassword3" class="col-sm-2 control-label">密碼</label>
<div class="col-sm-10">
<input type="password" class="form-control" id="inputPassword3" name="password" />
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<div class="checkbox">
<label><input type="checkbox" />記住我</label>
</div>
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<button type="submit" class="btn btn-default">注冊</button>
</div>
</div>
</form>
</div>
</div>
</div>
{% endblock %}
界面完成之后,添加路由,在users.js下添加以下內(nèi)容:
router.get('/registered', function(req, res){
res.render('registered');
});
瀏覽器打開http://127.0.0.1:3000/users/registered,這個頁面剛開始進去是這樣子的:

當注冊失敗是這樣子的:

成功我就不貼了。接下來繼續(xù):
2 注冊響應(yīng)
在user.js中添加registered的post請求:
var User = require('../modules/user')
router.post('/registered', function(req, res){
if (!(req.body['username'])){
req.flash("error", '用戶名不能為空!');
return res.redirect("/users/registered");
}
var user = new User({
name: req.body['username'],
password: req.body['password'],
});
// 檢查用戶是否存在
User.get(user.name, function(err, has_user){
if(has_user){
err = { "errmsg": "用戶已存在"};
}
if(err){
req.flash("error", err.errmsg);
return res.redirect("/users/registered");
}
// 保存新用戶
user.save(function(err){
if(err){
req.flash("error", err.errmsg);
return res.redirect("/users/registered");
}
req.flash("success", "注冊成功!");
res.redirect("/users/login");
});
});
});
在這段代碼中:
- req.body就是POST請求信息解析過后的對象。
- req.flash是Express提供的一個奇妙的工具,通過它保存的變量只會在用戶當前和下一次的請求中被訪問,之后會被清除。當然這里使用的時候需要就行一些設(shè)置,待會再說。
- res.redirect是重定向功能。
- User對象,是接下來要創(chuàng)建的用戶模型。實現(xiàn)了用戶的判斷和保存等。
3 創(chuàng)建用戶模型
User是一個描述數(shù)據(jù)的對象,即MVC架構(gòu)中的模型。在modules目錄下創(chuàng)建user.js,編寫內(nèi)容如下:
var mongodb = require('./db');
function User(user){
this.name = user.name;
this.password = user.password;
}
module.exports = User;
User.prototype.save = function save(callback){
var user = {
name: this.name,
password: this.password
};
mongodb.open(function(err, db){
if (err){
return callback(err);
}
db.collection('users', function(err, collection){
if(err){
mongodb.close();
return callback(err);
}
// 將name屬性添加為索引
collection.ensureIndex('name', {unique: true});
// 寫入新用戶到文檔
collection.insert(user, function(err, user){
mongodb.close();
callback(err, user);
});
});
});
};
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){
// 對數(shù)據(jù)庫操作完成之后,及時關(guān)閉數(shù)據(jù)庫
mongodb.close();
// 如果同名的用戶存在,那么就直接返回這個用戶信息
if(doc){
return callback(err, doc);
}else{
return callback(err, null);
}
});
});
});
};
以上代碼實現(xiàn)了兩個接口,User.prototype.save和User.get,前者是對象實例的方法,用于將用戶對象的數(shù)據(jù)保存到數(shù)據(jù)庫中,后者是對象構(gòu)造函數(shù)的方法,用于從數(shù)據(jù)庫中查找指定的用戶。
注意:以上兩個接口的類型是不一樣的,所以在前面users.js中,是使用User.get(...)和var user = new User(..);user.save(...);。
4 視圖交互
在視圖中訪問會話中的用戶數(shù)據(jù),同時為了顯示錯誤和成功的信息,也要增加響應(yīng)的函數(shù)。在app.js中添加以下內(nèi)容:
var flash = require('connect-flash');
app.use(flash());
app.use(function(req,res,next){
res.locals.user=req.session.user;
var err = req.flash('error');
var success = req.flash('success');
res.locals.error = err.length ? err : null;
res.locals.success = success.length ? success : null;
next();
});
'connect-flash也是需要通過npm install connect-flash進行安裝的。這里還需要注意的是以上在app.js中添加的代碼在文件中的順序很重要。這點也是一個大坑:
數(shù)據(jù)庫連接在相應(yīng)函數(shù)前面,響應(yīng)函數(shù)在路由前面,這樣才能在代碼中正確調(diào)用flash()方法。
5 小段總結(jié)
到這里注冊的功能差不多就完成了,假設(shè)沒有出現(xiàn)意外的話。祝你我都好運,以上代碼折騰了一天,但是基本上讓自己了解了Express和MongoDB的相互配合使用,也對整個系統(tǒng)的數(shù)據(jù)流了解的更加透徹。
接下來就是實現(xiàn)登錄和首頁的問題了,到這里就不貼代碼了,會將現(xiàn)在實現(xiàn)的這些代碼放在github上,做一個tab,感興趣的同學可以去看看。