REST即表述性状态传递(英文:Representational State Transfer,简称REST)是Roy Fielding博士在2000年他的博士论文中提出来的一种软件架构风格。
表述性状态转移是一组架构约束条件和原则。满足这些约束条件和原则的应用程序或设计就是RESTful。需要注意的是,REST是设计风格而不是标准。REST通常基于使用HTTP,URI,和XML(标准通用标记语言下的一个子集)以及HTML(标准通用标记语言下的一个应用)这些现有的广泛流行的协议和标准。REST 通常使用 JSON 数据格式。
本文主要阐述使用node.js的express 框架实现一个ToDo List(任务列表)的Restful 接口。基本的功能包括用户的登录注册、添加任务、完成任务、查看任务等。 最终所有的Api可以在Postman中进行测试。
完整的项目地址: https://github.com/superzhan/ToDo
mac, webstom, monogoDB , node.js 6.10.1 ,express 4.01
安装、创建express项目还挺简单的,官方还提供一个项目生成器。
npm install express-generator -g
执行命令就可以全局安装一个express的项目生成器。新建项目的时候执行命令
express todo -e
新建一个名称为 todo 的express 项目,使用ejs 模板。
执行 npm install
,安装相应的模块。
执行 npm start
, 运行工程。可以通过 http://localhost:3000 访问。
新建的工程中会有一些基本的代码。express 中实现Restful 接口是一件相当简单的事情。
添加一个简单的api接口 testAPI. 在 routes/index.js 添加代码
router.get('/testApi',function(req, res, next) {
res.json({result:'hi ,this is api result'});
});
重新启动工程后,可以在浏览器通过http://localhost:3000/testApi 访问这个接口。这个接口使用了GET方法。
在添加一个Post 方法的接口。
router.post('/testApi',function(req, res, next) {
res.json({result:'hi ,this is api result'});
});
这个接口可以再Postman中,通过POST 方法来访问。
添加一个addItem 的接口,把客户端发送过来的数据处理之后,插入到数据库中。
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('/');
}
});
});
添加一个finishItem 接口,返回已经完成的任务。接口的实现也是简单的数据库查询。
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('/');
}
});
});
这个工程使用monogoDb 作为数据库,相对地使用monogoose作为数据库连接模块。monogoose的使用也相当简单。monogoose Api 文档
数据库连接
新建一个数据库连接模块mongoDB.js ,管理数据库连接。数据库的配置文件是根目录下的 mongoConfig.json, 在这个文件中配置相应的数据库地址和端口号。
具体的可以参考github上的项目
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;
数据模型
这个工程用到的数据模型也比较简单,只有任务数据模型和用户数据模型。通过monogoose 预先定义好这两个数据模型。
任务数据模型
var mongoose = require('mongoose');
var timeTool = require('./timeTool');
var ToDoSchema = new mongoose.Schema({
completed: Boolean,
note: String,
userId : mongoose.Schema.Types.ObjectId, //表明任务所属的用户
updated_at: { type: Date, default: timeTool.getCurDate()},
});
module.exports = mongoose.model('Todo', ToDoSchema);
用户数据模型
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);
注册
用户注册的接口实现需要验证用户的密码是否相同,然后对密码进行哈希加密,把密文存放在数据中。
而且还需要查找是否有相同名称的用户,若有相同名称的用户,返回注册失败的结果。
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'});
}
});
});
});
登录
用户登录时需要把请求的明文密码用哈希算法加密后,再进行数据库查询。
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有一个安全问题,API没有安全验证,任何一台设备都可以通过url进行访问。这个时候就需要对API的访问者进行身份认证。
这里使用最基本的 http Basic Auth 认证,所有的访问请求都需要携带用户的帐号密码信息。
这里使用passport 模块,http://passportjs.org/ 。passport 是一个node.js的Http验证模块,可以快速开发http验证功能。 这里使用简单的basic auth 验证。
/*导入包*/
var passport = require('passport');
var Strategy = require('passport-http').BasicStrategy;
/*设置验证函数 验证帐号密码*/
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 进行API验证。
router.post('/getItem', authenti,function(req, res, next) {});
这个工程总共有8个任务接口,包括查看任务、添加任务、更新任务和删除任务。具体代码可以参考github 代码仓库。 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);
}
});
});