在AngularJS+NodeJS+MongoDB+OAuth中搭建web Service

本文永久链接: https://www.askmaclean.com/archives/try-mean.html

 

随着JavaScript的深入学习,在此我总结了web service的制作方法。编写了简单的Todo列表。

AngularJS+NodeJS(ExpressJS)+MongoDB

 

虽然搭建web service有很多方法,但这次主要讲解下述几种方法。

因为从服务器到客户端中,都可以用javascript来写。基于MonogoDB,我们称为MEAN栈。

以下我们将其取名为meanstack-sample,主要按顺序讲授直到启动为止所需要做的事。

Yeoman的设定

 

在制作网络应用的时候,使用yeoman的话,就可以简单地完成制作雏形以及调试,所以我很推荐大家使用。Yeoman是由Yo(项目管理)+bower(依赖性管理)+grunt(实施搭建测试)来组成的,这些都是能辅助JavaScript开发的nodejs的library。

npm install -g yo grunt-cli bower generator-angular generator-karma

 

Yo

使用Yo的话,就能安装各种雏形。雏形是可以在npm中搜索,通过输入

 npm saerch yeoman-generator

 

就可以实现搜索。根据想搜索的结果,可以选择想使用的雏形比如输入

% yo webapp

 

或者

% yo angular

 

就可以生成各种雏形。

 

Bower

 

在Yo中制作雏形之后,根据需要追加JavaScript library时,通过使用bower就可以简单地对此进行管理。

安装:

bower install jquery --save

 

通过添加-save,就可以实现在安装的同时记录设定文件。目录中有bower.json的话,通过输入

 bower install

 

Grunt就可以将已经记录的library之后进行汇总来一起安装。

grunt server

 

Grunt是构建工具。用yo制作雏形之后,输入% grunt server的话调试用的web service就会启动,连浏览器都会自行启动来确认操作。启动中的源代码会马上就被反映出来,使用起来非常方便。

另外,还有minify,这是实施发行用的相关操作,可以测试

 grunt build

 

以及jasmin一起来执行测试。还有

grunt test

 

 

AngularJS的模板安装

 

让我们来试着安装AngularJS的模板吧。

输入

% mkdir angular
% cd angular

% yo angular

 

的话,就可以选择是否使用Bootstrap、Compass以及是否使用一些angular的module。全部安装之后,输入 grunt server 的话,就会出现以下样本画面

在有画面出现的情况下,更新文件的话就会马上被反映出来,所以开发起来就非常轻松。

 

用NodeJS(+ExpressJS)制作REST mock

 

首先,因为没有服务器端就无法运行,所以用Node.js准备能够返回REST近似值的项目。

安装Node.js的Web application framework。

详细内容请参考这里。

% npm install express

 

使用Node.js/Express作为web.js制成以下项目

var express = require('express'),

http = require('http'),

path = require('path'),

db = require('./db.js'),

application_root = __dirname;



var app = express();

app.configure(function() {

app.set('port', process.env.PORT || 3000);

app.set('view engine', 'ejs');



// 将AngularJS的目录作为静态目录追加

app.use(express.static(path.join(application_root, "app")));



app.use(express.cookieParser());

app.use(express.bodyParser());

app.use(express.methodOverride());

app.use(app.router);

});



app.get('/todo', db.read); // GET的处理

app.post('/todo', db.create); // POST的处理

app.delete('/todo/:id', db.delete); // DELETE的处理

app.put('/todo/:id', db.update); // PUT的处理



// 启动服务器

http.createServer(app).listen(app.get('port'), function(){

console.log('Express server listening on port ' + app.get('port'));

});

 

 

 

定下了URL,通过使用HTTP的GET、POST、DELETE、PUT,就可以实现CRUD。

通过在AngularJS端中使用HTTP method,就可以简单地对数据进行操作了。

 

MongoDB的安装以及与NodeJS的连接

 

 

因为已经制作了服务器端的结构,接下来将其与DB连接。

我试着用同样的JavaScript来连接MongoDB。

MongoDB的定义是(引用自维基百科)——开源的文件指向型数据库。用C++语言来实现的,由MongoDB Inc开发与支持。MongoDB不是RDBMS,被分类为所谓的NoSQL的数据库。

