express+monogo實(shí)現(xiàn)ToDo Restful Api

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)。

Screen Shot 2017-08-05 at 23.24.20.png

簡(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)密碼信息。

Screen Shot 2017-08-06 at 21.01.34.png

這里使用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);
    }
  });

});
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 134,923評(píng)論 18 139
  • Spring Boot 參考指南 介紹 轉(zhuǎn)載自:https://www.gitbook.com/book/qbgb...
    毛宇鵬閱讀 46,951評(píng)論 6 342
  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 173,264評(píng)論 25 708
  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫(kù)、插件、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 12,229評(píng)論 4 61
  • 很多時(shí)候,我們想的,并不是時(shí)間去哪了,而是心去哪了。 一年365天,一天24小時(shí),一小時(shí)60分鐘,一分鐘60秒,這...
    唐思堯閱讀 406評(píng)論 0 0