一、Express框架简介
在前面Node基础中我们学习了 Node.js 中的 http 模块,虽然知道使用 Node.js 中的 http 模块是可以开发 Web 应用的,处理静态资源,处理动态资源,请求分发(路由)等等,也可以让开发者对 HTTP 协议的理解更加清晰,但是使用起来比较复杂,开发效率低。
npm 提供了大量的第三方模包,其中不乏许多 Web 框架,我们没有必要重复发明轮子,因而选择使用 Express 作为开发框架,因为它是目前最稳定、使用最广泛,而且 Node.js 官方推荐的唯一一个 Web 开发框架。除了为 http 模块提供了更高层的接口外,还实现了许多功能,其中包括:
- 静态文件服务;
- 路由控制;
- 模板解析支持;
- 动态视图;
- 用户会话;
- CSRF 保护;
- 错误控制器;
- 访问日志;
- 缓存;
- 插件支持。
官网:http://www.expressjs.com.cn/
express 是一个基于内置核心 http 模块
的,一个第三方的包
,专注于 web 服务器
的构建。
二、使用Express搭建服务器的Hello world程序
- 首先创建一个名为 myapp 的目录,在命令行输入并运行 yarn init -y (或者 npm init -y)。
- 使用 **yarn add express ** (或者 npm install express )安装 Express 包;
- 其次在 myapp 目录中,创建一个名为 app.js 的文件,并复制下面示例中的代码。
sudo npm init --yes
sudo npm install express --save
sudo npm install body-parser --save
测试代码
// 引入express
var express = require('express');
// 创建应用对象
var app = express();
// 创建路由规则
/**
* @request 封装请求对象
* @response 封装响应对象
*/
app.get('/', (request, response) => {
// 响应消息内容
response.send('桃李不言下自成蹊');
});
// 监听服务
app.listen(2021, () => {
// 监听log日志
console.log("服务已启动,{0.0.0.0:2020},请访问http://127.0.0.1:2021");
});
启动服务
node app.js
构建expres完整框架
sudo npx --yes --package express-generator express --force --no-view
sudo npm install
npm start
或者
node ./bin/www
案例初始化
初始化脚本
mkdir -p myapp/src
cd myapp
touch src/app.js
npm init --yes
npm i nodemon -D
npm i express -S
修改 package.json 文件
{
"name": "myapp",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"dev": "nodemon ./src/app.js",
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"nodemon": "^2.0.12"
},
"dependencies": {
"express": "^4.17.1"
}
}
初始化 app.js
// 引入express
const express = require('express');
const app = express();
/**
* 创建路由规则
* @req 封装请求对象
* @res 封装响应对象
*/
app.get('/slogan',(req,res)=>{
res.send('桃李不言下自成蹊');
});
// 监听服务
app.listen(6633,()=>{
// 监听log日志
console.log("服务已启动,{0.0.0.0:6633},请访问http://127.0.0.1:6633");
})
启动测试
npm run dev
三、使用Express对get请求方式的处理
3.1、返回页面
myapp 目录下新建src/public目录放入haha.html页面。
haha.html中:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>显示隐藏密码</title>
<style>
* {
margin: 0px;
padding: 0px;
}
body {
background: #DADBDC;
}
#box {
width: 640px;
height: 350px;
background: #FFFFFF;
margin: 0 auto;
margin-top: 167.576px;
border-radius: 15px;
text-align: center;
position: relative;
}
.input_container {
margin-top: 30px;
padding-left: 60px;
width: 290px;
height: 38px;
line-height: 38px;
font-size: 16px;
}
button,
input[type=submit]{
margin-top: 30px;
width: 350px;
height: 38px;
border-radius: 5px;
font-size: 18px;
}
</style>
<link rel="stylesheet" href="./lib/font-awesome-5.12.0/css/all.min.css">
</head>
<body>
<div id="box">
<h1>登录</h1>
<!-- <form action="login" method="get"> -->
<form action="login" method="post">
<i class="fa fa-user fa-2x" aria-hidden="true" style="position: absolute;top: 78px; left: 166px;"></i><input class="input_container" type="text" name="account" id="account" value="" placeholder="请输入账号" autocomplete="off">
<br>
<i class="fa fa-key fa-2x" aria-hidden="true" style="position: absolute;top: 150px; left: 166px;"></i><input class="input_container" type="password" name="password" id="password" value="" placeholder="请输入密码" autocomplete="off">
<i id="eye" class="fa fa-eye-slash fa-2x" aria-hidden="true" style="position: absolute;top: 150px; right: 160px;"></i>
<input type="submit" value="登录">
</form>
<button type="button" id="ajax_json">ajax json</button>
</div>
</body>
</html>
<script>
window.addEventListener('DOMContentLoaded', function(params) {
let eye = window.document.querySelector('#eye');
eye.addEventListener('click', function(params) {
let password = window.document.querySelector('#password');
let type = password.type;
if ('password' == type) {
password.type = 'text';
} else {
password.type = 'password';
}
// 获取该元素的类样式名称列表 DOMTokenList
let list = eye.classList;
// window.console.log(list);
for (const class_name of list) {
// window.console.log(class_name);
if ('password' == type) {
list.replace('fa-eye-slash', 'fa-eye');
} else {
list.replace('fa-eye', 'fa-eye-slash');
}
}
});
});
</script>
<script src="./lib/jquery-3.6.0.min.js"></script>
<script>
$(function (params) {
$('#ajax_json').click(function (e) {
e.preventDefault();
let data = {
account: $('#account').val(),
password: $('#password').val(),
}
$.ajax({
type: "post",
url: "login_json",
contentType:'application/json',
data: JSON.stringify(data),
dataType: "json",
success: function (response) {
window.alert('账号:' + response.account + ' 密码:' + response.password )
}
});
});
});
</script>
index.html引入css文件和js文件存储src/public/lib目录
在app.js 中编写路由:
// 引入fs和path模块
var fs = require("fs");
var path = require("path");
/**
* 创建haha.html路由规则
* @req 封装请求对象
* @res 封装响应对象
*/
app.get('/haha.html',(req,res)=>{
// 读取页面内容
const pathName = path.join(__dirname,'./public','haha.html');
const hahaPage = fs.readFileSync(pathName,'utf-8');
// 返回这个页面
res.send(hahaPage);
});
访问发现无法访问静态资源
3.2、使用Express获取静态资源
// 获取静态资源 "public"表示指定在本地public下找静态资源
app.use(express.static(path.join(__dirname, 'public')));
// 如果想要在请求的路径里面添加前缀
// app.use("/public",express.static(path.join(__dirname, 'public')));
myapp 目录下新建src/public/html目录,新建文件login.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>显示隐藏密码</title>
<style>
* {
margin: 0px;
padding: 0px;
}
body {
background: #DADBDC;
}
#box {
width: 640px;
height: 350px;
background: #FFFFFF;
margin: 0 auto;
margin-top: 167.576px;
border-radius: 15px;
text-align: center;
position: relative;
}
.input_container {
margin-top: 30px;
padding-left: 60px;
width: 290px;
height: 38px;
line-height: 38px;
font-size: 16px;
}
button,
input[type=submit]{
margin-top: 30px;
width: 350px;
height: 38px;
border-radius: 5px;
font-size: 18px;
}
</style>
<link rel="stylesheet" href="../lib/font-awesome-5.12.0/css/all.min.css">
</head>
<body>
<div id="box">
<h1>登录</h1>
<!-- <form action="login" method="get"> -->
<form action="login" method="post">
<i class="fa fa-user fa-2x" aria-hidden="true" style="position: absolute;top: 78px; left: 166px;"></i><input class="input_container" type="text" name="account" id="account" value="" placeholder="请输入账号" autocomplete="off">
<br>
<i class="fa fa-key fa-2x" aria-hidden="true" style="position: absolute;top: 150px; left: 166px;"></i><input class="input_container" type="password" name="password" id="password" value="" placeholder="请输入密码" autocomplete="off">
<i id="eye" class="fa fa-eye-slash fa-2x" aria-hidden="true" style="position: absolute;top: 150px; right: 160px;"></i>
<input type="submit" value="登录">
</form>
<button type="button" id="ajax_json">ajax json</button>
</div>
</body>
</html>
<script>
window.addEventListener('DOMContentLoaded', function(params) {
let eye = window.document.querySelector('#eye');
eye.addEventListener('click', function(params) {
let password = window.document.querySelector('#password');
let type = password.type;
if ('password' == type) {
password.type = 'text';
} else {
password.type = 'password';
}
// 获取该元素的类样式名称列表 DOMTokenList
let list = eye.classList;
// window.console.log(list);
for (const class_name of list) {
// window.console.log(class_name);
if ('password' == type) {
list.replace('fa-eye-slash', 'fa-eye');
} else {
list.replace('fa-eye', 'fa-eye-slash');
}
}
});
});
</script>
<script src="../lib/jquery-3.6.0.min.js"></script>
<script>
$(function (params) {
$('#ajax_json').click(function (e) {
e.preventDefault();
let data = {
account: $('#account').val(),
password: $('#password').val(),
}
$.ajax({
type: "post",
url: "login_json",
contentType:'application/json',
data: JSON.stringify(data),
dataType: "json",
success: function (response) {
window.alert('账号:' + response.account + ' 密码:' + response.password )
}
});
});
});
</script>
此时可以通过http://127.0.0.1:6633/html/login.html
html/login.html作为主页面编写路由
/**
* 创建主入口路由规则
* @req 封装请求对象
* @res 封装响应对象
*/
app.get('/',(req,res)=>{
// 读取页面内容
const pathName = path.join(__dirname,'public/html','login.html');
const loginPage = fs.readFileSync(pathName,'utf-8');
// 返回这个页面
res.send(loginPage);
});
此时可以通过http://127.0.0.1:6633访问
四、使用Express获取参数
4.1、get获取表单参数
/**
* 用户get方式表单登录
* @req 封装请求对象
* @res 封装响应对象
*/
app.get('/login',(req,res)=>{
// console.log(request.query);
let content = '';
content += '帐号--->' + req.query.account;
content += ' 密码--->' + req.query.password;
res.send(content);
});
4.2、post获取表单参数
使用第三方的包body-parser ,更加简便专业地处理请求参数
首先,项目目录下安装body-parser:
yarn add body-parser 或者 npm install body-parser
方法一:使用body-parser获取请求参数:(不推荐)
// 引入body-parser
const bodyParser = require('body-parser')
// parse application/x-www-form-urlencoded
app.use(bodyParser.urlencoded());
/**
* 用户post方式表单登录
* @request 封装请求对象
* @response 封装响应对象
*/
app.post('/login', (request, response) => {
// console.log(request.body);
let content = '';
content += '帐号--->' + request.body.account;
![20210826112708](img/20210826112708.png) content += ' 密码--->' + request.body.password;
// 响应消息内容
response.send(content);
});
方法二:设置解析body中间件(推荐)
express 无法直接获取Post请求的参数,需要设置body解析中间件
// parse application/x-www-form-urlencoded
app.use(express.urlencoded({ extended: false }));
/**
* 用户post方式表单登录
* @request 封装请求对象
* @response 封装响应对象
*/
app.post('/login', (request, response) => {
// console.log(request.body);
let content = '';
content += '帐号--->' + request.body.account;
content += ' 密码--->' + request.body.password;
// 响应消息内容
response.send(content);
});
4.3、解析json格式参数
// json解析
app.use(express.json());
/**
* 用户post方式ajax json登录
* @req 封装请求对象
* @res 封装响应对象
*/
app.post('/login_json', (req, res) => {
// console.log(request.body);
let data = req.body;
let content = '';
content += '帐号--->' + data.account;
content += ' 密码--->' + data.password;
console.log(content);
// 返回json对象
res.json(data);
});
4.4、解析地址栏参数
/**
* 地址栏参数
* 测试地址:http://127.0.0.1:6633/user/2/5
* @req 封装请求对象
* @res 封装响应对象
*/
app.get('/user/:pageNum/:pageSize', (req, res) => {
// console.log(request.params)
let data = {
pageNum: req.params.pageNum,
pageSize: req.params.pageSize,
}
// 返回对象需要配置app.use(express.json());
// res.send(data);
// 返回json格式字符串
// res.send(JSON.stringify(data));
res.json(data);
});
五、重定向到其他接口
一般注册成功之后可以跳转到登录页面,这就是重定向
我们使用 res.redirect(‘/login’); 来实现跳转到另外一个接口进行处理
res.redirect('地址');
六、all() 方法合并同个请求路径的不同方式
针对上面案例 /register 请求的方式可以有两种GET和POST,Express提供了合并书写接口的all()方法:
app.all('/login',(request, response) => {
let method = request.method
if(method==='GET'){
}else if(method==='POST'){
request.redirect('');
}
})
七、使用Express渲染模板页面
我们采用的是art-templates模板引擎
文档网址:http://aui.github.io/art-template/zh-cn/index.html
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nUgGwamu-1654648221215)(img/image-20210826093501872.png)]
使用之前需要安装 art-template和express-art-template
yarn add art-template 或者 npm install art-template
yarn add express-art-template 或者 npm install express-art-template
在views目录下新建list.html
// 1、修改模板引擎为html,导入express-art-template
app.engine('html', require('express-art-template'));
// 2、设置运行的模式为生产模式
// production 生产模式,线上模式
// development 开发模式
app.set('view options', {
debug: process.env.NODE_ENV !== 'production'
});
// 3、设置模板存放目录为public/views目录
app.set('views', path.join(__dirname, 'views'));
// 4、设置引擎后缀为html
app.set('view engine', 'html');
app.get('/list', (request, response) => {
var json_path='./public/resources/data.json';
var result=JSON.parse(fs.readFileSync(json_path));
// console.log(result);
//通过render返回该模板
response.render('list', result);
});
八、art-templates模板引擎的使用
使用语法: http://aui.github.io/art-template/zh-cn/docs/syntax.html
我们可以把数据从后端接口传入到前端页面中,这也是我们为什么用模板引擎的原因。
var fs = require("fs");
var path = require("path");
// json解析
app.use(express.json());
// 1、修改模板引擎为html,导入express-art-template
app.engine('html', require('express-art-template'));
// 2、设置运行的模式为生产模式
// production 生产模式,线上模式
// development 开发模式
app.set('view options', {
debug: process.env.NODE_ENV !== 'production'
});
// 3、设置模板存放目录为public/views目录
app.set('views', path.join(__dirname, 'views'));
// 4、设置引擎后缀为html
app.set('view engine', 'html');
app.get('/list', (req, res) => {
const json_path= path.join(__dirname,'public/resources','data.json');
var result=JSON.parse(fs.readFileSync(json_path));
// console.log(result);
//通过render返回该模板
res.render('data', result);
});
在views下的data.html中:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>数据列表</title>
<!-- 注意此处引入的静态资源的路径为浏览器访问静态资源路径并非网页文件与静态资源路径 -->
<link rel="stylesheet" href="resources/bootstrap-4.6.0/css/bootstrap.min.css">
<script src="javascripts/jquery-3.6.0.min.js"></script>
<script src="javascripts/popper.min.js"></script>
<script src="resources/bootstrap-4.6.0/js/bootstrap.min.js"></script>
</head>
<body>
<div class="table-responsive">
<table class="table table-bordered table-hover">
<thead>
<tr>
<th>ID</th>
<th>姓名</th>
<th>性别</th>
<th>城市</th>
<th>签名</th>
<th>积分</th>
<th>评分</th>
<th>职业</th>
<th>财富</th>
</tr>
</thead>
<tbody>
{{each data}}
<tr>
<td>{{$value.id}}</td>
<td>{{$value.username}}</td>
<td>{{$value.sex}}</td>
<td>{{$value.city}}</td>
<td>{{$value.sign}}</td>
<td>{{$value.experience}}</td>
<td>{{$value.logins}}</td>
<td>{{$value.wealth}}</td>
<td>{{$value.classify}}</td>
</tr>
{{/each}}
</tbody>
</table>
</div>
</body>
</html>
类似的模板引擎还有 ejs 模板引擎 https://ejs.bootcss.com/
九、在项目中使用路由
在项目中,我们不会把路由接口直接书写在项目入口文件中。
项目文件夹下新建routes文件夹,新建index.js:
var express = require('express');
var router = express.Router();
/**
* 获取主页面
* @request 封装请求对象
* @response 封装响应对象
* @next 勾子
*/
router.get('/', function(request, response, next) {
var json_path='./public/resources/data.json';
var result=JSON.parse(fs.readFileSync(json_path));
// console.log(result);
//通过render返回该模板
response.render('index', result);
});
module.exports = router;
项目文件夹下新建routes文件夹,新建user.js:
var express = require('express');
var router = express.Router();
/**
* 用户get方式表单登录
* @request 封装请求对象
* @response 封装响应对象
* @next 勾子
*/
router.get('/user/login', function (request, response, next) {
let content = '';
content += '帐号--->' + request.query.account;
content += ' 密码--->' + request.query.password;
response.send(content);
});
/**
* 用户post方式表单登录
* @request 封装请求对象
* @response 封装响应对象
* @next 勾子
*/
router.post('/user/login', function (request, response, next) {
let content = '';
content += '帐号--->' + request.body.account;
content += ' 密码--->' + request.body.password;
// 响应消息内容
response.send(content);
});
/**
* 用户post方式ajax json登录
* @request 封装请求对象
* @response 封装响应对象
*/
router.post('/user/login_json', function (request, response, next) {
let data = request.body;
let content = '';
content += '帐号--->' + data.account;
content += ' 密码--->' + data.password;
console.log(content);
// 返回json对象
response.send(data);
});
/**
* 地址栏参数
* 测试地址:http://127.0.0.1:2021/user/2/5
* @request 封装请求对象
* @response 封装响应对象
*/
router.get('/user/:pageNum/:pageSize', function (request, response, next) {
// console.log(request.params)
let data = {
pageNum: request.params.pageNum,
pageSize: request.params.pageSize,
}
// 返回对象需要配置app.use(express.json());
// response.send(data);
// 返回json格式字符串
response.send(JSON.stringify(data));
});
module.exports = router;
备份原app.js,重新编写app.js
在项目入口文件app.js中:
var express = require('express');
var path = require('path');
// var cookieParser = require('cookie-parser');
// var logger = require('morgan');
var app = express();
// app.use(logger('dev'));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
// app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));
// 设置模板引擎
app.engine('html', require('express-art-template'));
app.set('view options', {
debug: process.env.NODE_ENV !== 'production'
});
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'html');
// 引入路由模块
var indexRouter = require('./routes/index');
var userRouter = require('./routes/user');
// 路由绑定
app.use('/', indexRouter);
app.use('/user', userRouter);
// module.exports = app;
// 监听服务
app.listen(2021, () => {
// 监听log日志
console.log("服务已启动,{0.0.0.0:2020},请访问http://127.0.0.1:2021");
});
项目文件夹下新建views文件夹,新建login.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>显示隐藏密码</title>
<style>
* {
margin: 0px;
padding: 0px;
}
body {
background: #DADBDC;
}
#box {
width: 640px;
height: 350px;
background: #FFFFFF;
margin: 0 auto;
margin-top: 167.576px;
border-radius: 15px;
text-align: center;
position: relative;
}
.input_container {
margin-top: 30px;
padding-left: 60px;
width: 290px;
height: 38px;
line-height: 38px;
font-size: 16px;
}
button,
input[type=submit]{
margin-top: 30px;
width: 350px;
height: 38px;
border-radius: 5px;
font-size: 18px;
}
</style>
<link rel="stylesheet" href="./stylesheets/font-awesome-5.12.0/css/all.min.css">
</head>
<body>
<div id="box">
<h1>登录</h1>
<!-- <form action="user/login" method="get"> -->
<form action="user/login" method="post">
<i class="fa fa-user fa-2x" aria-hidden="true" style="position: absolute;top: 78px; left: 166px;"></i><input class="input_container" type="text" name="account" id="account" value="" placeholder="请输入账号" autocomplete="off">
<br>
<i class="fa fa-key fa-2x" aria-hidden="true" style="position: absolute;top: 150px; left: 166px;"></i><input class="input_container" type="password" name="password" id="password" value="" placeholder="请输入密码" autocomplete="off">
<i id="eye" class="fa fa-eye-slash fa-2x" aria-hidden="true" style="position: absolute;top: 150px; right: 160px;"></i>
<input type="submit" value="登录">
</form>
<button type="button" id="ajax_json">ajax json</button>
</div>
</body>
</html>
<script>
window.addEventListener('DOMContentLoaded', function(params) {
let eye = window.document.querySelector('#eye');
eye.addEventListener('click', function(params) {
let password = window.document.querySelector('#password');
let type = password.type;
if ('password' == type) {
password.type = 'text';
} else {
password.type = 'password';
}
// 获取该元素的类样式名称列表 DOMTokenList
let list = eye.classList;
// window.console.log(list);
for (const class_name of list) {
// window.console.log(class_name);
if ('password' == type) {
list.replace('fa-eye-slash', 'fa-eye');
} else {
list.replace('fa-eye', 'fa-eye-slash');
}
}
});
});
</script>
<script src="./javascripts/jquery-3.6.0.min.js"></script>
<script>
$(function (params) {
$('#ajax_json').click(function (e) {
e.preventDefault();
let data = {
account: $('#account').val(),
password: $('#password').val(),
}
$.ajax({
type: "post",
url: "user/login_json",
contentType:'application/json',
data: JSON.stringify(data),
dataType: "json",
success: function (response) {
window.alert('账号:' + response.account + ' 密码:' + response.password )
}
});
});
});
</script>
十、处理请求之前的勾子函数
这个功能在此先做了解,后面在项目中再去用。
如果在执行处理请求的函数之前想执行一些代码,例如验证是否已经登录的工作。
可以在app.use(utils.checkLogin, routers); 前面添加一个函数
新建utils文件夹,新建index.js文件:
function checkLogin(request, response, next){
console.log("执行接口代码之前会执行这里的代码");
next(); //直接跳入请求的接口执行代码
}
module.exports = {
checkLogin
}
在项目入口函数app.js中:
var express = require('express');
var path = require('path');
// var cookieParser = require('cookie-parser');
// var logger = require('morgan');
var app = express();
// app.use(logger('dev'));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
// app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));
// 设置模板引擎
app.engine('html', require('express-art-template'));
app.set('view options', {
debug: process.env.NODE_ENV !== 'production'
});
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'html');
// 引入路由模块
var indexRouter = require('./routes/index');
var userRouter = require('./routes/user');
// 引入对应工具模块
const utils = require('./utils/index.js');
// 执行接口函数之前所执行的函数
app.use(utils.checkLogin, indexRouter);
// 路由绑定
app.use('/', indexRouter);
app.use('/user', userRouter);
// module.exports = app;
// 监听服务
app.listen(2021, () => {
// 监听log日志
console.log("服务已启动,{0.0.0.0:2020},请访问http://127.0.0.1:2021");
});
效果:在执行routers下面每一个接口之前,都会执行checkLogin函数里面的代码。
应用:这可以用来我们后面在项目中做验证登录工作。