所以,不是使用SELECT语句等SQL,而是使用JavaScript的method来进行数据操作。

安装等准备可以在这里看见。

要从Node.js连接到MongoDB的话需要有各种各样的library。

试着去谷歌搜索下的话,会发现mongoose非常有名。

这次将使用更单纯的mongodb这个library。这几乎可以与在终端中使用MongoDB的情况相同地来使用。

% npm install mongodb

 

 

来安装,在web.js中用正在使用的db.js来制作连接部分。

var mongo = require('mongodb');

var Db = mongo.Db,

BSON = mongo.BSONPure;



var mongoUri = 'mongodb://localhost/contentdb';



// CRUD的C

exports.create = function(req, res) {

var content = JSON.parse(req.body.mydata);

Db.connect(mongoUri, function(err, db) {

db.collection('todolist', function(err, collection) {

collection.insert(content, {safe: true}, function(err, result) {

res.send();

db.close();

});

});

});

};



// CRUD的R

exports.read = function(req, res) {

Db.connect(mongoUri, function(err, db) {

db.collection('todolist', function(err, collection) {

collection.find({}).toArray(function(err, items) {

res.send(items);

db.close();

});

});

});

};



// CRUD的U

exports.update = function(req, res) {

var content = JSON.parse(req.body.mydata);

var updatedata = {};

updatedata.data = content.data;

updatedata.checked = content.checked;

var id = req.params.id;



Db.connect(mongoUri, function(err, db) {

db.collection('todolist', function(err, collection) {

collection.update({'_id':new BSON.ObjectID(id)}, updatedata, {upsert:true}, function(err, result) {

res.send();

db.close();

});

});

});

};



// CRUD的D

exports.delete = function(req, res) {

var id = req.params.id;

console.log(id);



Db.connect(mongoUri, function(err, db) { // add Db.connect

db.collection('todolist', function(err, collection) {

collection.remove({'_id':new BSON.ObjectID(id)}, {safe:true}, function(err, result) {

res.send();

db.close();

});

});

});

};

 

基本上是用这样的流程来处理:连接DB,选择collection,操作collection,关闭数据库。

我想也有每次不连接数据库从而保存collection的方法,但因为在heroku上的进展并不顺利,所以这次还是用这种形式。

AngularJS与REST的合作

 

制成就完成服务器端了,之后我们需要用Angular制作客户端。

使用yeoman作为yo angular之后的状态进行编辑。

修改app/views/main.html与app/scripts/controllers/main.js。

画面就变成了简单的TODO列表。(不知道为什么没有在HTML的中括号中表示出来,所以在此使用全角)。

 

<div class="hero-unit">

<input type="text" ng-model="newtodo"><input type="button" value="add todo" ng-click="createTodo()">

<ul>

<li ng-repeat="todo in todolist">

<input type="checkbox"  ng-change="updateTodo(todo)" ng-model="todo.checked">

<span class="done-{{todo.checked}}">{{todo.data}}</span>

<input type="button" value="×" ng-click="deleteTodo(todo)">

</li>

</ul>

</div>

'use strict';



angular.module('angularApp').controller('MainCtrl', function ($scope, $http) {//, $templateCache) {

var url = '/todo';



$scope.todolist = [];

$scope.getTodo = function() {

$http.get(url).success(function(data) {

$scope.todolist = data;

});

};



$scope.createTodo = function() {

var todo = {};

todo.checked = false;

todo.data = this.newtodo;



var senddata = 'mydata=' + JSON.stringify(todo);

$http({

method: 'POST',

url: url,

data: senddata,

headers: {'Content-Type': 'application/x-www-form-urlencoded'},

}).success(function(response) {

$scope.getTodo();

}).error(function(response) {

});

this.newtodo = "";

};



$scope.updateTodo = function(todo) {

var senddata = 'mydata=' + JSON.stringify(todo);

$http({

method: 'PUT',

url: url + '/' + todo._id,

data: senddata,

headers: {'Content-Type': 'application/x-www-form-urlencoded'},

}).success(function(response) {

$scope.getTodo();

}).error(function(response) {

});

};



$scope.deleteTodo = function(todo) {

var senddata = 'mydata=' + JSON.stringify(todo);

console.log(senddata);

$http({

method: 'DELETE',

url: url + '/' + todo._id,

data: senddata,

headers: {'Content-Type': 'application/x-www-form-urlencoded'},

}).success(function(response) {

$scope.getTodo();

}).error(function(response) {

});

};



$scope.getTodo();

});

 

 

 

