Express中文官网:https://www.expressjs.com.cn/
一、安装并初识Express
1、初始化一个npm项目:
npm init -y
2、安装express:
npm install express # 指定版本: npm install express@4.18.2
3、编写一个入门案例app.js:
const express = require('express');
const app = express();
const port = 3000;
app.get("/", (req, res) => {
res.send("Hello World!");
})
app.get("/user", (req, res) => {
res.send("这是/user路径的返回")
})
app.listen(port, () => {
console.log(`Server running at http://localhost:${port}/`);
})
4、运行app.js:
node app.js # 动态运行app.js,当有文件变动时,能够立即生效 nodemon app.js
5、此时访问 http://localhost:3000

如此简单,一个后端Server服务就启动起来了!
二、一个简单的TODO案例,练习路由
1、简单路由设计:
const express = require('express');
const app = express();
const port = 3000;
app.get("/todos", (req, res) => {
res.send("get /todos");
})
app.get("/todos/:id", (req, res) => {
res.send(`get /todos/${req.params.id}`);
})
app.post("/todos", (req, res) => {
res.send("post /todos");
})
app.patch("/todos/:id", (req, res) => {
res.send(`patch /todos/${req.params.id}`);
})
app.delete("/todos/:id", (req, res) => {
res.send(`delete /todos/${req.params.id}`);
})
app.listen(port, () => {
console.log(`Server running at http://localhost:${port}/`);
})
2、现在没有DB数据库,我们就先把数据存在本地的一个json文件中db.json:
{
"todos": [
{
"id" : 1,
"title" : "吃饭"
},
{
"id" : 2,
"title" : "睡觉"
},
{
"id" : 3,
"title" : "写代码"
}
]
}
3、在app.js中使用fs模块,进行db.json文件的操作,封装一个工具类db.js:
const fs = require('fs')
const { promisify } = require('util')
const path = require('path')
//将callBack形式的异步API,转化为promise形式, promise形式可以防止回调地狱
const readFile = promisify(fs.readFile)
const writeFile = promisify(fs.writeFile)
const dbPath = path.join(__dirname, './db.json')
exports.getDb = async () => {
const data = await readFile(dbPath, 'utf8')
return JSON.parse(data)
}
exports.saveDb = async db => {
// 最后的两个参数可以让写入 db.json 的数据可读性更好
const data = JSON.stringify(db, null, ' ')
await writeFile(dbPath, data)
}
4、整个增删改查逻辑代码如下app.js:
const express = require('express')
const fs = require('fs')
const { getDb, saveDb } = require('./db.js')
const app = express()
// /配置允许解析表单请求体application/json
app.use(express.json())
// 配置允许解析表单请求体application/x-www-form-urlencoded
app.use(express.urlencoded())
const port = 3000
app.get("/todos", async (req, res) => {
try {
const db = await getDb()
return res.status(200).json(db.todos)
} catch ( err ) {
return res.status(500).json({
error: err.message
})
}
})
app.get("/todos/:id", async (req, res) => {
try {
const db = await getDb()
const todo = db.todos.find(todo => todo.id === Number.parseInt(req.params.id))
if (!todo) {
return res.status(404).end()
}
return res.status(200).json(todo)
} catch (err) {
return res.status(500).json({
error: err.message
})
}
})
app.post("/todos", async (req, res) => {
try {
// 1.获取请求体数据
const todo = req.body
// 2.验证数据
if (!todo.title) {
return res,status(422).json({
error : 'The field title is required'
})
}
// 3.数据验证通过,保存数据
const db = await getDb()
const lastTodo = db.todos[db.todos.length -1]
todo.id = lastTodo ? lastTodo.id + 1 : 1
db.todos.push(todo)
await saveDb(db)
return res.status(200).json(todo)
} catch (err) {
return res.status(500).json({
error: err.message
})
}
})
app.patch("/todos/:id", async (req, res) => {
try {
// 1.获取请求体数据
const todo = req.body
// 2.查找到对应的数据
const db = await getDb();
const oldTodo = db.todos.find(todo => todo.id === Number.parseInt(req.params.id))
if (!oldTodo) {
return res.status(404).end();
}
// 3.合并todo,保存数据
Object.assign(oldTodo, todo)
await saveDb(db)
return res.status(200).json(oldTodo)
} catch (err) {
return res.status(500).json({
error: err.message
})
}
})
app.delete("/todos/:id", async (req, res) => {
try {
// 1.获取请求体数据
const todo = req.body
// 2.查找到对应的数据的索引
const db = await getDb();
const index = db.todos.findIndex(todo => todo.id === Number.parseInt(req.params.id))
if (index === -1) {
return res.status(404).end()
}
// 3.删除对应索引的数据
db.todos.splice(index, 1)
await saveDb(db)
return res.status(204).end()
} catch (err) {
return res.status(500).json({
error: err.message
})
}
})
app.listen(port, () => {
console.log(`Server running at http://localhost:${port}/`)
})
三、Express中的中间件

1、简单案例:对所有的请求进行访问日志打印(中间件的引入):
const express = require('express')
const fs = require('fs')
const { getDb, saveDb } = require('./db.js')
const app = express()
// /配置允许解析表单请求体application/json
app.use(express.json())
// 配置允许解析表单请求体application/x-www-form-urlencoded
app.use(express.urlencoded())
// 编写我们自己的中间件,实现日志功能
app.use((req, res, next) => {
console.log(req.method, req.url, Date.now())
next()
})
const port = 3000
app.get("/", async (req, res) => {
res.send('/get /')
})
app.get("/user", async (req, res) => {
res.send('/get user')
})
app.post("/save", async (req, res) => {
res.send('/post save')
})
app.put("/update", async (req, res) => {
res.send('/put update')
})
app.delete("/remove", async (req, res) => {
res.send('/delete remove')
})
app.listen(port, () => {
console.log(`Server running at http://localhost:${port}/`)
})
日志显示结果如下:
[nodemon] starting `node app.js` Server running at http://localhost:3000/ GET /user 1686823299996 POST /save 1686823307997 PUT /update 1686823314216 DELETE /remove 1686823319492
经过上面的实现,我们发现Nodejs中的中间件,与JAVA中的AOP思想很像;
如果有多个中间件,那么就会按照顺序,自上而下地依次执行,中间件中一定要记得通过next()将请求传递给下一个中间件!
中间件use的位置非常重要,如果在某个路由之后使用中间件,那么这个路由对应的请求就不会进入这个中间件!
2、Express中的中间件函数:
在Express中,中间件就是一个可以访问请求对象、响应对象和调用next方法的一个函数;

我们可以看到,在Router路由中,也可以使用中间件函数,将请求处理完成后,交给后续的中间件/路由进行处理;
【注意】如果当前的中间件功能没有结束请求-响应周期,则必须调用next()将控制全传递给下一个中间件功能。否则,该请求将被挂起!
3、Express中中间件的分类:
-
应用程序级别中间件:
-
路由级别中间件:
-
错误处理中间件:
-
内置中间件
-
第三方中间件
Express官方罗列的常见的第三方中间件列表:https://www.expressjs.com.cn/resources/middleware.html

