推荐

  有时间可以阅读以下文章:

# 一、Express 基础

# 1、基本概述

  Express 是由 TJ Holowaychuk (opens new window) 开发的,它是一个流行的 Node.js 后端框架。

我们可以使用 Express (opens new window) 方便、快捷的构建 Web 应用程序和 API .

  从开发角度来讲,可以说它是一个路由和中间件的 Web 框架。

# 2、常规使用

安装:

npm install express

使用:

与 原生 http 模块对比
const { createServer } = require('http')

const server = createServer()

server.on('request', (req, res) => {
  //
  // 逻辑处理

  res.end()
})

server.listen(6061)
const express = require('express')

const app = express()

// 逻辑处理 —— " 中间件💖 "
// 例如:
app.post('/login', (req, res, next) => {
  //

  res.send()
})

app.listen(6061)

# 3、脚手架使用

  如果我们想要使用 express 快速搭建 node 项目,我们可以使用其提供了 Express generator (opens new window) 脚手架。

项目依赖/结构
  • 项目结构
<projectName>
├─ app.js
├─ bin
│  └─ www 🚩
├─ package.json
├─ public
│  ├─ images
│  ├─ javascripts
│  └─ stylesheets
│     └─ style.css
├─ routes
│  ├─ index.js
│  └─ users.js
└─ views
   ├─ error.ejs
   └─ index.ejs
  • 默认依赖
{
  "dependencies": {
    "cookie-parser": "~1.4.4",
    "debug": "~2.6.9",
    "ejs": "~2.6.1",
    "express": "~4.16.1",
    "http-errors": "~1.6.3",
    "morgan": "~1.9.1"
  }
}

安装:

npm install express-generator -g
express -h # 查看是否成功

使用:

express myapp #(默认是基于jade模板引擎)
express myapp --view=ejs #(指定基于ejs模板引擎)

node bin/www
npm run start

# 二、中间件

  Express 应用程序本质上就是一系列的中间件函数的调用。而中间件(middleware)的本质就是传递给 express 的一个回调函数,该函数接收三个参数:

  • request 对象
  • response 对象
  • next 函数

# 1、中间件函数

  在中间件函数 (opens new window)中,我们可以利用函数的三个参数执行很多任务,如:

  • 打印、逻辑判断、数据操作……
  • 修改 request 对象、response 对象……
  • 可以 使用 next() 续接下一个中间件(挂起响应周期)
  • 或者 使用 res.send() / res.json() 结束响应周期……
中间件注册

  对于中间件函数的注册,express 主要提供了以下两种方式(app):

Application Router
app.use() router.use() 任何请求都可以匹配上
app.METHOD() router.METHOD() 与对应的请求类型和请求路径相同才可以匹配上

  其实app.METHOD()准确来说只是app.use()的一个小分支罢了。

更多可以查看:Application (opens new window)Router (opens new window)

app.use('/posts/:id', function (req, res, next) {
  if (req.method === 'GET') {
    console.log(req.requestTime)

    res.send()
  }
  if (req.method === 'POST') {
    console.log(req.requestTime)

    res.send()
  }
  // ……
})
const express = require('express')
const app = express()

// --------------

// 中间件 1
const MTime = function (req, res, next) {
  req.requestTime = Date.now() // 修改 request 对象

  next() // 续接下一个中间件
}
// 中间件注册 1
app.use(MTime)

// ------

// 中间件 2
const MGetPost = function (req, res) {
  console.log(req.requestTime) // 打印

  res.send() // 结束响应周期
}

// 中间件注册 2
app.post('/posts/:id', MGetPost)

// --------------

app.listen(6061)

# 2、请求处理 ✨

  在 express 中,我们可以直接通过 req.pathreq.queryreq.body 等快速获取目标数据。

req.body 的来源 ✍

  实际上express.json()内部其实使用了body-parser 中间件,它可以将 Content-Type 为 application/json 的 JSON 字符串 转换为 js 对象,并将其放入 req.body 中。

// app.use(express.json())
app.use((req, res, next) => {
  if (req.headers['content-type'] === 'application/json') {
    let data = ''
    req.on('data', (chunk) => {
      data += chunk
    })
    req.on('end', () => {
      req.body = JSON.parse(data) // 数据挂载
      next()
    })
  } else {
    next()
  }
})
路径匹配 与 正则

  在 express 中,我们可以使用正则等方式灵活设置待匹配的路径 (opens new window)

