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()将控制权传递给下一个中间件功能,否则,请求将被挂起
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 等
- express主要提供了两种方式:
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!')