一文搞懂 Express:使用、路由、中间件与源码

本篇文章将带你深入了解 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 应用程序生成器

bash 复制代码
npm init -y        # 初始化一个 Node.js 项目
npm install express --save  # 安装 Express 框架

安装完成后,在项目根目录下会生成 node_modules 文件夹和 package.json 文件。

3. 创建一个简单的服务器

通过 Express 创建一个简单的 Web 服务器,监听 HTTP 请求。

js 复制代码
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中定义的用于执行下一个中间件的函数))

执行顺序:按注册顺序依次执行

功能分类

js 复制代码
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. 使用中间件

使用中间件的基本语法如下:

js 复制代码
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 文件等):

js 复制代码
app.use(express.static('public'));  // 将 public 文件夹作为静态资源目录

中间件开发实战

多种中间件基础使用

js 复制代码
//...
// 注册普通的中间件
// 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');
})
//....

参数解析实例

js 复制代码
// 应用中间件解析参数
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参数解析

js 复制代码
const formdata = multer()
app.post("/login",formdata.any(), (req, res) => {
  console.log(req.body)
  res.send("登录成功")
})
javascript 复制代码
// 自定义日志中间件
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解析

上传文件实例

js 复制代码
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(),其余部分变化不大

js 复制代码
// 使用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 路由参数。
js 复制代码
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 状态码。
js 复制代码
app.get('/user', (req, res) => {
  res.status(200).json({ name: 'Express', age: 5 });
});

3. 设置响应头

有时我们需要设置一些自定义的响应头,Express 也提供了简便的方式:

js 复制代码
res.set('X-Custom-Header', 'MyValue');

四、Express 路由的使用

1. 定义基本路由

Express 提供了简单的路由定义方法,如 app.get()app.post()app.put() 等:

js 复制代码
app.get('/users', (req, res) => {
  res.send('User list');
});

app.post('/users', (req, res) => {
  res.send('Create user');
});

2. 路由参数

我们可以使用路由参数来捕获动态数据:

js 复制代码
app.get('/user/:id', (req, res) => {
  const userId = req.params.id;
  res.send(`User ID: ${userId}`);
});

访问 /user/123 会返回 User ID: 123

解析querystring

js 复制代码
app.get('/user', (req, res) => {
  const queryInfo = req.query;
  console.log("🚀 ~ app.get ~ queryInfo:", queryInfo)
  res.send(queryInfo);
});

其他参数解析参考上文中间件的操作使用

3. 使用 Router 模块化路由

为了更好地组织代码,可以使用 express.Router() 来创建模块化的路由。

js 复制代码
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

js 复制代码
app.use((err, req, res, next) => {
  console.error(err.stack);  // 打印错误栈
  res.status(500).send('Something broke!');
});

实际使用实例

js 复制代码
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) 手动抛出错误,并交由错误处理中间件处理。

js 复制代码
app.get('/', (req, res, next) => {
  const error = new Error('Oops! Something went wrong');
  next(error);  // 将错误传递给错误处理中间件
});

3. 捕捉异步错误

对于异步操作的错误,我们可以通过 async/awaittry/catch 来捕获异常。

js 复制代码
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可写流

js 复制代码
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() 函数返回一个应用实例。该函数会在内部初始化中间件、路由和错误处理机制。

js 复制代码
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 中。

js 复制代码
app.use = function use(fn) {
  this._router.use(fn);  // 注册中间件
  return this;
}

3. 请求与响应对象

Express 会对 reqres 对象进行封装,提供更多有用的方法来处理请求和响应。req 中包含了 URL、请求头、请求体等信息;res 对象则提供了 send()json()status() 等方法来处理响应。

4.由的匹配机制

Express 在内部通过 router.match() 方法进行路由匹配。当请求到来时,框架会遍历所有注册的路由,直到找到第一个匹配的路由为止。

相关推荐
Lemon程序馆10 分钟前
今天聊聊 Mysql 的那些“锁”事!
后端·mysql
龙卷风040513 分钟前
使用本地IDEA连接服务器远程构建部署Docker服务
后端·docker
vv安的浅唱17 分钟前
Golang基础笔记七之指针,值类型和引用类型
后端·go
陪我一起学编程28 分钟前
MySQL创建普通用户并为其分配相关权限的操作步骤
开发语言·数据库·后端·mysql·oracle
xptwop1 小时前
05-ES6
前端·javascript·es6
Heo1 小时前
调用通义千问大模型实现流式对话
前端·javascript·后端
JS_Love1 小时前
nodejs 手动实现 multipart/byteranges 分块下载
node.js
Java水解2 小时前
RabbitMQ用法的6种核心模式全面解析
后端·rabbitmq
用户4099322502122 小时前
FastAPI的查询白名单和安全沙箱机制如何确保你的API坚不可摧?
前端·后端·github