本篇文章将带你深入了解 Express 框架,从安装配置到如何使用中间件、路由、请求响应、错误处理等,再到源码层面的解析,帮助你全方位掌握 Express。
一、Express 认识初体验
1. 什么是 Express?
Express 是一个简洁且灵活的 Web 应用框架,基于 Node.js,帮助开发者快速搭建 Web 应用程序和 RESTful API。其最大的特点是轻量级和高度可扩展,广泛应用于构建小型到中型的 Web 应用。
Express 提供了大量的中间件,方便处理 HTTP 请求、响应,简化了服务器端开发的复杂度。
Express 是 Node.js 最流行的 Web 框架,基于中间件架构设计,提供以下核心功能:
- 路由管理:灵活处理 HTTP 请求
- 中间件支持:模块化处理请求生命周期
- 模板引擎集成:支持 Pug/EJS 等视图渲染
- 错误处理:统一错误捕获机制
2. 安装 Express
首先,我们需要初始化一个 Node.js 项目并安装 Express。 也可以使用Express 应用程序生成器
npm init -y # 初始化一个 Node.js 项目
npm install express --save # 安装 Express 框架
安装完成后,在项目根目录下会生成 node_modules
文件夹和 package.json
文件。
3. 创建一个简单的服务器
通过 Express 创建一个简单的 Web 服务器,监听 HTTP 请求。
const express = require('express');
const app = express();
app.get('/', (req, res) => {
res.send('Hello, Express!');
});
app.get('/login', (req, res) => {
//处理loginc操作
res.send('登录成功,太好了!');
});
app.listen(3000, () => {
console.log('Server is running at http://localhost:3000');
});
在浏览器中访问 http://localhost:3000
,你会看到页面显示 "Hello, Express!"。
二、Express 中间件使用
1. 什么是中间件?
中间件是 Express 中最核心的概念之一,它是指在请求和响应周期之间的处理逻辑。每个中间件可以执行以下任务:
- 修改请求对象(req)或响应对象(res)
- 执行一些代码
- 结束请求响应循环
- 调用下一个中间件(next函数(在express中定义的用于执行下一个中间件的函数))
执行顺序:按注册顺序依次执行
功能分类:
const express = require('express');
const app = express();
// 给express创建的app传入一个回调函数
// 传入的这个回调函数就称之为是中间件(middleware)
// 如果当前中间件功能没有结束请求-响应周期,则必须调用next()将控制权传递给下一个中间件功能,否则,请求将被挂起
app.use((req, res, next) => {
console.log('中间件被调用了');
next();
});
app.post('/login', (req, res, next) => {
// 中间件中可以执行任意代码,打印,查询数据,判断逻辑
// 在中间件中修改reqres对象
// 在中间件中结束响应周期
console.log('中间件被调用了登录');
// res.send('登录成功,太好了!');
res.json({ code: 0, msg: '登录成功' })
// next();
});
app.listen(3000, () => {
console.log('Server is running at http://localhost:3000');
});
2. 使用中间件
使用中间件的基本语法如下:
app.use((req, res, next) => {
console.log(`${req.method} ${req.url}`);
next(); // 调用下一个中间件
});
// 通过use方法注册的中间件是最普通的1简单的中间件
// 通过use注册的中间件,无论是什么请求方式都可以匹配上
app.use((req, res, next) => {
console.log("中间件1")
next()
})
app.use((req, res, next) => {
console.log("中间件2")
res.json({ message: '测试中间件返回' })
})
总结:
- 当express接收到客户端发送的网络请求时,在所有中间中开始进行匹配
- 当匹配到第一个符合要求的中间件时,那么就会执行这个中间件
- 后续的中间件是否会执行,取决于上一个中间件有没有执行next
3. 常见中间件类型
- 应用级中间件:作用于所有请求或特定路由,如
app.use()
或app.get()
注册。 - 路由级中间件:通过
express.Router()
创建,限定在特定路由上使用。 - 内置中间件:Express 提供了一些内置中间件,如
express.json()
、express.static()
等。 - 错误处理中间件:用于捕捉并处理应用中的错误。
4. 例子:静态文件中间件
Express 提供了 express.static
中间件用于处理静态资源(如 HTML、CSS、JS 文件等):
app.use(express.static('public')); // 将 public 文件夹作为静态资源目录
中间件开发实战
多种中间件基础使用
//...
// 注册普通的中间件
// app.use((req,res,next)=>{
// console.log('普通通用,中间件1');
// res.end('--------');
// })
// 注册路径匹配的中间件
// 路径匹配的中间件是不会对请求方式(method)进行限制
// app.use("/home",(req,res,next)=>{
// console.log('home接口中间件');
// res.end('home接口返回');
// })
// 注册中间件:对path/method都有限制
// app.get("/home",(req,res,next)=>{
// console.log('home接口和请求方式中间件');
// res.end('home接口和请求方式方式返回');
// })
// 注册多个中间件,可以持续后面接中间件。
//通常用在复杂接口操作数据中进行分割局部完成事件使用,避免放在同一个中间件里
app.get("/home",(req,res,next)=>{
console.log('home get中间件1');
next();
},(req,res,next)=>{
console.log('home get中间件2');
})
//....
参数解析实例
// 应用中间件解析参数
app.use(express.json());//解析客户端传递过来的json
// 1.解析传递过来urlencoded的时候,,默认使用的node内置querystring模块
//{extended:true}:不再使用内置的querystring,而是使用qs第三方库
app.use(express.urlencoded({ extended: false }));//解析客户端传递过来的urlencoded
app.post("/login", (req, res) => {
console.log(req.body)
res.send("登录成功")
})
formData参数解析
const formdata = multer()
app.post("/login",formdata.any(), (req, res) => {
console.log(req.body)
res.send("登录成功")
})
// 自定义日志中间件
const logger = (req, res, next) => {
const start = Date.now();
res.on('finish', () => {
const duration = Date.now() - start;
console.log(`${req.method} ${req.url} - ${res.statusCode} [${duration}ms]`);
});
next();
};
// 应用中间件
app.use(logger);
app.use(express.json()); // 内置JSON解析
上传文件实例
const express = require('express');
const multer = require('multer');
const path = require('path');
const fs = require('fs');
const cors = require('cors');
const app = express();
app.use(cors()); // 跨域支持
const uploadDir = 'uploads';
if (!fs.existsSync(uploadDir)) {
fs.mkdirSync(uploadDir);
}
// 设置文件存储路径和文件名
const storage = multer.diskStorage({
destination: (req, file, cb) => {
cb(null, uploadDir); // 保存路径
},
filename: (req, file, cb) => {
const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9);
const ext = path.extname(file.originalname);
cb(null, file.fieldname + '-' + uniqueSuffix + ext);
}
});
const upload = multer({ storage: storage });
app.post("/avatar", upload.single('file'), (req, res) => {
console.log(req.file);
res.send({
message: '上传成功',
filename: req.file.filename,
url: `/uploads/${req.file.filename}`
});
});
// 静态资源托管(访问图片用)
app.use('/uploads', express.static(path.join(__dirname, 'uploads')));
app.listen(3000, () => {
console.log('Server is running at http://localhost:3000');
});
多文件上传只需用 upload.array() 替换 upload.single(),其余部分变化不大
// 使用express内置中间件
app.post("/avatar", upload.array('file',3), (req, res, next) => {
console.log(req.files); // 注意是 req.files(数组)
const fileInfos = req.files.map(file => ({
filename: file.filename,
url: `/uploads/${file.filename}`
}));
res.send({
message: '上传成功',
files: fileInfos
});
})
三、Express 请求和响应
1. 获取请求数据
Express 提供了丰富的 API 来处理不同类型的请求数据:
req.query
:获取 URL 查询参数。req.body
:获取请求体数据(需配合express.json()
或express.urlencoded()
中间件)。req.params
:获取 URL 路由参数。
app.use(express.json()); // 解析 JSON 格式的请求体
app.post('/user', (req, res) => {
console.log(req.body); // 打印请求体
res.send('User received');
});
2. 设置响应数据
Express 的 res
对象提供了多种方法来设置响应内容:
res.send()
:发送普通文本或 HTML 内容。res.json()
:发送 JSON 格式的数据。res.status()
:设置 HTTP 状态码。
app.get('/user', (req, res) => {
res.status(200).json({ name: 'Express', age: 5 });
});
3. 设置响应头
有时我们需要设置一些自定义的响应头,Express 也提供了简便的方式:
res.set('X-Custom-Header', 'MyValue');
四、Express 路由的使用
1. 定义基本路由
Express 提供了简单的路由定义方法,如 app.get()
、app.post()
、app.put()
等:
app.get('/users', (req, res) => {
res.send('User list');
});
app.post('/users', (req, res) => {
res.send('Create user');
});
2. 路由参数
我们可以使用路由参数来捕获动态数据:
app.get('/user/:id', (req, res) => {
const userId = req.params.id;
res.send(`User ID: ${userId}`);
});
访问 /user/123
会返回 User ID: 123
。
解析querystring
app.get('/user', (req, res) => {
const queryInfo = req.query;
console.log("🚀 ~ app.get ~ queryInfo:", queryInfo)
res.send(queryInfo);
});
其他参数解析参考上文中间件的操作使用
3. 使用 Router 模块化路由
为了更好地组织代码,可以使用 express.Router()
来创建模块化的路由。
const userRouter = express.Router();
//路由映射可以抽取为单独文件夹导入
userRouter.get('/', (req, res) => res.send('User Home'));
userRouter.get('/info', (req, res) => res.send('User Info'));
app.use('/user', userRouter); // 将路由挂载到 /user 路径下
五、Express 的错误处理
1. 错误处理中间件
错误处理中间件是处理应用程序错误的关键。其特殊之处在于,它接受四个参数:err
, req
, res
, next
。
app.use((err, req, res, next) => {
console.error(err.stack); // 打印错误栈
res.status(500).send('Something broke!');
});
实际使用实例
const express = require('express');
const app = express();
// 支持 JSON 和表单数据
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
// 登录接口
app.post('/login', (req, res, next) => {
const { name, password } = req.body;
// 校验用户名和密码
if (!name || !password) {
return next(new Error('请输入用户名或密码'));
}
if (name !== "linxi" || password !== "123456") {
const err = new Error('用户名或密码错误');
err.status = 400;
return next(err);
}
// 登录成功
res.status(200).json({
code: 200,
msg: '登录成功',
token: "sadjanb4qwesada5"
});
});
// 统一错误处理
app.use((err, req, res, next) => {
res.status(err.status || 500).json({
code: err.status || 500,
msg: err.message || '服务器内部错误'
});
});
// 启动服务
app.listen(3000, () => {
console.log('Server is running at http://localhost:3000');
});
2. 抛出错误
我们可以通过 next(err)
手动抛出错误,并交由错误处理中间件处理。
app.get('/', (req, res, next) => {
const error = new Error('Oops! Something went wrong');
next(error); // 将错误传递给错误处理中间件
});
3. 捕捉异步错误
对于异步操作的错误,我们可以通过 async/await
和 try/catch
来捕获异常。
app.get('/async', async (req, res, next) => {
try {
const data = await someAsyncOperation();
res.json(data);
} catch (err) {
next(err);
}
});
4. 日志记录
使用morgan npm install morgan
搭配了fs去写出日志文件,不明白的移步学习fs可写流
const fs = require('fs')
const express = require('express');
const morgan = require('morgan')
const app = express();
// 应用第三方中间件
const writeStream = fs.createWriteStream('./log.txt', { flags: 'a' })
app.use(morgan('combined', { stream: writeStream }))
app.post("/login", (req, res) => {
res.send("登录成功")
})
app.listen(3000, () => {
console.log('Server is running at http://localhost:3000');
});
六、Express 源码解析
核心架构剖析
1. 项目结构
Express 的核心代码位于 lib/
目录下。最重要的文件是 express.js
,它定义了 Express 应用的核心逻辑。其主要功能是返回一个可以处理请求和响应的应用实例。
通过 createApplication() 函数返回一个应用实例。该函数会在内部初始化中间件、路由和错误处理机制。
module.exports = function createApplication() {
const app = function(req, res, next) {
app.handle(req, res, next); // 请求处理
}
app.handle = function(req, res, next) {
// 处理请求并调用路由或中间件
};
return app;
}
2. 路由和中间件的管理
在 Express 中,中间件是按顺序执行的。每个中间件执行后,必须调用 next(),否则请求会停留在当前中间件中,无法传递给下一个中间件。
Express 通过 Router
模块来管理路由和中间件。在 app.use()
或 app.get()
方法中注册的中间件都会被添加到 app._router
中。
app.use = function use(fn) {
this._router.use(fn); // 注册中间件
return this;
}
3. 请求与响应对象
Express 会对 req
和 res
对象进行封装,提供更多有用的方法来处理请求和响应。req
中包含了 URL、请求头、请求体等信息;res
对象则提供了 send()
、json()
、status()
等方法来处理响应。
4.由的匹配机制
Express 在内部通过 router.match() 方法进行路由匹配。当请求到来时,框架会遍历所有注册的路由,直到找到第一个匹配的路由为止。