第一章:快速上手Express
1.1-为什么要学习框架
在使用Node.js原始方式开发web应用时,我们发现不管是对请求的处理(如:路由的判断、参数的处理等),还是对响应的处理(如:设置响应状态、类型等),操作依然比较繁琐,大把的精力和时间消耗在这些和业务无关的操作上,降低了开发效率。而在实际开发中,我们关注的更多是业务(功能)的开发,为了简化我们的开发、提高我们的开发效率,Express框架诞生。
1.2-什么是Express
基于 Node.js 平台,快速、开放、极简的 Web 开发框架。
Express提供了一系列强大的特性,来帮助我们创建各种web应用。
- 提供了简洁的路由定义方式
- 对获取HTTP请求参数进行了简化处理
- 对模板引擎支持程度高,便于渲染动态的HTML页面
- 提供了中间件机制有效的控制HTTP请求
- 拥有大量第三方中间件对功能进行扩展
原生Node.js和Express框架路由对比:
原生Node.js和Exprees框架获取请求参数对比
1.3-快速入门
安装express框架
命令:npm install express --save
构建第一个基于express框架的web应用
// 导入express包
const express = require('express')
// 创建服务器对象
const app = express()
// 监听请求(网站根路径)
app.get('/', (req, res) => {
// 响应内容
// send方法自动检测并设置响应内容类型以及状态码
res.send('Hello Expreess')
})
// 设置端口
app.listen(3000, 'localhost')
console.log('服务器启动成功:http://localhost:3000')
第二章:中间件
2.1-认识中间件
什么是中间件
我们开发web应用,核心操作无非就是处理请求和响应。
从请求处理开始直到响应处理结束,才是一个完整的周期。
在请求和响应之间,我们可以通过express提供的一系列方法或第三方模块进行额外的操作。
而中间件就是处理请求和响应之间的一系列方法,这些方法可以接收客户端发送的请求、可以对请求作出响应、也可以将请求继续交给下一个中间件处理。
中间件由两部分组成,中间件方法以及请求处理函数
- 中间件方法由Express提供,负责拦截请求
- 请求处理函数由开发人员提供,负责处理请求。
比如我们用到的路由,其实就是一个中间件(我们用到的get和post方法就是中间件方法)
体验中间件的使用
我们可以针对同一个请求,设置多个中间件进行多次处理。
默认情况下,请求从上到下依次匹配中间件,一旦匹配成功,终止匹配。
可以调用next方法将请求控制权交给下要给中间件,直到遇到结束请求的中间件。
const express = require('express')
const app = express()
// 中间件1处理请求
app.get('/user', (req, res,next) => {
req.name = 'Burce'
// 调用next方法,将控制权交给下一个中间件
next()
})
// 中间件2处理请求
app.get('/user', (req, res) => {
res.send(req.name)
})
app.listen(3000, 'localhost')
console.log('服务器启动成功:http://localhost:3000/user')
2.2-中间件use方法
use方法使用介绍
app.use方法可以匹配所有的请求方式(不区分get和post),也可以直接传入请求处理函数,接收所有请求
app.use((req,res,next)=>{
console.log('处理请求')
...
next()
})
app.use第一个参数也可以传入请求地址,代表不论什么请求方式,只要是这个请求地址就可以接收这个请求
app.use('请求路径',(req,res,next)=>{
console.log('处理请求')
...
next()
})
代码
const express = require('express')
const app = express()
// 【中间件a-处理所有请求】
app.use((req, res, next) => {
console.log('中间件a处理了请求')
next()
})
// 【中间b-处理/user请求-不论是get还是post】
app.use('/user', (req, res, next) => {
console.log('中间件b处理了/user请求')
next()
})
// 【中间c-处理/user请求-get请求】
app.get('/user', (req, res, next) => {
console.log('中间件c处理了/user-get请求')
res.send('ok')
})
// 【中间d-处理/user请求-get请求】
app.get('/list', (req, res, next) => {
console.log('中间件d处理了/list-get请求')
res.send('ok')
})
app.listen(3000)
在浏览器中输入地址http://localhost:3000/user发送请求
请求结果
中间件a处理了请求
中间件b处理了/user请求
中间件c处理了/user-get请求
2.3-中间件的实际应用
登录验证
比如登录验证,客户端在访问需要登录的页面时,可以先使用中间件判断用户的登录状态,如果未登录则拦截请求,直接响应,禁止用户进入需要登录的页面。
app.use('/user', (req, res, next) => {
// 模拟是否登陆状态
let isLogin = false
if (isLogin) {
next()
} else {
res.send('请先登录')
}
})
app.get('/user', (req, res, next) => {
res.send('欢迎来到user页面')
})
网站维护公告
在所有的路由最上面定义所有请求的中间件,直接为客户端做出响应,网站正在维护中
app.use((req, res, next) => {
res.send('服务器正在维护中,截止到2030年')
})
app.use('/user', (req, res, next) => {
let isLogin = true
if (isLogin) {
next()
} else {
res.send('请先登录')
}
})
app.get('/user', (req, res, next) => {
res.send('欢迎来到user页面')
})
自定义404页面
可以把接收所有请求的中间接件定义在路由最后面,当上面的路由不满足时,说明没有匹配的路由,此时可以响应页面不存在。
app.use('/user', (req, res, next) => {
let isLogin = true
if (isLogin) {
next()
} else {
res.send('请先登录')
}
})
app.get('/user', (req, res, next) => {
res.send('欢迎来到user页面')
})
app.use((req, res, next) => {
res.status(404).send('你访问的页面被外星人带走了!')
})
2.4-错误处理中间件
使用方式
在程序执行的过程中,不可避免可能会发生一些无法预料的错误,比如数据库连接失败、文件读取失败等。
而错误处理中间件是一个集中处理错误的地方。
app.use((err,req,res,next)=>{
res.status(500).send('服务器内部错误')
})
对于异步错误,调用next方法,可以将错误信息通过参数的方式传递给next()方法,即可触发错误处理中间件。
app.get('/file',(req,res,next)=>{
fs.readFile('01.txt','utf-8',(err,data)=>{
if(err) {
next(err)
}else {
res.send(data)
}
})
})
代码演示
const express = require('express')
const fs = require('fs')
const app = express()
app.use('/user', (req, res, next) => {
throw new Error('未知错误')
})
app.get('/file', (req, res, next) => {
fs.readFile('01.txt', 'utf-8', (err, data) => {
if (err) {
next(err)
} else {
res.send(data)
}
})
})
app.use((err, req, res, next) => {
if (err) {
res.status(500).send(err.message)
}
})
app.listen(3000)
2.5-捕获错误
try catch介绍
在node.js中,异步ApI的错误信息都是通过回调函数获取的,支持Promise对象的异步API发送错误可以通过catch方法捕获。
异步函数执行如果发生错误要如何捕获错误呢?
try catch
可以捕获异步函数以及其他同步代码在执行过程中发生的错误,但是不能捕获其他类型的API发送的错误。
代码
const express = require('express')
const fs = require('fs')
/*默认情况下,fs.readFile方法返回的不是promise对象,所以无法使用await关键字,可以通过util模块下的promisify方法改造其他方法,使其可以使用await关键字 */
const promisify = require('util').promisify
fs.readFile = promisify(fs.readFile)
const app = express()
app.get('/file', async (req, res, next) => {
try {
const result = await fs.readFile('01.txt', 'utf-8')
res.send(result)
} catch (ex) { // 捕获错误
next(ex)
}
})
app.use((err, req, res, next) => {
if (err) {
res.status(500).send(err.message)
}
})
app.listen(3000)
第三章: 模块化路由
3.1-为什么要构建模块化路由
在项目实际开发当中,我们的项目是要根据业务划分模块并且多人协同开发的,若把所有的路由都集中在一个程序文件中处理,则难以实现多人协同开发且难以维护。
3.2-如何实现模块化路由
实现方式
首先根据模块的划分,为每个模块创建独立的路由程序文件;
其次在模块中通过express的Router方法
创建路由对象;
然后在模块中使用路由对象处理指定的请求;
最后,在程序入口文件中导入各个路由模块,并使用use方法挂载到web应用中。
app.use('路由名称',路由对象)
发送请求:/路由名称/请求地址
代码演示
路由模块home.js
const express = require('express')
const home = express.Router();
home.get('/index', (req, res) => {
res.send('欢迎来到home首页')
})
home.get('/list', (req, res) => {
res.send('欢迎来到home列表页')
})
module.exports = home
路由模块admin.js
const express = require('express')
const admin = express.Router();
admin.get('/index', (req, res) => {
res.send('欢迎来到admin首页')
})
admin.get('/list', (req, res) => {
res.send('欢迎来到admin列表页')
})
module.exports = admin
web应用程序入口app.js
const express = require('express')
// 导入home路由模块
const home = require('./router/home')
// 导入admin路由模块
const admin = require('./router/admin')
const app = express()
// 把home路由模块挂载到web应用中,监听/home路径下的请求
app.use('/home', home)
// 把admin路由模块挂载到web应用中,监听/admin路径下的请求
app.use('/admin', admin)
app.listen(80)
访问:例如http://localhost/admin/list
结果:欢迎来到admin列表页
第四章:请求参数
4.1-获取get请求参数
获取方式
req.query
,返回值是一个对象。
express框架内部将请求参数转换为对象。
代码演示
const express = require('express')
const app = express()
app.get('/index', (req, res) => {
res.send(req.query)
})
app.listen(80)
请求:http://localhost/index?word=程序媛&gender=女
结果:{"word":"程序媛","gender":"女"}
4.2-获取post请求参数
获取方式
安装第三方模块:npm install body-parser
导入并配置body-parser模块:
const bodyParser = require('body-parser')
// 拦截所有请求
// extend: false 表示方法内部使用querystring模块处理请求参数格式
// extend: true 表示方法内部使用第三方模块qs处理请求参数格式
app.use(bodyParser.urlencoded({extend: false}))
获取参数:req.body
返回一个对象
代码演示
form表单
<form action="http://localhost/add" method="post">
<p>账号:<input type="text" name="account"></p>
<p>密码:<input type="password" name="pwd"></p>
<p><input type="submit" value="提交"></p>
</form
服务端程序
const express = require('express')
// 导入body-parser模块
const bodyParser = require('body-parser')
const app = express()
// 配置bodyParser
app.use(bodyParser.urlencoded({extended:false}))
app.post('/add', (req, res) => {
// 获取post请求参数
res.send(req.body)
})
app.listen(80)
返回结果:{"account":"admin","pwd":"123456"}
4.3-Express中的路由参数
路由参数的定义和使用
请求路径格式: /路径名称/:参数1名称/:参数2名
调用方式:http://localhost/find/值1/值2
代码演示
const express = require('express')
const app = express()
app.get('/find/:id', (req, res) => {
res.send(req.params)
})
// 测试:http://localhost/find/1
app.get('/find/:id/:name', (req, res) => {
res.send(req.params)
})
// 测试:http://localhost/find/1/admin
app.get('/find', (req, res) => {
res.send('find')
})
// 测试:http://localhost/find
app.listen(80)
第五章:静态资源的处理
通过Express内置的express.static可以方便地托管静态文件,例如img、css、javascript文件等。
配置方式
app.use('虚拟路径',express.static('静态资源真实路径'))
虚拟路径可选
代码演示
const express = require('express')
const path = require('path')
const app = express()
const statiPath = path.join(__dirname,'./public')
// app.use(express.static(statiPath))
// 测试:http://localhost/index.html
app.use('/static', express.static(statiPath))
// 测试:http://localhost/static/index.html
app.listen(80)
第六章:Express中使用模板引擎
为了使art-template模板引擎能够更好的和Express框架结合,模板引擎官方在原art-template
模板引擎的基础上封装了express-art-template
。
安装模板引擎
安装命令: npm install art-template express-art-template
代码演示
app.js
const express = require('express')
const path = require('path')
const app = express()
// 多个模板使用公共数据
// app.locals.key = value
app.locals.users = [
{name:'Bruce',age: 10},
{name:'Andy',age: 20}
]
// 配置框架使用哪个模板引擎,以及处理后缀为什么的模板
app.engine('art', require('express-art-template'))
// 设置模板存放的目录
app.set('views', path.join(__dirname, 'views'))
// 设置模板的默认后缀
app.set('view engine', 'art')
app.get('/index', (req, res) => {
// index 模板名称 data模板中需要的数据
res.render('index', { data: '首页的数据' })
// render方法会自动匹配并响应
})
app.get('/list', (req, res) => {
res.render('list',{data:'列表页的数据'})
})
app.listen(80)
list.art
{{data}}
<ol>
{{each users}}
<li>
第{{$index+1}}个用户
<ul>
<li>name:{{$value.name}}</li>
<li>age:{{$value.age}}</li>
</ul>
</li>
{{/each}}
</ol>
index.art
{{data}}
<ol>
{{each users}}
<li>
第{{$index+1}}个用户
<ul>
<li>name:{{$value.name}}</li>
<li>age:{{$value.age}}</li>
</ul>
</li>
{{/each}}
</ol>
第七章:cookie和Session
7.1-概述
众所周知,Http协议是无状态的,也就意味着,针对浏览器与服务器之间的请求和响应(也叫会话),当两者之间的会话结束时,服务器端并不会记忆客户端(浏览器)曾经访问过。
但是,在实际应用程序开发中,有些业务需要浏览器和服务器之间能够保持会话。比如常见的登录业务,在同一个浏览器下,当用户第一次登录成功并进入首页时,下次再使用同一个浏览器访问首页时,则不需要再登录。而要实现下次访问不再登录时,需要让服务端能够识别曾经访问过它的浏览器,这就需要会话跟踪技术来实现。分别是cookie
和session
。
7.2-cookie
cookie:浏览器在电脑硬盘中开辟的一块空间,主要提供服务端存储数据。
- cookie中的数据是以域名的形式进行区分的。
- cookie中的数据是有过期时间的,超过时间数据会被浏览器自动删除。
- cookie中的数据会随着请求被自动发送到服务器端。
7.3-session
实际上就是一个对象,存储在服务器端的内存中,在session对象中也可以存储多条数据,每一条数据都有一个sessionid做为唯一标识。
session依赖于cookie,比如我们要实现一个邮箱和密码登录会话跟踪的功能。其实现过程如下图:
7.4-express-session
在express框架中,可以使用第三方包express-session
操作session
具体代码如下:实现一个记录用户使用某个终端访问该网站的次数
// 导入express模块
const express = require('express')
// 导入express-session模块
const session = require('express-session')
// 创建服务对象
const app = express();
// session的名称
let identityKey = 'skey';
//使用session
app.use(session({
name: identityKey,
secret: 'appkey', // 用来对session id相关的cookie进行签名
// store: new FileStore(), // 本地存储session(文本文件,也可以选择其他store,比如redis的)
saveUninitialized: false, // 是否自动保存未初始化的会话,建议false
resave: false, // 是否每次都重新保存会话,建议false
cookie: {
maxAge: 10 * 1000 // 有效期,单位是毫秒
}
}));
// session存储数据
app.get('/', (req, res) => {
// 判断是否是第一次访问
if (req.session.count) {
req.session.count += 1;
} else {
req.session.count = 1
}
res.send('欢迎访问,您是第' + req.session.count + '此访问我们')
})
//清除session
app.get('/logout', function(req, res, next){
// 备注:这里用的 session-file-store 在destroy 方法里,并没有销毁cookie
// 所以客户端的 cookie 还是存在,导致的问题 --> 退出登陆后,服务端检测到cookie
// 然后去查找对应的 session 文件,报错
// session-file-store 本身的bug
req.session.destroy(function(err) {
if(err){
res.json({ret_code: 2, ret_msg: '退出登录失败'});
return;
}
// req.session.loginUser = null;
res.clearCookie(identityKey);
res.send('ok')
});
});
app.listen(80)