Begin
REST即表述性狀態(tài)傳遞(英文:Representational State Transfer,簡(jiǎn)稱REST)是Roy Fielding博士在2000年他的博士論文中提出來(lái)的一種軟件架構(gòu)風(fēng)格。
表述性狀態(tài)轉(zhuǎn)移是一組架構(gòu)約束條件和原則。滿足這些約束條件和原則的應(yīng)用程序或設(shè)計(jì)就是RESTful。需要注意的是,REST是設(shè)計(jì)風(fēng)格而不是標(biāo)準(zhǔn)。REST通常基于使用HTTP,URI,和XML(標(biāo)準(zhǔn)通用標(biāo)記語(yǔ)言下的一個(gè)子集)以及HTML(標(biāo)準(zhǔn)通用標(biāo)記語(yǔ)言下的一個(gè)應(yīng)用)這些現(xiàn)有的廣泛流行的協(xié)議和標(biāo)準(zhǔn)。REST 通常使用 JSON 數(shù)據(jù)格式。
本文主要闡述使用node.js的express 框架實(shí)現(xiàn)一個(gè)ToDo List(任務(wù)列表)的Restful 接口。基本的功能包括用戶的登錄注冊(cè)、添加任務(wù)、完成任務(wù)、查看任務(wù)等。 最終所有的Api可以在Postman中進(jìn)行測(cè)試。
完整的項(xiàng)目地址: https://github.com/superzhan/ToDo
開發(fā)環(huán)境
mac, webstom, monogoDB , node.js 6.10.1 ,express 4.01
安裝、創(chuàng)建express項(xiàng)目還挺簡(jiǎn)單的,官方還提供一個(gè)項(xiàng)目生成器。
npm install express-generator -g
執(zhí)行命令就可以全局安裝一個(gè)express的項(xiàng)目生成器。新建項(xiàng)目的時(shí)候執(zhí)行命令
express todo -e
新建一個(gè)名稱為 todo 的express 項(xiàng)目,使用ejs 模板。
執(zhí)行 npm install
,安裝相應(yīng)的模塊。
執(zhí)行 npm start
, 運(yùn)行工程。可以通過(guò) http://localhost:3000 訪問(wèn)。
基本的Restful 接口示例
新建的工程中會(huì)有一些基本的代碼。express 中實(shí)現(xiàn)Restful 接口是一件相當(dāng)簡(jiǎn)單的事情。
添加一個(gè)簡(jiǎn)單的api接口 testAPI. 在 routes/index.js 添加代碼
router.get('/testApi',function(req, res, next) {
res.json({result:'hi ,this is api result'});
});
重新啟動(dòng)工程后,可以在瀏覽器通過(guò)http://localhost:3000/testApi 訪問(wèn)這個(gè)接口。這個(gè)接口使用了GET方法。
在添加一個(gè)Post 方法的接口。
router.post('/testApi',function(req, res, next) {
res.json({result:'hi ,this is api result'});
});
這個(gè)接口可以再Postman中,通過(guò)POST 方法來(lái)訪問(wèn)。
簡(jiǎn)單添加和獲取任務(wù)
添加一個(gè)addItem 的接口,把客戶端發(fā)送過(guò)來(lái)的數(shù)據(jù)處理之后,插入到數(shù)據(jù)庫(kù)中。
router.post('/addItem',function(req,res,next){
var item = {};
item.note =req.body.note;
item.completed=false;
item.updated_at =timeTool.getCurDate();
todoSchema.create(item,function(err,post){
if(err)
{
next(err);
}else
{
res.redirect('/');
}
});
});
添加一個(gè)finishItem 接口,返回已經(jīng)完成的任務(wù)。接口的實(shí)現(xiàn)也是簡(jiǎn)單的數(shù)據(jù)庫(kù)查詢。
router.post('/finishItem',function(req,res,next){
var item = {};
item._id =req.body._id;
item.completed=true;
item.updated_at =timeTool.getCurDate();
todoSchema.findByIdAndUpdate(item._id,item,function(err,post){
if(err)
{
next(err);
}else
{
res.redirect('/');
}
});
});
數(shù)據(jù)庫(kù)mongoose的使用
這個(gè)工程使用monogoDb 作為數(shù)據(jù)庫(kù),相對(duì)地使用monogoose作為數(shù)據(jù)庫(kù)連接模塊。monogoose的使用也相當(dāng)簡(jiǎn)單。monogoose Api 文檔
數(shù)據(jù)庫(kù)連接
新建一個(gè)數(shù)據(jù)庫(kù)連接模塊mongoDB.js ,管理數(shù)據(jù)庫(kù)連接。數(shù)據(jù)庫(kù)的配置文件是根目錄下的 mongoConfig.json, 在這個(gè)文件中配置相應(yīng)的數(shù)據(jù)庫(kù)地址和端口號(hào)。
具體的可以參考github上的項(xiàng)目https://github.com/superzhan/ToDo
var config = require('../../mongoConfig.json');
var connectStr = '';
if(config.isAuth)
{
connectStr='mongodb://'+config.username+':'+config.password+'@'+config.host$
}else
{
connectStr='mongodb://'+config.host+':'+config.port+'/'+config.database;
}
console.log(connectStr);
var mongoose = require('mongoose');
mongoose.Promise = global.Promise;
mongoose.connect(connectStr)
.then(function () {console.log('connection succesful')})
.catch(function (err) {console.error(err)});
module.exports = mongoose;
數(shù)據(jù)模型
這個(gè)工程用到的數(shù)據(jù)模型也比較簡(jiǎn)單,只有任務(wù)數(shù)據(jù)模型和用戶數(shù)據(jù)模型。通過(guò)monogoose 預(yù)先定義好這兩個(gè)數(shù)據(jù)模型。
任務(wù)數(shù)據(jù)模型
var mongoose = require('mongoose');
var timeTool = require('./timeTool');
var ToDoSchema = new mongoose.Schema({
completed: Boolean,
note: String,
userId : mongoose.Schema.Types.ObjectId, //表明任務(wù)所屬的用戶
updated_at: { type: Date, default: timeTool.getCurDate()},
});
module.exports = mongoose.model('Todo', ToDoSchema);
用戶數(shù)據(jù)模型
var mongoose = require('mongoose');
var timeTool = require('./timeTool');
var UserSchema = new mongoose.Schema({
name : String,
password :String,
updated_at: { type: Date, default: timeTool.getCurDate()}
});
module.exports = mongoose.model('User', UserSchema);
用戶注冊(cè)和登錄
注冊(cè)
用戶注冊(cè)的接口實(shí)現(xiàn)需要驗(yàn)證用戶的密碼是否相同,然后對(duì)密碼進(jìn)行哈希加密,把密文存放在數(shù)據(jù)中。
而且還需要查找是否有相同名稱的用戶,若有相同名稱的用戶,返回注冊(cè)失敗的結(jié)果。
router.post('/register',function (req, res, next) {
var name = req.body.name;
var password = req.body.password;
var comfirmPassword = req.body.comfirmPassword;
if( password != comfirmPassword)
{
res.json({code:500,msg:'password is not same'});
return;
}
UserSchema.findOne({'name':name} , function (err, data) {
if(err)
{
res.json({code:500,msg:'check error'});
return;
}
if(data != null)
{
console.log(data);
res.json({code:500,msg:'userName is exist'});
return;
}
var hash = crypto.createHash('sha1');
hash.update(password);
password = hash.digest('hex');
var userInfo={};
userInfo.name = name;
userInfo.password= password;
UserSchema.create( userInfo,function (err,resData) {
if(err)
{
res.json({code:500,msg:'data base error'});
}else
{
res.json({code:200,msg:'success'});
}
});
});
});
登錄
用戶登錄時(shí)需要把請(qǐng)求的明文密碼用哈希算法加密后,再進(jìn)行數(shù)據(jù)庫(kù)查詢。
router.post('/login',function (req, res, next) {
var name = req.body.name;
var password = req.body.password;
UserSchema.findOne({'name':name} , function (err, data) {
if(err)
{
res.json({code:500,msg:'data base error'});
return;
}
if(data == null)
{
res.json({code:500,msg:'user not exist'});
return;
}
var hash = crypto.createHash('sha1');
hash.update(password);
password = hash.digest('hex');
UserSchema.findOne({'name':name ,'password':password},function (err, data) {
if(err)
{
res.json({code:500,msg:'data base error'});
return;
}
if(data==null)
{
res.json({code:500,msg:'password not right'});
return;
}
UserSchema.findOneAndUpdate({name:name ,password:password},
{updated_at:timeTool.getCurDate()},
function (err, data) {
if(err)
{
res.json({code:500,msg:'data base error'});
return;
}
res.json({code:200,msg:'success',_id:data._id});
}
);
});
});
});
API驗(yàn)證 Basic Auth
這些API有一個(gè)安全問(wèn)題,API沒有安全驗(yàn)證,任何一臺(tái)設(shè)備都可以通過(guò)url進(jìn)行訪問(wèn)。這個(gè)時(shí)候就需要對(duì)API的訪問(wèn)者進(jìn)行身份認(rèn)證。
這里使用最基本的 http Basic Auth 認(rèn)證,所有的訪問(wèn)請(qǐng)求都需要攜帶用戶的帳號(hào)密碼信息。
這里使用passport 模塊,http://passportjs.org/ 。passport 是一個(gè)node.js的Http驗(yàn)證模塊,可以快速開發(fā)http驗(yàn)證功能。 這里使用簡(jiǎn)單的basic auth 驗(yàn)證。
/*導(dǎo)入包*/
var passport = require('passport');
var Strategy = require('passport-http').BasicStrategy;
/*設(shè)置驗(yàn)證函數(shù) 驗(yàn)證帳號(hào)密碼*/
passport.use(new Strategy(
function(username, password, cb) {
var hash = crypto.createHash('sha1');
hash.update(password);
var cryPassword = hash.digest('hex');
UserSchema.findOne({name:username,password:cryPassword}, function(err, user) {
if (err) { return cb(err); }
if (!user) { return cb(null, false); }
return cb(null, user);
});
}));
var authenti=passport.authenticate('basic', { session: false });
最后在路由上添加 authenti 進(jìn)行API驗(yàn)證。
router.post('/getItem', authenti,function(req, res, next) {});
完整的任務(wù)接口
這個(gè)工程總共有8個(gè)任務(wù)接口,包括查看任務(wù)、添加任務(wù)、更新任務(wù)和刪除任務(wù)。具體代碼可以參考github 代碼倉(cāng)庫(kù)。 https://github.com/superzhan/ToDo
router.post('/getItem', authenti,function(req, res, next) {
TodoSchema.findOne({"_id":req.body._id},function(err, data){
if(err){
next(err);
}else
{
res.json(data);
}
});
});
/*返回未完成的Item*/
router.post('/getUnDoItem', authenti,function(req, res, next) {
if(req.body.userId==null)
{
res.json({code:500,msg:"request error"});
return;
}
var userId =mongoose.Types.ObjectId(req.body.userId);
TodoSchema.find({"completed":false ,"userId":userId},function(err, data){
if(err){
next(err);
}else
{
res.json(data);
}
});
});