使用Node.js+Express+MongoDB+AngularJS构建应用程序(一)——使用Node.js和Express构建RESTful API

本系列文章将分四部分详细介绍使用Node.js、Express、MongoDB和AngularJS来构建应用程序,内容涵盖以下几部分:

第一部分:使用Node.js和Express构建RESTful Api

第二部分:MongoDB数据库基本使用

第三部分:使用Node.js+Express+MongoDB整合构建RESTful Api

第四部分:使用AngularJS调用Node.js+Express+MongoDB RESTful接口实例


本文是《使用Node.js + Express + MongoDB + AngularJS构建应用程序》系列文章之一:《使用Node.js + Express构建RESTful API》,本文将介绍使用Node.js和Express来构建RESTful API。

【预备知识及资料】

■ Node.js (https://nodejs.org/en/)

■ express (http://expressjs.com/)

■ nodemon(非必须, http://nodemon.io/)

一、程序构架

1、新建项目文件夹nodexpress

2、在命令行中进入到项目文件夹nodexpress中,执行

npm init

输入项目的信息,直至完成。

3、安装express

在命令行中进入到项目文件夹nodexpress中,执行

npm install --save express

4、安装nodemon

nodemon不是必须的,但使用nodemon可以更让程序在修改过程中,不用频繁重启伺服,极大地方便开发。在命令行中进入到项目文件夹nodexpress中,执行

npm install -g nodemon

至此,项目文件夹nodexpress下应该有package.json文件和node_modules目录。package.json文件内容如下:

{
  "name": "nodexpress",
  "version": "1.0.0",
  "description": "node express rest api",
  "main": "app.js",
  "dependencies": {
    "express": "^4.14.0"
  },
  "devDependencies": {},
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [
    "node",
    "express",
    "rest",
    "api"
  ],
  "author": "ken",
  "license": "ISC"
}

 

二、使用Node.js和express创建伺服(服务器)

在项目文件夹nodexpress下新建app.js入口文件。打开app.js作以下操作:

1、引入express模块

var express = require('express');

2、初始化express

var app = express();

3、执行监听伺服

app.listen(3000, function(){
    console.log('伺器正在3000端口运行.');
});

这样我们就使用node.js和express创建了一个最简单的伺服。伺服地址为http://localhost:3000或http://127.0.0.1:3000

这时,app.js完整代码如下:

//引入express
var express = require('express');

//初始化express
var app = express();

//配置服务器监听
app.listen(3000, function(){
	console.log('伺器正在3000端口运行.')
});

4、开启伺服

在命令行中进入到项目文件夹nodexpress中,执行

node app.js

如果安装了nodemon,则可以使用

nodemon app.js

使用Node.js和Express构建RESTful Api

这时当我们在浏览器中访问http://localhost:3000或http://127.0.0.1:3000的时候,浏览器是空白的,不会报错,命令行中会提示“伺服正在3000端口运行”,这表示我们的伺服已经搭建成功。

 

三、为Node.js和express伺服配置路由

我们要使用Node.js和express创建RESTful API,光有伺服还不够,还需要定义路由,给外部程序调用。

在app.js文件中加入路由定义:

app.get('/', function(req, res){
     res.send("这是伺服响应的信息");
});

上面的路由定义直接输出一个字符串信息。上面注意到,在路由的回调函数中有req和res两个参数,分别代表请求和响应。req请求表示从客户端(即请求端)发送请求所携带的信息,res响应表示服务器(即伺服端)向客户端(请求端)发送响应的信息。在开发中,经常需要使用请求req信息来做逻辑判断。经常使用的请求req信息有(req.header,req.url,req.method,req.params,req.query等)

基本的路由已定义好,我们开始来使用路由。如果你没有安装nodemon,则需要重新打开伺服,这样,你在app.js中的修改才能生效。如果你安装了nodemon,并使用Nodemon来开启伺服,当你在保存app.js文件的时候,会发现命令行会有反应,nodemon重新启动了伺服,同时,当你的程序有问题时,会在命令行提醒错误的地方和原因。

现在重新在浏览器中访问http://localhost:3000或http://127.0.0.1:3000,会发现我们在路由定义中的响应字符串信息被返回被打印出来。这是我们用node.js和express建立的第一个接口。

使用Node.js和Express构建RESTful Api

 

四、分离路由

重新看回上面定义的一个最基本的路由:

app.get('/', function(req, res){
	res.send("这是伺服响应的信息");
});

这里其实是包括三个东西:首先,这个路由在入口文件app.js中定义,我们就可以直接使用这个路由;第二,这个路由定义“路径”——“/”;第三,这个路由包括了该路由下需要进行逻辑处理的控制器,即function部分。

在实际的开发中,一般不会将这三个部分的内容都放在入口文件app.js中,特别是复杂一点的程序,会造成混乱,不利管理和维护。因此,推荐的做法是做分离处理和嵌套引入。一般情况下,把路由定义和逻辑处理(即控制器)各自分离出来,这是分离处理。在入口文件app.js中引入路由定义,在路由定义中引入库逻辑处理(即控制器),这是嵌套引入。这样可以让程序更简单明了,易维护管理。

这部分先来看分离路由。分离路由操作如下:

1、在项目文件夹新建路由文件夹routes,用于专门存入路由定义文件

2、在上面的路由文件夹routes中新建一个路由定义文件userRouter.js,路由定义文件需要包括下面内容:

(1)引入express

var express = require('express');

(2)使用express定义一个新的路由userRouter

var userRouter = express.Router();

(3)给路由定义请求方法和逻辑控制器

userRouter.route('')
	.get(function(req, res){
		res.send("这是一个请求用户列表的接口,使用分离路由");
	});

(4)导出路由定义,以便在入口文件中引入使用

module.exports = userRouter;

最终routes/userRouter.js文件代码如下:

//引入express
var express = require('express');

//新建“用户”路由
var userRouter = express.Router();

//给路由定义请求方法和逻辑控制器
userRouter.route('')
	.get(function(req, res){
		res.send("这是一个请求用户列表的接口,使用分离路由");
	});

//导出路由定义
module.exports = userRouter;

3、在入口文件app.js中使用路由

(1)引入路由定义文件

var userRouter = require('./routes/userRouter');

(2)使用路由,定义路由路径,传入路由定义

app.use('/user', userRouter);

此时app.js代码如下:

//引入express
var express = require('express');

//分离路由后引入路由配置文件
var userRouter = require('./routes/userRouter');

//初始化express
var app = express();

//配置服务器监听
app.listen(3000, function(){
	console.log('伺器正在3000端口运行.')
});

//配置路由
//回调函数中,req表示请求,res表示响应
app.get('/', function(req, res){
	//请求req的一些常用信息
	console.log(req.headers);
	console.log(req.url);
	console.log(req.method);
	console.log(req.params);
	console.log(req.query);
	//发送响应信息
	res.send("这是伺服响应的信息");
});

//使用路由
app.use('/user', userRouter);

运行效果:

使用Node.js和Express构建RESTful Api

注:使用node.js和express可以开发RESTful标准的API,所以在路由定义文件中,可以在一个路由中实现多个路由方法的定义,如:

userRouter.route('')
	.get(function(req, res){
		res.send("这是一个请求用户列表的接口,使用分离路由");
	})
	.post(function(req, res){
		//逻辑处理
	})
	.put(function(req, res){
		//逻辑处理
	})
	.delete(function(req, res){
		//逻辑处理
	});

 

五、分离路由逻辑处理控制器

这部分讲如何将路由定义中的程序逻辑处理部分从路由定义文件中分离开来。

1、首先,在项目文件夹下新建controllers文件夹,专门用于存放路由逻辑处理文件

2、在controllers文件下新建用户路由的逻辑处理文件userController.js,用路由逻辑处理其实就是函数,由于我们定义的userController.js这个文件是专门用来处理用户路由的,用户路由不可能只是一个,所以这个文件应该是一个函数集。

(1)在此文件中定义用户处理函数

var userController = {
	handleGetUserList: function(req, res){
		res.send("这是一个请求用户列表的接口,使用分离路由和分离逻辑处理");
	},
	handleAddUser: function(req, res){
		res.send("这是一个添加用户的接口,使用分离路由和分离逻辑处理");
	},
	handleUpdateUser: function(req, res){
		res.send("这是一个修改用户信息的接口,使用分离路由和分离逻辑处理");
	},
	handleDelUser: function(req, res){
		res.send("这是一个删除用户的接口,使用分离路由和分离逻辑处理");
	}
};

这里定义了处理用户增删改查的四个路由处理函数。

(2)导出逻辑处理函数定义,以便在路由定义文件中引入使用

module.exports = userController;

3、在路由定义文件routes/userRouter.js中引入路由逻辑处理控制器文件改写路由定义

(1)引入

var userController = require('./../controllers/userController');

(2)改写路由定义

userRouter.route('')
	.get(userController.handleGetUserList)
	.post(userController.handleAddUser)
	.put(userController.handleUpdateUser)
	.delete(userController.handleDelUser);

此时app.js文件并没有改动,只是路由定义文件routes/userRouter.js和路由逻辑处理控制器文件controllers/userController.js改变。文件代码如下:

//引入express
var express = require('express');

//分离路由处理控制器后引入路由控制器
var userController = require('./../controllers/userController');

//新建“用户”路由
var userRouter = express.Router();

//给路由定义请求方法和逻辑控制器
userRouter.route('')
	.get(userController.handleGetUserList)
	.post(userController.handleAddUser)
	.put(userController.handleUpdateUser)
	.delete(userController.handleDelUser);

//导出路由定义
module.exports = userRouter;
var userController = {
	handleGetUserList: function(req, res){
		res.send("这是一个请求用户列表的接口,使用分离路由和分离逻辑处理");
	},
	handleAddUser: function(req, res){
		res.send("这是一个添加用户的接口,使用分离路由和分离逻辑处理");
	},
	handleUpdateUser: function(req, res){
		res.send("这是一个修改用户信息的接口,使用分离路由和分离逻辑处理");
	},
	handleDelUser: function(req, res){
		res.send("这是一个删除用户的接口,使用分离路由和分离逻辑处理");
	}
};

module.exports = userController;

运行效果:

使用Node.js和Express构建RESTful Api

使用Node.js和Express构建RESTful Api

在浏览器中打开路由地址即可获得GET方法的效果,其他方法可使用postman等工具打开。

 

六、完善接口配置

上面定义的user用户接口,虽然定义了GET、POST、PUT和DELETE四种请求方法,但是每个方法的请求都只是返回一个字符串。而实际上,调用POST、PUT和DELETE方法时必须要传参,调用GET方法有时也需要传参。

传参的方式有两种,一种是通过请求链接添加参数,可以使用路由处理控制器回调函数参数req.params来获取,一般用于GET和DELETE方法;另一种是通过请求时添加body实体,使用req.body来获取。

下面就来进行完善,并提供一个基本的CRUD操作的例子。为了更好地演示程序运行效果,在userController.js中模拟一些初始数据。

var users = [
	{id: 1, name: "ken1", age: 20, gender: '男', job: 'computer'},
	{id: 2, name: "ken2", age: 25, gender: '男', job: 'HR'},
	{id: 3, name: "ken3", age: 23, gender: '女', job: 'sales'},
	{id: 4, name: "ken4", age: 28, gender: '女', job: 'computer'},
	{id: 5, name: "ken5", age: 25, gender: '男', job: 'programmer'},
	{id: 6, name: "ken6", age: 24, gender: '女', job: 'teacher'},
	{id: 7, name: "ken7", age: 29, gender: '男', job: 'computer'},
	{id: 8, name: "ken8", age: 23, gender: '女', job: 'student'},
	{id: 9, name: "ken9", age: 22, gender: '男', job: 'sport'},
	{id: 10, name: "ken10", age: 21, gender: '男', job: 'computer'}
];

同时,设定一个统一返回响应的数据格式为:

{
status: true/false        表示请求是否成功的状态
data: []                表示请求成功时返回的数据主体
info: ‘’                 表示请求的提示信息
}

由于每个接口都使用这个数据格式,因此,在userController.js中定义一个返回数据格式的函数:

var output = function(status, data, info){
	return JSON.stringify({status: status, data: data, info: info})
}

这时,userController.js的代码如下:

var users = [
	{id: 1, name: "ken1", age: 20, gender: '男', job: 'computer'},
	{id: 2, name: "ken2", age: 25, gender: '男', job: 'HR'},
	{id: 3, name: "ken3", age: 23, gender: '女', job: 'sales'},
	{id: 4, name: "ken4", age: 28, gender: '女', job: 'computer'},
	{id: 5, name: "ken5", age: 25, gender: '男', job: 'programmer'},
	{id: 6, name: "ken6", age: 24, gender: '女', job: 'teacher'},
	{id: 7, name: "ken7", age: 29, gender: '男', job: 'computer'},
	{id: 8, name: "ken8", age: 23, gender: '女', job: 'student'},
	{id: 9, name: "ken9", age: 22, gender: '男', job: 'sport'},
	{id: 10, name: "ken10", age: 21, gender: '男', job: 'computer'}
];

var output = function(status, data, info){
	return JSON.stringify({status: status, data: data, info: info})
}

var userController = {
	handleGetUserList: function(req, res){
		res.send("这是一个请求用户列表的接口,使用分离路由和分离逻辑处理");

	},
	handleAddUser: function(req, res){
		res.send("这是一个添加用户的接口,使用分离路由和分离逻辑处理");
	},
	handleUpdateUser: function(req, res){
		res.send("这是一个修改用户信息的接口,使用分离路由和分离逻辑处理");
	},
	handleDelUser: function(req, res){
		res.send("这是一个删除用户的接口,使用分离路由和分离逻辑处理");
	}
};

module.exports = userController;

 

好了,现在就开始来改写各个路由。

 

1、返回所有用户的数据

请求路径 请求方法 参数 备注
/user GET

重写路由逻辑控制器(controllers/userController.js)中处理返回用户列表的函数(handleGetUserList):

handleGetUserList: function(req, res){
	res.send(output(true, users, '请求成功'));
}

运行效果:

使用Node.js和Express构建RESTful Api

2、返回单个用户的数据(新建)

请求路径 请求方法 参数 备注
/user/{id} GET {id} id为用户id

(1)在controllers/userController.js中添加处理返回单个用户数据的函数(handleGetUser):

handleGetUser: function(req, res){
	var result;
	console.log(req.params);
	if(req.params && req.params.id){
		for(var i=0; i<users.length; i++){
			if(users[i].id == parseInt(req.params.id)){
				result = users[i];
			}
		}
	}
	if(result){
		res.send(output(true, result, '请求成功'));
	}else{
		res.send(output(false, null, '没有找到指定用户'));
	}
}

(2)接着需要在路由定义文件routes/userRouter.js中定义返回单个用户信息的路由,加入:

userRouter.route('/:id')
	.get(userController.handleGetUser);

上面的代码看到一个req.params,这是客户端请求发送过来的参数,是一个对象。对象属性根据用户的路由定义为准。如我们在路由定义中定义传递参数名为”:id”,则我们在后端可以通过req.params.id来获取这个参数值。

运行效果:

使用Node.js和Express构建RESTful Api

3、添加一条用户数据

请求路径 请求方法 参数 备注
/user POST {

id: {id},

name: {name},

age: {age},

gender: {gender},

job: {job}

}

添加一条数据一般需要用到POST请求,而且请求时携带的数据不带在路由地址中,而是通过请求body实体携带,在后端使用req.body获取携带数据。

重写路由逻辑控制器(controllers/userController.js)中增加用户数据的函数(handleAddUser),用于添加一条数据:

handleAddUser: function(req, res){
	console.log(req.body);
	if(req.method === 'POST'){
		if(req.body.name && req.body.age && req.body.gender && req.body.job){
			users.push(req.body);
			res.send(output(true, users, '请求成功'));
		}else{
			res.send(output(false, null, '信息不完整'));
		}
	}else{
		res.send(output(false, null, '请求错误'));
	}
}

这时在postman中请求接口会发现以下错误提示,同时伺服窗口也出现错误提示。这是因为我们传过去的数据无法被正确解析。

使用Node.js和Express构建RESTful Api

为了解决这个问题,我们需要增加一个工具body-parser,用于请求body实体解析转化。步骤如下:

(1)在命令行进入项目文件夹,执行

npm install body-parser --save

(2)在app.js中引入body-parser

var bodyParser = require('body-parser');

(3)使用并配置body-parser,在app.js中加入

app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));

完成以上操作后,我们重新请求添加用户接口,这时请求成功了,返回数据在原来总数据上增加了一条我们指定的数据。

使用Node.js和Express构建RESTful Api

4、修改一条用户数据

请求路径 请求方法 参数 备注
/user PUT {

id: {id},

name: {name},

age: {age},

gender: {gender},

job: {job}

}

重写路由逻辑控制器(controllers/userController.js)中增加用户数据的函数(handleUpdateUser),用于修改一条数据:

handleUpdateUser: function(req, res){
	if(req.method === 'PUT'){
		var index;
		if(req.body.id && req.body.name && req.body.age && req.body.gender && req.body.job){
			for(var i=0; i<users.length; i++){
				if(users[i].id == parseInt(req.body.id)){
					index = i;
					users[i].name = req.body.name;
					users[i].age = req.body.age;
					users[i].gender = req.body.gender;
					users[i].job = req.body.job;
				}
			}

			if(index){
				res.send(output(true, users[index], '请求成功'));
			}else{
				res.send(output(false, null, '不存在数据'));
			}
		}else{
			res.send(output(false, null, '信息不完整'));
		}
	}else{
		res.send(output(false, null, '请求错误'));
	}
}

运行效果:

使用Node.js和Express构建RESTful Api

4、删除一条用户数据

请求路径 请求方法 参数 备注
/user DELETE id: {id}

重写路由逻辑控制器(controllers/userController.js)中增加用户数据的函数(handleDelUser),用于删除一条数据:

handleDelUser: function(req, res){
	if(req.method === 'DELETE'){
		if(req.body.id){
			var count = users.length;
			for(var i=0; i<users.length; i++){
				if(users[i].id == parseInt(req.body.id)){
					console.log(i);
					users.splice(i, 1);		
				}
			}
			if(count == users.length){
				res.send(output(true, null, '删除失败'));
			}else{
				res.send(output(true, users, '删除成功'));
			}
		}else{
			res.send(output(false, null, '请求错误'));
		}
	}
}

运行效果:

使用Node.js和Express构建RESTful Api

至此,已经完成了使用Node.js和Express来构建CRUD操作的RESTful接口。

目前各个文件代码如下:

{
  "name": "nodexpress",
  "version": "1.0.0",
  "description": "node express rest api",
  "main": "app.js",
  "dependencies": {
    "body-parser": "^1.15.2",
    "express": "^4.14.0"
  },
  "devDependencies": {},
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [
    "node",
    "express",
    "rest",
    "api"
  ],
  "author": "ken",
  "license": "ISC"
}
//引入express
var express = require('express');

//分离路由后引入路由配置文件
var userRouter = require('./routes/userRouter');

var bodyParser = require('body-parser');

//初始化express
var app = express();

//配置服务器监听
app.listen(3000, function(){
	console.log('伺器正在3000端口运行.')
});

app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));