至此就可以运行了,我将至此的Sauce放到了github之中。

NodeJS中的OAuth认证

 

至此,对已经制成的项目添加认证功能。

需要修正的地方主要有以下3点。

  • 使用Node.js的Passport library,追加认证功能(修正web.js)
  • 在数据库的项目中追加uid
  • 修正客户端的app.js,追加login.html

 

 

web.js修正

 

使用Node.js的Passport这个library的话,就可以简单地使其拥有各种各样的认证功能。这次参考了Passport的官方网站,追加了Twitter认证。

首先准备一个新的auth函数,确认是否通过认证,在通过认证之后可以变更为执行DB操作用。

另外consumerKey以及consumerSecret可以从Twitter的开发者页面取得。

/* passport的设定 */

var passport = require('passport'),

TwitterStrategy = require('passport-twitter').Strategy;



// Passport: Twitter 的OAuth设定

passport.use(new TwitterStrategy({

consumerKey: "your key",

consumerSecret: "your secret",

callbackURL: "/auth/twitter/callback"

}, function(token, tokenSecret, profile, done) {

// 设定用户ID

profile.uid = profile.provider + profile.id;

process.nextTick(function() {

return done(null, profile);

});

}));



// Serialized and deserialized methods when got from session

passport.serializeUser(function(user, done) {

done(null, user);

});



passport.deserializeUser(function(user, done) {

done(null, user);

});



// Define a middleware function to be used for every secured routes

// 没有通过认证的话就返回401

var auth = function(req, res, next){

if (!req.isAuthenticated()) {

res.send(401);

} else {

next();

}

};





var app = express();

app.configure(function() {

...



// OAuth认证用

app.use(express.session({ secret: 'hogehoge', cookie: {maxAge: 1000 * 60 * 60 * 24 * 30} }));

app.use(passport.initialize()); // Add passport initialization

app.use(passport.session());    // Add passport initialization



app.use(app.router);

});



// route to log in

app.get("/auth/twitter", passport.authenticate('twitter'));

app.get("/auth/twitter/callback", passport.authenticate('twitter', {

successRedirect: '/',

falureRedirect: '/login'

}));



app.get('/todo', auth, db.read); // GET的处理。要认证(在auth函数之后执行至此的处理)

...

 

 

db.js的修正

 

因为每个用户都有数据,所以在MongoDB的CRUD操作时,追加uid。

 

...



// CRUD的C

exports.create = function(req, res) {

var uid = req.user.uid;

var content = JSON.parse(req.body.mydata);

content.uid = uid;

Db.connect(mongoUri, function(err, db) {

db.collection('todolist', function(err, collection) {

collection.insert(content, {safe: true}, function(err, result) {

res.send();

db.close();

});

});

});

};



...

 

app.js的修正(与追加login.html)

 

通过与web.js的auth函数合作,可以实现搜索到在没有通过认证的情况下,被返回的401错误的功能。

...

// 不用[]来围住的话,grunt中,进行minify之后就会报错。

$httpProvider.responseInterceptors.push(['$q', '$location',function($q, $location) {

return function(promise) {

return promise.then(function(response) {

// Success: 成功时原样返回

return response;

}, function(response) {

// Error:错误是401的话/迁移到login中。

if (response.status === 401) {

$location.url('/login');

}

return $q.reject(response);

}

);

};

}]);

...

<div class="container">

<div class="page-header">

login

</div>



<div>

<p><button class="btn-auth btn-twitter" onclick="location.href='/auth/twitter'">

Sign in with <b>Twitter</b></button></p>

</div>

</div>

 

总结与参考

 

 

直到追加了认证为止,源代码都会被放在github中。虽然看起来这很古老,安全性方面也会有问题。

之后使用最开始输入的BootStrap,来改善对其的成见,试着提升heroku,添加Facebook中的认证等,这能应用于许多地方。

Comment

*

沪ICP备14014813号

沪公网安备 31010802001379号