// 要使用路由参数✨定义路由
app.get('/users/:userId/books/:bookId', function (req, res) {
  res.send(req.params)
})

// 此路径将匹配 acd 和 abcd。
app.get('/ab?cd', function (req, res) {
  res.send('ab?cd')
})

// 此路径将匹配 abcd、 abbcd、 abbbcd 等。
app.get('/ab+cd', function (req, res) {
  res.send('ab+cd')
})

// 此路径将匹配 abcd、 abxcd、 abRANDOMcd、 ab123cd 等。
app.get('/ab*cd', function (req, res) {
  res.send('ab*cd')
})
const express = require('express')
const app = express()

// --------------

// 1、路径参数
app.get('/posts', (req, res, next) => {
  const pathname = req.path
  const query = req.query

  console.log(pathname, query) // /posts { id: '1' }
  res.send()
})
app.get('/posts/:id', (req, res, next) => {
  const pathname = req.path
  const params = req.params

  console.log(pathname, params) // /posts { id: '1' }
  res.send()
})

// 2、stream流数据
app.use(express.json())
app.post('/posts', (req, res, next) => {
  const pathname = req.path
  const body = req.body // json数据 👀

  console.log(pathname, body) // /posts { title: 'foo', body: 'bar', userId: 1 }
  res.send()
})

// --------------

app.listen(6061)

# 3、内置中间件

  前面我们已经提到了express.json内置中间件,它可以处理 json 格式的请求数据,并将其放在 req.body 中。

  除此之外,还有其他的express 内置中间件 (opens new window)同样值得我们关注:

  • express.urlencoded({extended: false }):处理 x-www-form-urlencoded 格式的请求数据(from 数据现在基本不用了)
  • express.static('./build'): 指定哪些文件夹为静态文件夹(或者说 express 部署静态文件项目)
  • ……
// 使用querystring而不是过时的QS
app.use(express.urlencoded({ extended: true }))

// 给静态资源访问设置一个访问前缀
app.use('/imitation-tencent', express.static(path.join(__dirname, 'build'))) // http://localhost:6061/imitation-tencent/build/bg.png

# 4、响应处理 ✨

逻辑分层

  其实,app.METHOD() 是可以同时注册多个中间件 (opens new window)

const express = require('express')
const app = express()
app.use(express.json())

// --------------

const MTokenHandle = function (req, res, next) {
  // 身份验证
  next()
}
const MDatabaseHandle = function (req, res, next) {
  // 数据库操作
  next()
}

app.post('/posts', [MToken, MDatabaseHandle], (req, res, next) => {
  // 逻辑处理/返回数据
  res.send()
})

// --------------

app.listen(6061)
const express = require('express')
const app = express()

// --------------

app.get('/posts', (req, res, next) => {
  res.status(403)
  res.set('Content-Type', 'multipart/form-data;charset=utf-8')

  // ……

  // res.send()
  res.json({
    code: 1,
    message: '获取文章列表成功',
    data: []
  })
})

// --------------

app.listen(6061)

# 三、特殊中间件

# 1、router 中间件

  -rotuer 中间件 (opens new window) 你可以理解为 mini 版的 app 中间件。它的产生是为了帮助完善 app 中间件,它可以将 存在关联性的逻辑作为一个整体抽离出来。

提示

  Router 中间件是绑定到 express.Router() (opens new window) 的实例上的,而不是绑定在 Application (opens new window) 相关实例上的。

const express = require('express')
const postsRouter =  require('./postsRouter.js')

const app = express()

// posts相关
app.use('/posts', postsRouter)

app.listen(6061)
// postsRouter.js
const express = require('express')

const postsRouter = express().Router()

postsRouter.get('/', (req, res, next) => {})
postsRouter.get('/:id', (req, res, next) => {})
postsRouter.post('/', (req, res, next) => {})
postsRouter.patch('/:id', (req, res, next) => {})
postsRouter.delete('/:id', (req, res, next) => {})

module.exports = postsRouter