//配置路由
//回调函数中,req表示请求,res表示响应
app.get('/', function(req, res){
	//请求req的一些常用信息
	console.log(req.headers);
	console.log(req.url);
	console.log(req.method);
	console.log(req.params);
	console.log(req.query);
	//发送响应信息
	res.send("这是伺服响应的信息");
});

//使用路由
app.use('/user', userRouter);
//引入express
var express = require('express');

//分离路由处理控制器后引入路由控制器
var userController = require('./../controllers/userController');

//新建“用户”路由
var userRouter = express.Router();

//给路由定义请求方法和逻辑控制器
userRouter.route('')
	.get(userController.handleGetUserList)
	.post(userController.handleAddUser)
	.put(userController.handleUpdateUser)
	.delete(userController.handleDelUser);

userRouter.route('/:id')
	.get(userController.handleGetUser);

//导出路由定义
module.exports = userRouter;
var users = [
	{id: 1, name: "ken1", age: 20, gender: '男', job: 'computer'},
	{id: 2, name: "ken2", age: 25, gender: '男', job: 'HR'},
	{id: 3, name: "ken3", age: 23, gender: '女', job: 'sales'},
	{id: 4, name: "ken4", age: 28, gender: '女', job: 'computer'},
	{id: 5, name: "ken5", age: 25, gender: '男', job: 'programmer'},
	{id: 6, name: "ken6", age: 24, gender: '女', job: 'teacher'},
	{id: 7, name: "ken7", age: 29, gender: '男', job: 'computer'},
	{id: 8, name: "ken8", age: 23, gender: '女', job: 'student'},
	{id: 9, name: "ken9", age: 22, gender: '男', job: 'sport'},
	{id: 10, name: "ken10", age: 21, gender: '男', job: 'computer'}
];

