Node服务器-express框架

158 阅读10分钟

1. express安装

  • express的使用过程有两种方式:
    • 方式一:通过express提供的脚手架,直接创建一个应用的骨架

      # 安装脚手架
      npm install -g express-generator
      # 创建项目
      express express-demo
      # 安装依赖
      npm install
      # 启动项目
      node bin/www
      
    • 方式二:从零搭建自己的express应用结构

      • npm init -y

2. express基本使用

const express = require('express')

// 1.创建express的服务器
const app = express()

// 客户端访问URL: /login和/home
app.post('/login', (req, res) => {
  // 处理login请求
  res.end('登录成功, 欢迎回来~')
})

app.get('/home', (req, res) => {
  res.end('首页的数据列表~')
})

// 2.启动服务器, 并且监听端口
app.listen(9000, () => {
  console.log('express服务器启动成功~')
})

3. express中间件

  • 中间件是什么呢?

    • 中间件的本质是传递给express的一个回调函数;
    • 这个回调函数接受三个参数:
      • 请求对象(request对象)
      • 响应对象(response对象)
      • next函数(在express中定义的用于执行下一个中间件的函数
  • 中间件中可以执行哪些任务呢?

    • 执行任何代码
    • 更改请求(request)和响应(response)对象
    • 结束请求-响应周期(返回数据)
    • 调用栈中的下一个中间件
  • 如果当前中间件功能没有结束请求-响应周期,则必须调用next()将控制权传递给下一个中间件功能,否则,请求将被挂起

    image.png

    const express = require('express')
    
    const app = express()
    
    // 给express创建的app传入一个回调函数
    // 传入的这个回调函数就称之为是中间件(middleware)
    // app.post('/login', 回调函数 => 中间件)
    app.post('/login', (req, res, next) => {
      // 1.中间件中可以执行任意代码
      console.log('first middleware exec~')
      // 打印
      // 查询数据
      // 判断逻辑
    
      // 2.在中间件中修改req/res对象
      req.age = 99
    
      // 3.可以在中间件中结束响应周期
      // res.json({ message: "登录成功, 欢迎回来", code: 0 })
    
      // 4.执行下一个中间件
      next()
    })
    
    app.use((req, res, next) => {
      console.log('second middleware exec~')
    })
    
    
    app.listen(9000, () => {
      console.log('express服务器启动成功~')
    })
    

4. 中间件的深入理解

  • 如何将一个中间件应用到我们的应用程序中呢?
    • express主要提供了两种方式:
      • app/router.use
      • app/router.methods
    • 可以是 app,也可以是router
    • methods指的是常用的请求方式,比如: app.get 或app.post 等

4.1 普通的中间件

  • 总结: 当express接收到客户端发送的网络请求时, 在所有中间中开始进行匹配
    • 当匹配到第一个符合要求的中间件时, 那么就会执行这个中间件
    • 后续的中间件是否会执行呢? 取决于上一个中间件有没有执行next
    const express = require('express')
    
    const app = express()
    
    // 通过use方法注册的中间件是最普通的/简单的中间件
    // 通过use注册的中间件, 无论是什么请求方式都可以匹配上
    // login/get
    // login/post
    // abc/patch
    app.use((req, res, next) => {
      console.log('normal middleware 01')
      // res.end('返回数据')
      next()
    })
    
    app.use((req, res, next) => {
      console.log('normal middleware 02')
    })
    
    
    // 开启服务器
    app.listen(9000, () => {
      console.log('express服务器启动成功~')
    })
    

4.2 路径匹配的中间件

const express = require('express')

const app = express()

// 注册普通的中间件
// app.use((req, res, next) => {
//   console.log('match normal middleware')
//   res.end('--------')
// })

// 注册路径匹配的中间件
// 路径匹配的中间件是不会对请求方式(method)进行限制
app.use('/home', (req, res, next) => {
  console.log('match /home middleware')
  res.end('home data')
})

app.listen(9000, () => {
  console.log('express服务器启动成功~')
})

4.3 路径和方法匹配的中间件

const express = require('express')

const app = express()


// 注册中间件: 对path/method都有限制
// app.method(path, middleware)
app.get('/home', (req, res, next) => {
  console.log('match /home get method middleware')
  res.end('home data')
})

app.post('/users', (req, res, next) => {
  console.log('match /users post method middleware')
  res.end('create user success')
})


app.listen(9000, () => {
  console.log('express服务器启动成功~')
})

4.4 注册多个中间件

  • app.get('/abc', 中间件1, 中间件2)

    const express = require('express')
    
    const app = express()
    
    // app.get(路径, 中间件1, 中间件2, 中间件3)
    app.get('/home', (req, res, next) => {
      console.log('match /home get middleware01')
      next()
    }, (req, res, next) => {
      console.log('match /home get middleware02')
      next()
    }, (req, res, next) => {
      console.log('match /home get middleware03')
      next()
    }, (req, res, next) => {
      console.log('match /home get middleware04')
    })
    
    app.listen(9000, () => {
      console.log('express服务器启动成功~')
    })
    
  • 匹配规则:

    • 发送请求, 匹配到第一个符合规则的中间件, 执行这个中间件
    • 只有调用next才会执行下面的中间件
  • 应用场景:

    • 将一个中间件进行拆分成多个中间件,将功能拆分,更清晰
    • 注意拆分的中间件执行时需要执行next

4.5 中间件匹配的练习

const express = require('express')

const app = express()

// 1.注册两个普通的中间件
app.use((req, res, next) => {
  console.log('normal middleware01')
  next()
})

app.use((req, res, next) => {
  console.log('normal middleware02')
  next()
})


// 2.注册路径path/method的中间件
app.get('/home', (req, res, next) => {
  console.log('/home get middleware01')
  next()
}, (req, res, next) => {
  console.log('/home get middleware02')
  next()
})

app.post('/login', (req, res, next) => {
  console.log('/login post middleware')
  next()
})


// 3.注册普通的中间件
app.use((req, res, next) => {
  console.log('normal middleware03')
  next()
})

app.use((req, res, next) => {
  console.log('normal middleware04')
})


app.listen(9000, () => {
  console.log('express服务器启动成功~')
})
  • /abc GET
    • normal middleware01
    • normal middleware02
    • normal middleware03
    • normal middleware04
  • /home GET
    • normal middleware01
    • normal middleware02
    • /home get middleware01
    • /home get middleware02
    • normal middleware03
    • normal middleware04

5. 中间件的应用案例

5.1 用户登录和注册

  • 原始操作
    const express = require('express')
    
    const app = express()
    
    // 注册两个实际请求的中间件
    // 案例一: 用户登录的请求处理 /login post => username/password
    app.post('/login', (req, res, next) => {
      // 1.获取本次请求过程中传递过来的json数据
      let isLogin = false
      req.on('data', (data) => {
        const dataString = data.toString()
        const dataInfo = JSON.parse(dataString)
        if (dataInfo.username === 'zhangsan' && dataInfo.password === '123456') {
          isLogin = true
        }
      })
    
      req.on('end', () => {
        if (isLogin) {
          res.end('登录成功, 欢迎回来~')
        } else {
          res.end('登录失败, 请检测账号和密码是否正确~')
        }
      })
    })
    
    // 案例二: 注册用户的请求处理 /register post => username/password
    app.post('/register', (req, res, next) => {
        // 1.获取本次请求过程中传递过来的json数据
        let isRegister = false
        req.on('data', (data) => {
          const dataString = data.toString()
          const dataInfo = JSON.parse(dataString)
          // 查询数据库中该用户是否已经注册过
          isRegister = false
        })
    
        req.on('end', () => {
          if (isRegister) {
            res.end('注册成功, 开始你的旅程~')
          } else {
            res.end('注册失败, 您输入的用户名被注册~')
          }
        })
    })
    
    app.listen(9000, () => {
      console.log('express服务器启动成功~')
    })
    
  • body手动解析
    const express = require('express')
    
    const app = express()
    
    // app.use((req, res, next) => {
    //   if (req.headers['content-type'] === 'application/json') {
    //     req.on('data', (data) => {
    //       const jsonInfo = JSON.parse(data.toString())
    //       req.body = jsonInfo
    //     })
    
    //     req.on('end', () => {
    //       next()
    //     })
    //   } else {
    //     next()
    //   }
    // })
    
    // 直接使用express提供给我们的中间件
    app.use(express.json())
    
    // 注册两个实际请求的中间件
    // 案例一: 用户登录的请求处理 /login post => username/password
    app.post('/login', (req, res, next) => {
      console.log(req.body)
    })
    
    // 案例二: 注册用户的请求处理 /register post => username/password
    app.post('/register', (req, res, next) => {
      console.log(req.body)
    })
    
    app.listen(9000, () => {
      console.log('express服务器启动成功~')
    })
    

5.2 内置中间件

  • express.json()

    • 解析request body 中间件
  • express.urlencoded({extended: true})

    • 解析 application/x-www-form-urlencoded 格式
    const express = require('express')
    
    // 创建app对象
    const app = express()
    
    // 应用一些中间件
    app.use(express.json()) // 解析客户端传递过来的json
    // 解析传递过来urlencoded的时候, 默认使用的node内置querystring模块
    // { extended: true }: 不再使用内置的querystring, 而是使用qs第三方库
    app.use(express.urlencoded({ extended: true })) // 解析客户端传递过来的urlencoded
    
    // 编写中间件
    app.post('/login', (req, res, next) => {
      console.log(req.body)
      res.end('登录成功, 欢迎回来~')
    })
    
    // 启动服务器
    app.listen(9000, () => {
      console.log('express服务器启动成功~')
    })
    

5.3 第三方中间件

  • 日志记录中间件:

    • morgan => express官方提供/单独安装
    • npm install morgan
    const fs = require('fs')
    const express = require('express')
    const morgan = require('morgan')
    
    // 创建app对象
    const app = express()
    
    // 应用第三方中间件
    const writeStream = fs.createWriteStream('./logs/access.log')
    app.use(morgan('combined', { stream: writeStream }))
    
    // 编写中间件
    app.post('/login', (req, res, next) => {
      res.end('登录成功, 欢迎回来~')
    })
    
    // 启动服务器
    app.listen(9000, () => {
      console.log('express服务器启动成功~')
    })
    
  • 文件上传的中间件: multer

    • 安装:npm install multer

    • 单文件上传

      const express = require('express')
      const multer = require('multer')
      
      // 创建app对象
      const app = express()
      
      // 应用一个express编写第三方的中间件
      const upload = multer({
        // 上传文件存储位置
        dest: './uploads'
      })
      
      // 编写中间件
      // 上传单文件: single方法
      app.post('/avatar', upload.single('avatar') , (req, res, next) => {
        console.log(req.file)
        res.end('文件上传成功~')
      })
      
      // 启动服务器
      app.listen(9000, () => {
        console.log('express服务器启动成功~')
      })
      
    • 多文件上传

      const express = require('express')
      const multer = require('multer')
      
      // 创建app对象
      const app = express()
      
      // 应用一个express编写第三方的中间件
      const upload = multer({
        storage: multer.diskStorage({
          // 文件存放位置
          destination(req, file, callback) {
            callback(null, './uploads')
          },
          // 文件名称修改
          filename(req, file, callback) {
            callback(null, Date.now() + '_' + file.originalname)
          }
        })
      })
      
      // 编写中间件
      // 上传单文件: single方法
      app.post('/avatar', upload.single('avatar') , (req, res, next) => {
        console.log(req.file)
        res.end('文件上传成功~')
      })
      
      // 上传多文件: 
      app.post('/photos', upload.array('photos'), (req, res, next) => {
        console.log(req.files)
        res.end('上传多张照片成功~')
      })
      
      // 启动服务器
      app.listen(9000, () => {
        console.log('express服务器启动成功~')
      })
      
    • 解析 formdata

      const express = require('express')
      const multer = require('multer')
      
      // 创建app对象
      const app = express()
      
      // express内置的插件
      app.use(express.json())
      app.use(express.urlencoded({ extended: true }))
      
      // 编写中间件
      const formdata = multer()
      
      app.post('/login', formdata.any(), (req, res, next) => {
        console.log(req.body)
        res.end('登录成功, 欢迎回来~')
      })
      
      // 启动服务器
      app.listen(9000, () => {
        console.log('express服务器启动成功~')
      })
      

6. 客户端传递的参数

  • 通过get请求中的URL的params

  • 通过get请求中的URL的query

  • 通过post请求中的body的json格式(中间件中已经使用过)

  • 通过post请求中的body的x-www-form-urlencoded格式(中间件使用过)

  • 通过post请求中的form-data格式(中间件中使用过)

    const express = require('express')
    
    // 创建app对象
    const app = express()
    
    // 编写中间件
    // 1.解析queryString
    app.get('/home/list', (req, res, next) => {
      // offset/size
      const queryInfo = req.query
      console.log(queryInfo)
    
      res.end('data list数据')
    })
    
    // 2.解析params参数
    app.get('/users/:id', (req, res, next) => {
      const id = req.params.id
    
      res.end(`获取到${id}的数据~`)
    })
    
    // 启动服务器
    app.listen(9000, () => {
      console.log('express服务器启动成功~')
    })
    

7. 服务器响应数据方式

  • end方法
    • 类似于http中的response.end方法,用法是一致的
  • json方法
    • json方法中可以传入很多的类型:object、array、string、boolean、number、null等,它们会被转换成json格式返回
  • status方法
    • 用于设置状态码
    • 注意:这里是一个函数,而不是属性赋值
    const express = require('express')
    
    // 创建app对象
    const app = express()
    
    // 编写中间件
    app.post('/login', (req, res, next) => {
      // 1.res.end方法(比较少)
      // res.end('登录成功, 欢迎回来~')
    
      // 2.res.json方法(最多)
      // res.json({
      //   code: 0,
      //   message: '欢迎回来~',
      //   list: [
      //     { name: 'iPhone', price: 111 },
      //     { name: 'iPad', price: 111 },
      //     { name: 'iMac', price: 111 },
      //     { name: 'Mac', price: 111 },
      //   ]
      // })
    
      // 3.res.status方法: 设置http状态码
      res.status(201)
      res.json('创建用户成功~')
    })
    
    // 启动服务器
    app.listen(9000, () => {
      console.log('express服务器启动成功~')
    })
    

8. express的路由

  • express.Router()

    const express = require('express')
    const userRouter = require('./router/userRouter')
    
    // 创建app对象
    const app = express()
    
    // 编写中间件
    app.post('/login', (req, res, next) => {
    
    })
    
    app.get('/home', (req, res, next) => {
    
    })
    
    /** 用户的接口 */
    // 1.将用户的接口直接定义在app中
    // app.get('/users', (req, res, next) => {})
    // app.get('/users/:id', (req, res, next) => {})
    // app.post('/users', (req, res, next) => {})
    // app.delete('/users/:id', (req, res, next) => {})
    // app.patch('/users/:id', (req, res, next) => {})
    
    // 2.将用户的接口定义在单独的路由对象中
    const userRouter = express.Router()
    userRouter.get('/', (req, res, next) => {
      res.json('用户列表数据')
    })
    userRouter.get('/:id', (req, res, next) => {
      const id = req.params.id
      res.json('某一个用户的数据:' + id)
    })
    userRouter.post('/', (req, res, next) => {
      res.json('创建用户成功')
    })
    userRouter.delete('/:id', (req, res, next) => {
      const id = req.params.id
      res.json('删除某一个用户的数据:' + id)
    })
    userRouter.patch('/:id', (req, res, next) => {
      const id = req.params.id
      res.json('修改某一个用户的数据:' + id)
    })
    
    // 让路由生效
    app.use('/users', userRouter)
    
    // 启动服务器
    app.listen(9000, () => {
      console.log('express服务器启动成功~')
    })
    

9. express静态资源服务器

  • express.static():将一个文件夹作为静态资源

    const express = require('express')
    
    // 创建app对象
    const app = express()
    
    // 内置的中间件: 直接将一个文件夹作为静态资源
    app.use(express.static('./uploads'))
    app.use(express.static('./build'))
    
    // 编写中间件
    app.post('/login', (req, res, next) => {})
    
    // 启动服务器
    app.listen(9000, () => {
      console.log('express服务器启动成功~')
    })
    

10. express中错误处理方案

  • 常见的对于错误的处理会写一个中间件

    const express = require('express')
    
    // 创建app对象
    const app = express()
    
    app.use(express.json())
    
    // 编写中间件
    app.post('/login', (req, res, next) => {
      // 1.获取登录传入的用户名和密码
      const { username, password } = req.body
    
      // 2.对用户名和密码进行判断
      if (!username || !password) {
        next(-1001)
      } else if (username !== 'zhangsan' || password !== '123456') {
        next(-1002)
      } else {
        res.json({
          code: 0,
          message: '登录成功, 欢迎回来~',
          token: '323dfafadfa3222'
        })
      }
    })
    
    
    // 错误处理的中间件
    app.use((errCode, req, res, next) => {
      const code = errCode
      let message = '未知的错误信息'
    
      switch(code) {
        case -1001:
          message = '没有输入用户名和密码'
          break
        case -1002:
          message = '输入用户名或密码错误'
          break
      }
    
      res.json({ code, message })
    })
    
    
    // 启动服务器
    app.listen(9000, () => {
      console.log('express服务器启动成功~')
    })
    
  • 注意事项:

    • 异常中间件全局只包含一个(如果有多个,第二个及以后的是不会生效的)
    • 异常中间件可以传递给普通中间件
    • 异常中间件需要放在所有中间件的最后,保证能捕获到异常
    • 异常中间件只能捕获中间件回调函数中的异常,比如在回调函数中使用 promise 抛出的异常 是不会被捕获的
    // 针对上面第四条的异常捕获
    app.use((req, res, next) => {
        new Promise((resolve,reject) => {
            resolve()
        }).then(() => {
            console.log('then')
            throw new Error('promise error')
        })
    })
    
    // 全局异常捕获
    process.on('uncaughException', function(err) {
        console.log('uncaughException', err.message)
    })
    
    // 全局 promise 异常捕获
    process.on('unhandledRejection', function(err) {
        console.log('unhandledRejection', err.message)
    })
    
    // 全局抛出异常
    throw new Error('all error!')