# 2、next 函数

  通常情况下,next() 函数是不带参数,用于续接下一个中间件处理

  但是,next() 函数可以传参的,这些参数代表抛出一个错误,参数为错误文本

  • 传递错误信息
app.get('/some-route', (req, res, next) => {
  // 创建一个错误对象并传递给 next()
  const error = new Error('Custom Error')
  next(error)
})

// 错误处理中间件,接收错误对象
app.use((err, req, res, next) => {
  // 处理错误
  res.status(500).json({ error: err.message })
})
  • 传递自定义数据
app.get('/some-route', (req, res, next) => {
  // 传递自定义数据给 next()
  const customData = { message: 'Custom Data' }
  next(customData)
})

// 后续中间件或路由可以访问 customData
app.use((req, res, next) => {
  const data = req.customData
  res.json({ data })
})

# 3、err 中间件

  err 中间件是为了对错误信息进行统一处理而产生的中间件,它必须放在所有要捕捉 err 信息的中间件之后

服务器错误信息 ✍ 返回方案

方案 1:

直接返回 http 状态码

res.status(401).send('未授权访问的信息')

方案 2:

http 状态码全部返回 200,返回的数据中 code 存储 自定义的状态码

res.json({
  code: -10001,
  message: '未授权访问的信息,请检测token'
})

需要注意的是,next() 函数是可以接受任何类型的数据的

next(-1002)
const express = require('express')

const app = express()

// ……
// app.use()
// ……

// 错误处理中间件封装
app.use((err, req, res, next) => {
  // console.error(err.stack)

  const errCode = typeof err === 'number' ? err : 0
  let message = '未知的错误信息'

  switch (errCode) {
    case -1001:
      message = '没有输入用户名和密码'
      break
    case -1002:
      message = '输入用户名和密码错误'
      break
    default:
      break
  }

  res.json({ code: errCode, msg: message })
})

app.listen(6061)

# 四、其他

# 1、官方第三方库

具体参考:Express middleware (opens new window)

  请求日志记录相关的库 morgan:

const express = require('express')
const  { createWriteStream } = require('fs')
const morgan = require('morgan')

const app = express()

const accessLogStream = createWriteStream('./logs/access.log'), { flags: 'a' })
app.use(morgan('combined', { stream: accessLogStream }))

  文件上传相关的库 multer:

其实 multer 也是一个用于处理 multipart/form-data 类型的表单数据的库,而不局限于文件上传

const express = require('express')
const multer = require('multer')
const upload = multer({
  storage: multer.diskStorage({
    destination: function (req, file, cb) {
      cb(null, __dirname + '/tmp/uploads')
    },
    filename: function (req, file, cb) {
      cb(null, Date.now() + '-' + file.originalname) // 自定义文件名
      // cb(null, file.fieldname + '_' + Date.now() + '.' + file.mimetype.split('/')[1])
    }
  })
})

const app = express()

app.post('/profile', upload.single('avatar'), function (req, res, next) {
  // req.file   // req.body
})

  cookie 处理相关的库:cookie-parser

// 前端:
// 🚩document.cookie="username=lencamo"

// 后端:
app.get('/login', (req, res) => {
  // 读取Cookie
  console.log(req.cookies)

  // 设置Cookie
  res.cookie('username', 'lencamo')
  res.send()
})

# 2、模板引擎 ejs

  在没有使用脚手架创建项目时,需要我们使用 app.engine 方法 (opens new window) 手动的指定模板引擎:

设置 port 变量

  其实,与 app.set() 对应的还有一个 app.get() 方法(不是那个 app.METHODS 方法)。

app.set('port', process.env.PORT || 3000)

// ……

app.listen(app.get('port'))
app.set('views', path.join(__dirname, 'views'))
app.set('view engine', 'html')

// 运行 ejs 引擎
app.engine('html', require('ejs').renderFile)
app.get('/about', (req, res) => {
  // 静态页面
  res.sendFile(__dirname + '/views/about.html')

  // 动态页面
  res.render('about', { title: '自我介绍' }) // 使用/views/about.html文件(前面已经配置好了)

  // 重定向
  response.redirect('/hello/anime')
  response.redirect('http://www.example.com')
})

# 3、数据库集成

略 可以参考官方文档 Database integration (opens new window)

更新于 : 8/7/2024, 2:16:31 PM