一. Express简介
Express 是一个极简而灵活的 node.js Web应用框架, 提供了一系列强大特性帮助你创建各种 Web 应用,和丰富的 HTTP 工具。Express中文官网
为什么说Express是一个极简的框架
Express的核心只有两个部分
路由
中间件
Express提供了基本的路由处理和中间件功能, 几乎所有功能的实现都由一个个独立的中间件完成
1 路由
路由可以认为是一种找到数据的路径, 或者说是URL+处理函数
通过URL来区分不同的资源(页面或数据)
通过处理函数来返回资源(页面或数据)
前端通过URL请求数据.
后端通过不同的路由, 调用对应的方法, 返回指定的数据
2 中间件
顾名思义, 中间件就是在什么的中间
在请求和响应中间的处理程序
有时候从请求到响应的业务比较复杂, 将这些复杂的业务拆开成一个个功能独立的函数, 就是中间件
对于处理请求来说,在响应发出之前,可以在请求和响应之间做一些操作,并且可以将这个处理结果传递给下一个函数继续处理
express 中间件函数,帮助拆解主程序的业务逻辑,
并且每一个的中间件函数处理的结果都会传递给下一个中间件函数。
就好比工厂里流水线工人清洗一个箱子:
第一个人清洗侧面,第二个人清洗底面,第三个人清洗顶面,。。。
这条流水线结束后,箱子也就清洗干净了
各做各的,不相互影响,又彼此协作
二. 安装及使用
express也是一个node的包, 可以npm来安装
1 安装
npm i express
2 使用
:::info
步骤
导入express包
实例化对象
编写路由(中间件)
监听端口
示例
// 1. 引入express包
const express = require('express')
// 2. 实例化对象
const app = express()
// 3. 编写路由
app.get('/', function(req, res) {
res.send('hello world')
})
// 4. 监听端口
app.listen(3000)
三. 路由
路由模块由三部分组成
请求方式
URL
处理函数
路由模块主要方法是 app.METHOD
练习
// 需求:
// 1. 当GET请求/或者/index.html时, 返回 '首页'
// 2. 当GET请求/list.html时, 返回 '列表页'
// 3. 当GET请求/detail.html时, 返回 '详情页'
// 4. 其它情况 返回, '404 Not Found'
示例
// 1. 导入express包
const express = require('express')
// 2. 实例化app对象
const app = express()
// 3. 编写路由(根据不同的path, 返回不同的内容)
app.get('/', function (req, res) {
// req: request(请求对象)
// res: response(响应对象)
res.send('首页')
})
app.get('/index.html', function (req, res) {
res.send('首页')
})
app.get('/list.html', function (req, res) {
res.send('列表页')
})
app.get('/detail.html', function (req, res) {
res.send('详情页')
})
app.get('*', function (req, res) {
res.send('404 Not Found')
})
// 4. 监听端口
app.listen(3000, function () {
console.log('server is running on http://localhost:3000')
})
1 请求方式
请求方式就是HTTP协议的请求方式, 常见的有
get: 对应app.get()--查询
post: 对应app.post()--创建(新增)
put: 对应app.put()--修改
delete: 对应app.delete()--删除
2 URL
URL的写法
// 第一种, 不带参数
app.get('/users', function(req, res) {
res.send('hello world')
})
// 第二种, 带参数
app.get('/users/:id', function(req, res) {
res.send('hello world')
})
// 第三种, 正则表达式, 以html结尾
// 请求index.html abc.html
app.get(/.html$/, function(req, res) {
res.send('hello world')
})
3 处理函数
语法
function(req, res, next) {
// todo
}
在处理函数中, 有两个形参
req(请求对象)
res(响应对象)
1) 请求对象
请求对象包含了一次请求中的所有数据(http请求头, 请求参数...)
示例
app.get('/', function (req, res) {
console.log(req)
})
2) 获取请求参数
常见的请求
GET请求: 通过URL
URL的path部分, eg /users/1
URL的queryString部分, eg /users?page=1&size=5
POST请求: 通过body
语法
// 对于GET请求的path传参
req.params
// 对于GET请求的queryString传参
req.query
示例: get请求
// 1.导入express包
const express = require('express')
// 2.实例化app对象
const app = express()
// 3.编写路由
/**
* 获取用户id=1的用户信息
* GET /users/:id
* eg. GET /users/1 返回{id:1, name: 'xiaoming', age: 20}
*/
app.get('/users/:id', (req, res) => {
// 请求对象(包含所有http请求信息)
console.log(req.params)
// 查询数据库, 得到user对象, 返回
res.send({
id: 1,
name: 'xiaoming',
age: 20,
})
})
/**
* 获取所有用户(限定条件: 第1页,每页显示5条)
* GET /users
* 请求参数 urlencoded [?page=1&size=5]queryString
* 响应数据 [{}, {}, {}, {}, {}]
*/
app.get('/users', (req, res) => {
// 通过req.query获取GET请求中的queryString的值
console.log(req.query)
res.send([
{ id: 1, name: 'xiaoming' },
{ id: 2, name: 'xiaomei' },
{ id: 3, name: 'xiaopang' },
])
})
// 4.监听端口
app.listen(3000)
示例: post请求
// 1. 导入express包
const express = require('express')
// 2. 实例化app对象
const app = express()
// 3. 编写路由
/**
* 创建用户
* POST /users
* 请求参数: {name: 'xiaoming', age: 20}
* 响应: {id: 5, name: 'xiaoming', age: 20}
*/
app.post('/users', (req, res) => {
// 解析req中的请求体参数
let postData = ''
req.on('data', (data) => {
// 把所有的post的数据拼接
postData = postData + data
})
req.on('end', () => {
// 当所有的数据接收完成
// 将postData字符串->JSON格式的对象
const obj = JSON.parse(postData)
console.log(obj) // 在后端的控制台中观察(Cmd窗口)
res.send(obj) // 将数据返回客户端(浏览器Network中观察)
})
})
// 4. 监听端口
app.listen(3000)
根据id查找对应的数据并返回
编写post.http
POST http://localhost:3000/
Content-Type: application/json
{
"name": "xiaoming"
}
3) 响应对象
响应对象用于向客户端返回数据, 在处理函数中需要调用以返回数据
常用的有两个
res.send(): 返回各种类型
res.json(): 返回json格式的数据
作业
实现一个商品模块
const db = [
{id: 1, name: 'iphone13', price: '5299', number: '10'},
{id: 2, name: 'MacBookPro M1', price: '11999', number: '20'},
{id: 3, name: 'iPad Pro', price: '4899', number: '30'}
]
写5个接口
获取所有数据
根据id获取单个数据
新增数据
修改数据
删除数据
四. 中间件
中间件的设计模式符合软件工程中的经典模式 CP 模式
Core: 核心
Pulgin: 插件
比如
浏览器, 最核心的是浏览器, 可以通过插件扩展功能
VSCode, 最核心的是代码编辑, 可能通过安装各种插件实现不同的功能
这里的中间件类似于Plugin的作用. 可以通过使用中间件来实现丰富的功能
1 中间件类型
2 应用级中间件
在Express中, 使用app.use或者app.METHOD注册的中间件叫做应用级中间件
:::info
中间件就是一个函数
:::
app.use('path', function (req, res, next) {
next()
})
在中间件中需要通过调用next()执行下一个中间件
如果不执行next(), 也没有调用send(). 这次请求将会被挂起
中间件从影响范围上, 又分为全局中间件和局部中间件
1) 全局中间件
一般, 使用app.use 注册全局中间件
全局中间件: 给每个路由规则都添加中间件处理函数
app.use(function (req, res, next) {
console.log('我是一个全局中间件, 影响所有的路由规则')
})
2) 局部中间件
可以使用app.use或者app.METHOD注册局部中间件
局部中间件: 给某一个特定的路由规则添加中间件处理函数
app.use('/users', function (req, res, next) {
console.log(`这里是一个局部中间件, 只影响/users路由`)
next()
})
示例一
// 不写第一个参数, 给所有访问都注册了一个中间件
app.use(function (req, res, next) {
console.log('Time:', Date.now())
next()
})
示例二
app.use('/user/:id', function (req, res, next) {
console.log('Request Type:', req.method)
next()
})
app.get('/user/:id', function (req, res, next) {
res.send('USER')
})
示例三
可以同时注册多个中间件函数
app.use('/user/:id', function (req, res, next) {
console.log('Request Type:', req.method)
next()
}, function (req, res, next) {
console.log('Request Params:', req.params.id)
next()
})
app.get('/user/:id', function (req, res, next) {
res.send('USER')
})
示例四
app.use除了注册函数做为中间件外, 还可注册一个express.Router()对象
const router = express.Router()
app.use('/user/:id', router)
作业
:::warning
需求
编写一个中间件: 是一个全局中间件, 记录请求的时间 Date.now()
编写一个中间件: 是一个局部中间件, 只有当匹配/users路由时执行. 记录请求的方法
编写一个中间件: 是一个局部中间件, 只有当匹配/users/:id路由时执行. 记录id值
当匹配/users/:id时执行, 返回 '用户的id是XX'
:::
/**
* 全局中间件: app.use(全局中间件)
* 局部中间件: app.use('路由', 局部中间件)
*/
const express = require('express')
const app = express()
// 编写一个中间件: 是一个全局中间件, 记录请求的时间 Date.now()
app.use(function (req, res, next) {
console.log(`当前时间: ${Date.now()}`)
next()
})
// 处理 /users 接口
// 编写一个中间件: 是一个局部中间件, 只有当匹配/users路由时执行. 记录请求的方法
app.use('/users', function (req, res, next) {
console.log(`请求的方法是: ${req.method}`)
next()
})
app.get('/users', function (req, res) {
res.send('用户信息...')
})
// 处理 /users/:id 接口
app.use('/users/:id', function (req, res, next) {
console.log(`请求的id是: ${req.params.id}`)
next()
})
app.get('/users/:id', function (req, res) {
res.send(`用户的id是${req.params.id}`)
})
// app.get(
// '/users/:id',
// function (req, res, next) {
// console.log(`请求的id是: ${req.params.id}`)
// next()
// },
// function (req, res) {
// res.send(`用户的id是${req.params.id}`)
// }
// )
app.listen(3000)
3 路由级中间件
express.Router()对象也可以注册中间件.
使用router.use或者router.METHOD注册的中间件叫做路由级中间件
var app = express()
var router = express.Router()
router.use(function (req, res, next) {
console.log('Time:', Date.now())
next()
})
router.get('/users/', function(req, res) {
res.send('hello')
})
路由级中间件的应用
当路由很多的时候, 如果全部写在app入口会使用文件过大, 不好维护. 可以把不同的路由拆分成多个模块
const express = require('express')
const userRouter = require('./routes/users.js')
const app = express()
app.use(function (req, res, next) {
console.log(req.url)
next()
})
// 加载路由
app.use('/users', userRouter)
app.listen(3000)
const express = require('express')
const router = express.Router()
router.get('/', function(req, res) {
// 返回所有用户信息
const data = [
{id:1, username: 'xiaoming', age: 20},
{id:2, username: 'xiaomei', age: 18},
{id:3, username: 'xiaopang', age: 1},
]
res.json(data)
})
router.get('/:id', function(req, res) {
// 返回所有用户信息
const user = {id:1, username: 'xiaoming', age: 20}
res.json(user)
})
module.exports = router
5 小结
路由级中间件的操作步骤
:::warning
步骤
编写routes/路由文件
导入express包
创建路由对象
编写相关路由
导出路由对象
在入口文件中导入路由对象
注册中间件
:::
五. 数据库操作
1 安装mysql库
参考npm包的官方文档
npm i mysql
2 入门案例
操作数据库, 就是模拟客户端. 向MySQL的服务端发送SQL语句. 基本步骤如下:
引入mysql包
创建数据库连接
连接数据库
执行SQL查询
关闭连接
示例
// 1. 引入mysql包
const mysql = require('mysql')
// 2. 创建数据库连接
const con = mysql.createConnection({
host: 'localhost',
user: 'root',
password: '123456',
database: 'db'
})
// 3. 连接数据库
con.connect()
// 4. 执行查询
con.query('select * from student', function (err, res) {
if (err) throw err
console.log(res)
})
// 5. 关闭连接
con.end()
3 集成到express
示例
获取所有的用户
// 1. 导入express包
const express = require('express')
// 2. 实例化app对象
const app = express()
// 3. 路由
/**
* 获取所有的用户信息
*/
app.get('/users', function (req, res) {
// 导入mysql的包
const mysql = require('mysql')
// 创建连接
const con = mysql.createConnection({
host: '127.0.0.1',
user: 'root',
password: '123456',
database: 'user',
})
// 连接数据
con.connect()
// 执行sql语句
let sql = 'select * from student'
con.query(sql, function (err, data) {
if (err) throw err
// 返回结果
res.send(data)
})
})
// 4. 监听端口
app.listen(3000)
练习
实现 /users/:id根据id获取用户信息
参考答案
/**
* 根据id获取用户信息
* GET /users/:id
*/
app.get('/users/:id', function (req, res) {
// 一. 解析请求数据
const id = req.params.id
// 二. 操作数据库
// 导入mysql的包
const mysql = require('mysql')
// 创建连接
const con = mysql.createConnection({
host: '127.0.0.1',
user: 'root',
password: '123456',
database: 'user',
})
// 连接数据
con.connect()
// 执行sql语句
let sql = `select * from student where id=${id}`
con.query(sql, function (err, data) {
if (err) throw err
// 返回结果
res.send(data)
})
})
我们发现操作数据库部分的代码是重复的.
因此, 我们需要对这部分的内容进行封装(模块化编程的思想)
4 封装mysql
在src下创建db/index.js, 编写如下内容
// 封装数据库的操作
// 1. 导入mysql包
const mysql = require('mysql')
// 2. 创建数据库连接
const con = mysql.createConnection({
host: '127.0.0.1',
port: 3306,
user: 'root',
password: '123456',
database: 'db',
})
// 3. 连接数据库
con.connect()
// 4. 编写操作
/**
* 获取所有数据
* @param {string} sql : 执行的sql语句
* @return {promise}
*/
function getAll(sql) {
return new Promise((resolve, reject) => {
con.query(sql, (err, data) => {
if (err) reject(err)
resolve(data)
})
})
}
/**
* 获取所有数据
* @param {string} sql : 执行的sql语句
* @return {promise}
*/
function getOne(sql) {
return new Promise((resolve, reject) => {
con.query(sql, (err, data) => {
if (err) reject(err)
// data是一个数组
// if (data.length != 0) {
// // 查询到了数据
// resolve(data[0])
// } else {
// resolve(null)
// }
data.length != 0 ? resolve(data[0]) : resolve(null)
})
})
}
/**
* 执行sql语句
* @param {string} sql : 执行的sql语句
* @return {promise}
*/
function exec(sql) {
return new Promise((resolve, reject) => {
con.query(sql, (err, data) => {
if (err) reject(err)
resolve(data)
})
})
}
// 暴露3个方法
module.exports = {
getAll,
getOne,
exec,
}
封装后的操作
// 1. 导入express
const express = require('express')
const { getAll, getOne } = require('./db')
// 2. 实例化app对象
const app = express()
// 3. 编写路由
/**
* GET /users: 获取所有的用户信息
* 返回: [{}, {}, {}]
*/
// app.get('/users', (req, res) => {
// // 完成数据库的操作
// let sql = `select * from users`
// getAll(sql).then((data) => {
// res.send(data)
// })
// })
app.get('/users', async (req, res) => {
// 完成数据库的操作
let sql = `select * from users`
const data = await getAll(sql)
res.send(data)
})
/**
* GET /users/:id 根据id获取单个用户的信息
* 返回: {}
*/
// app.get('/users/:id', (req, res) => {
// // 一. 解析请求参数, id
// const { id } = req.params
// // 二. 操作数据库
// let sql = `select * from users where id=${id}`
// getOne(sql).then((data) => {
// res.send(data)
// })
// })
app.get('/users/:id', async (req, res) => {
// 一. 解析请求参数, id
const { id } = req.params
// 二. 操作数据库
let sql = `select * from users where id=${id}`
const data = await getOne(sql)
res.send(data)
})
// 4. 监听端口
app.listen(3000)
5 使用async...await语法
// 1. 导入express包
const express = require('express')
// process是当前node的进程对象. cwd(current working directory)
// 在node中不推荐使用相对路径, 相对路径 相当于 cwd()
// console.log(process.cwd())
// 导入db(数据库操作的包)
/*
db = {
getAll,
getById,
exec,
}
*/
// const db = require('./db/index')
const { getAll, getById, exec } = require('./db/index')
// 2. 实例化app对象
const app = express()
// 处理请求体的数据. 使用express.json()中间件
app.use(express.json())
// 3. 路由
/**
* 获取所有的用户信息
* GET /users
*/
app.get('/users', async function (req, res) {
// 编写sql语句
let sql = 'select * from student'
// 执行sql语句
// getAll返回一个promise对象. 调用then方法得到data数据
// await等待promise返回结果, 将结果作为表达式的值返回
const data = await getAll(sql)
res.send(data)
})
/**
* 根据id获取用户信息
* GET /users/:id
*/
app.get('/users/:id', async function (req, res) {
// 一. 解析请求数据
const id = req.params.id
// 二. 操作数据库
// 2.1 编写sql语句
let sql = `select * from student where id=${id}`
// 2.2 执行sql语句
const data = await getById(sql)
res.send(data)
})
/**
* 新增用户
* POST /users {name: 'test', age: 20}
*/
app.post('/users', async function (req, res) {
// 一. 解析请求数据
console.log(req.body)
// 对象的解构, 将body对象解构出name和age两个变量
const { name, age } = req.body
// console.log(name, age)
// 二. 操作数据库
// 2.1 编写sql语句(插入3个点: 表, 字段, 值)
let sql = `insert into student (name, age) values ('${name}', ${age})`
// !!!!!!!!!!!!!! 重要调试技巧. 当sql执行出错时, 打印sql, 到控制台执行
console.log(sql)
// 2.2 执行sql语句
const data = await exec(sql)
res.send({
id: data.insertId,
name: name,
age: age,
})
})
/**
* 修改用户
* PUT /users/:id {name:'xiaoming-new', age: 21}
*/
app.put('/users/:id', async function (req, res) {
// 一. 解析请求数据
const id = req.params.id
// 二. 操作数据库
const { name, age } = req.body
// 2.1 编写sql语句(更新4个点 表, 字段, 值, 条件)
let sql = `update student set name='${name}', age=${age} where id=${id}`
// 2.2 执行sql语句
await exec(sql)
res.send({
id: id,
name: name,
age: age,
})
})
/**
* 删除用户
* DELETE /users/:id
*/
app.delete('/users/:id', async function (req, res) {
// 一. 解析请求数据
const id = req.params.id
// 二. 操作数据库
// 2.1 编写sql语句
let sql = `delete from student where id=${id}`
// 2.2 执行sql语句
await exec(sql)
res.status(204).send('')
})
// 4. 监听端口
app.listen(3000)