1 Express认识初体验
2 Express中间件使用
3 Express请求和响应
4 Express路由的使用
5 Express的错误处理
6 Express的源码解析
一、手动创建express的过程:
1、在项目文件的根目录创建package.json文件
npm init
2、下载express
npm install express
3、基本使用
const express = require('express')// 1.创建express的服务器 const app = express()// 客户端访问URL: /login和/home // 这里的/login只能有POST方法请求,/home只能由GET方法请求 app.post('/login', (req, res) => {// 处理login请求res.end('登录成功, 欢迎回来~') })app.get('/home', (req, res) => {res.end('首页的轮播图/推荐数据列表~') })// 2.启动服务器, 并且监听端口 app.listen(9000, () => {console.log('express服务器启动成功~') })
4、运行服务器
先要下载nodemon
nodemon xxx.js
二、普通中间件的使用
express认识中间件
中间件在express就是post或者get里面的回调函数(req,res,next){}和app.use()都是中间件。
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服务器启动成功~') })
middleware-注册普通的中间件
普通中间件就是 app.use((req,res,next){ })
多个普通中间件只会执行第一个,除非在第一个普通中间件里面加next()。
先要结束一个中间件就调用app.end(xxx) 类似:app.use((req,res,next){ app.end(xxx) })
普通中间件是不会验证是什么请求的,只要有客户端请求服务器,如果普通中间件放在外层就会直接被执行一次。
const express = require('express')const app = express()// 总结: 当express接收到客户端发送的网络请求时, 在所有中间中开始进行匹配 // 当匹配到第一个符合要求的中间件时, 那么就会执行这个中间件 // 后续的中间件是否会执行呢? 取决于上一个中间件有没有执行next// 通过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服务器启动成功~') })
middleware-注册有路径匹配的普通中间件
可以限制请求的路径(locallhost:xxx/home),但是不能限制请求的方式(get、post)
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服务器启动成功~') })
middleware-注册路径-方法匹配的中间件
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服务器启动成功~') })
middleware-注册多个中间件
app.get(路径, 中间件1, 中间件2, 中间件3)中间件里面可以放很多的中间件。但是需要都执行的话就得添加next(),如果一个next()都不写那就只会执行一次,写一个就多执行一个中间件。入下代码所示。
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服务器启动成功~') })
这么多中间件的作用就是下图这样子使用
middleware-中间件匹配练习
做一个请求路径是不存在的,并且下面代码的中间件都有next(),所以会执行4个use的普通中间件。
这个路径存在,所以,最后会执行5个中间件(4个use的和一个匹配路径的)。
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服务器启动成功~') })
middleware-中间件案例练习
输入用户名和密码
const express = require('express')const app = express()// 注册两个实际请求的中间件 // 案例一: 用户登录的请求处理 /login post => username/password app.post('/login', (req, res, next) => {// 1.获取本次请求过程中传递过来的json数据let isLogin = falsereq.on('data', (data) => {const dataString = data.toString()const dataInfo = JSON.parse(dataString)if (dataInfo.username === 'coderwhy' && 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 = falsereq.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服务器启动成功~') })
middleware-中间件案例练习(重构)
这里用到了中间件的特性,第一个app.use()普通中间件直接就把客户端发送上来的json放到了req.body里面了,后续两个路径匹配的中间件就可以直接使用req.body来获取客户端发送上来的json消息了。没重构之前更繁琐。
第一个注释掉的use普通中间件可以被express提供的中间件取代掉app.use(express.json())。使用的时候还是使用req.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服务器启动成功~') })
中间件应用-urlencoded解析
这里指的是我们可以设置服务器获取的格式,如果客户端发送的是别的类型的,我们可以使用urlencoded解析。
一般发过来的是row的json格式的参数。
下图的这个数据app.use(express.json())是没法解析到req.body里面的。需要使用app.use(express.urlencoded({ extended: true }))来解析到req.body里面的。
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服务器启动成功~') })
中间件应用-请求日志记录
使用express提供的中间件其实还是不太够用,所以我们还需要学会使用其他库给的中间件,比如说morgan,具体使用信息看最下方上课的PPT。
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服务器启动成功~') })
写入log的记录信息
中间件应用-单个文件上传---一般使用formdata格式上传文件
这个第三方中间件叫multer
npm install multer
这个中间件是放在中间件里面使用的,
配置multer的这段代码可以设置把文件放在哪个文件夹里面。
const upload = multer({
dest: './uploads'
})
下面这段代码:upload.single('avatar')就是帮我们解析文件的第三方中间件的用法,'avatar'是客户端上传的key名称。解析完的文件会放在req.file里面。
app.post('/avatar', upload.single('avatar') , (req, res, next) => {
console.log(req.file)
res.end('文件上传成功~')
})
可以上传图片,但是没有后缀名,后面一节多文件上传有提到如何给文件添加相应的后缀名。
const express = require('express') const multer = require('multer')// 创建app对象 const app = express()// 应用一个express编写第三方的中间件 const upload = multer({dest: './uploads' })// 编写中间件 // 上传单文件: singer方法 app.post('/avatar', upload.single('avatar') , (req, res, next) => {console.log(req.file)res.end('文件上传成功~') })// 启动服务器 app.listen(9000, () => {console.log('express服务器启动成功~') })
中间件应用-多个文件上传---一般使用formdata格式上传文件
由于单纯使用dest的话,文件没有后缀名,打不开文件,于是我们得用另外一种写法给文件添加后缀名字。
const upload = multer({
// dest: './uploads'
storage: multer.diskStorage({
destination(req, file, callback) {//存放文件的位置
callback(null, './uploads')
},
filename(req, file, callback) {//从这里开始给文件设置一个时间戳_文件名字.文件后缀的文件
callback(null, Date.now() + '_' + file.originalname)
}
})
})
实现多文件上传,包括多个一个key对应一个文件的上传和一个key对应上传多个文件的情况。
//这里用到是multer的array的数组形式,而且需要从req.files拿到文件信息
app.post('/photos', upload.array('photos'), (req, res, next) => {
console.log(req.files)
res.end('上传多张照片成功~')
})
const express = require('express') const multer = require('multer')// 创建app对象 const app = express()// 应用一个express编写第三方的中间件 //下面这段是为了设置存放文件的文件夹和给文件添加后缀 const upload = multer({// dest: './uploads'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('文件上传成功~') })// 上传多文件: array方法 app.post('/photos', upload.array('photos'), (req, res, next) => {console.log(req.files)res.end('上传多张照片成功~') })// 启动服务器 app.listen(9000, () => {console.log('express服务器启动成功~') })
中间件应用-formdata解析
一般我吗需要客户端向服务器传递的信息格式是json格式,但是也有可能是form-data的格式
这个时候需要使用const formdata = multer()来解决,语法也是用在另外一个中间件里面
// formdata.any()表示可以解析formdata的所有内容
app.post('/login', formdata.any(), (req, res, next) => {
console.log(req.body)
res.end('登录成功, 欢迎回来~')
})
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服务器启动成功~') })
客户端参数解析-其他方式
get方法有两种发送方法,一种是querystring,后面加问号和参数的;另外一个是params的动态路由的参数,这种一般因为是路径,所以不能定死路径。 express默认对get的这两种方法都有解析。
const express = require('express')// 创建app对象 const app = express()// 编写中间件 // 1.解析queryString app.get('/home/list', (req, res, next) => {// offset/sizeconst queryInfo = req.queryconsole.log(queryInfo) //由于客户端上传这些信息肯定都是字符串的,我们可以设置一下参数类型为数字类型 Number(queryInfo.offset)res.end('data list数据') })// 2.解析params参数 动态路由,id可以是任何参数 app.get('/users/:id', (req, res, next) => {const id = req.params.idres.end(`获取到${id}的数据~`) })// 启动服务器 app.listen(9000, () => {console.log('express服务器启动成功~') })
三、Express响应参数res
服务器返回客户端数据的方式可以是res.on()也可以是res.json()
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服务器启动成功~') })
四、Express路由的使用
express中路由的使用方式
这是单独创建了一个关于/user匹配路径的各种请求的抽取,可以写在一起,也可以单独放在一个映射文件里面。
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服务器启动成功~') })
后端路由也可以单独放在一个映射文件里面:
userRouter.js的代码:
const express = require('express')// 1.创建路由对象
const userRouter = express.Router()// 2.定义路由对象中的映射接口
userRouter.get('/', (req, res, next) => {res.json('用户列表数据')
})
userRouter.get('/:id', (req, res, next) => {const id = req.params.idres.json('某一个用户的数据:' + id)
})
userRouter.post('/', (req, res, next) => {res.json('创建用户成功')
})
userRouter.delete('/:id', (req, res, next) => {const id = req.params.idres.json('删除某一个用户的数据:' + id)
})
userRouter.patch('/:id', (req, res, next) => {const id = req.params.idres.json('修改某一个用户的数据:' + id)
})// 3.将路由导出
module.exports = userRouter
使用router的文件代码:
const express = require('express')
const userRouter = require('./router/userRouter')// 创建app对象
const app = express()// 让路由生效
app.use('/users', userRouter)// 启动服务器
app.listen(9000, () => {console.log('express服务器启动成功~')
})
express的静态资源服务器
这样子可以实现使用网址就可以拿到存在后端文件夹里面的静态资源了,比如图片等等。
静态资源可以放图片文件夹,甚至还可以放包管理器打包好的文件夹(在打开网址的时候,如果只输入比如:http://localhost:9000这样子的话是相当于访问http://localhost:9000/index.html的,这个时候浏览器就会自动下载这个index.js的依赖文件。)
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服务器启动成功~') })
express中错误处理的方案
就是客户端发送的东西没有权限或者有误时候服务器要怎么回复客户端。有两种方式:
利用next(参数)来跳转到有err参数的普通中间件里面
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 !== 'coderwhy' || password !== '123456') {next(-1002)} else {res.json({code: 0,message: '登录成功, 欢迎回来~',token: '323dfafadfa3222'})} })// 错误处理的中间件 app.use((errCode, req, res, next) => {const code = errCodelet message = '未知的错误信息'switch(code) {case -1001:message = '没有输入用户名和密码'breakcase -1002:message = '输入用户名或密码错误'break}res.json({ code, message }) })// 启动服务器 app.listen(9000, () => {console.log('express服务器启动成功~') })