var output = function(status, data, info){
	return JSON.stringify({status: status, data: data, info: info})
}

var userController = {
	handleGetUserList: function(req, res){
		res.send(output(true, users, '请求成功'));
	},
	handleGetUser: function(req, res){
		var result;
		console.log(req.params);
		if(req.params && req.params.id){
			for(var i=0; i<users.length; i++){
				if(users[i].id == parseInt(req.params.id)){
					result = users[i];
				}
			}
		}
		if(result){
			res.send(output(true, result, '请求成功'));
		}else{
			res.send(output(false, null, '没有找到指定用户'));
		}
	},
	handleAddUser: function(req, res){
		console.log(req.body);
		if(req.method === 'POST'){
			if(req.body.name && req.body.id && req.body.age && req.body.gender && req.body.job){
				users.push(req.body);
				res.send(output(true, users, '请求成功'));
			}else{
				res.send(output(false, null, '信息不完整'));
			}
		}else{
			res.send(output(false, null, '请求错误'));
		}
	},
	handleUpdateUser: function(req, res){
		if(req.method === 'PUT'){
			var index;
			if(req.body.id && req.body.name && req.body.age && req.body.gender && req.body.job){
				for(var i=0; i<users.length; i++){
					if(users[i].id == parseInt(req.body.id)){
						index = i;
						users[i].name = req.body.name;
						users[i].age = req.body.age;
						users[i].gender = req.body.gender;
						users[i].job = req.body.job;
					}
				}

				if(index){
					res.send(output(true, users, '请求成功'));
				}else{
					res.send(output(false, null, '不存在数据'));
				}
			}else{
				res.send(output(false, null, '信息不完整'));
			}
		}else{
			res.send(output(false, null, '请求错误'));
		}
	},
	handleDelUser: function(req, res){
		if(req.method === 'DELETE'){
			if(req.body.id){
				var count = users.length;
				for(var i=0; i<users.length; i++){
					if(users[i].id == parseInt(req.body.id)){
						console.log(i);
						users.splice(i, 1);		
					}
				}
				if(count == users.length){
					res.send(output(true, null, '删除失败'));
				}else{
					res.send(output(true, users, '删除成功'));
				}
			}else{
				res.send(output(false, null, '请求错误'));
			}
		}
	}
};

module.exports = userController;

本文完

 

那时那我

随遇,随缘,随安,随喜!

发表评论

电子邮件地址不会被公开。 必填项已用*标注

This site uses Akismet to reduce spam. Learn how your comment data is processed.