# 一、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.path
、req.query
、req.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、官方第三方库
请求日志记录相关的库 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)