第一章:快速上手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}}
    {{each users}}
        第{{$index+1}}个用户
          <li>name:{{$value.name}}</li>
          <li>age:{{$value.age}}</li>
    {{/each}}
    

    index.art

    {{data}}
    {{each users}}
        第{{$index+1}}个用户
          <li>name:{{$value.name}}</li>
          <li>age:{{$value.age}}</li>
    {{/each}}
    

    第七章:cookie和Session

    7.1-概述

    ​ 众所周知,Http协议是无状态的,也就意味着,针对浏览器与服务器之间的请求和响应(也叫会话),当两者之间的会话结束时,服务器端并不会记忆客户端(浏览器)曾经访问过。

    ​ 但是,在实际应用程序开发中,有些业务需要浏览器和服务器之间能够保持会话。比如常见的登录业务,在同一个浏览器下,当用户第一次登录成功并进入首页时,下次再使用同一个浏览器访问首页时,则不需要再登录。而要实现下次访问不再登录时,需要让服务端能够识别曾经访问过它的浏览器,这就需要会话跟踪技术来实现。分别是cookiesession

    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)