既然我们已经在纯 Node.js 中学会了如何连接和操作 MySQL,现在是时候将这个能力集成到 Express 应用中了。在 Express 中,我们通常会在应用启动时创建数据库连接或连接池,然后在需要访问数据库的路由处理函数中获取连接并执行查询。这就像在 Express 厨房管理系统中,直接配置好了通往原材料仓库的通道,厨师们(路由处理函数)可以直接通过这个通道获取所需的原材料。
在 Express 应用中配置数据库:
通常会在应用的入口文件或单独的配置文件中创建连接池,并使其在整个应用中可用。
// app.js (Express 应用)
const express = require('express');
const mysql = require('mysql2/promise'); // 使用 Promise 版本的驱动
const app = express();
const port = 3000;
// 数据库连接池配置 (通常从环境变量或配置文件读取)
const dbConfig = {
host: 'localhost',
user: '',
password: '',
database: 'my_webapp_db',
waitForConnections: true,
connectionLimit: 10,
queueLimit: 0
};
// 创建数据库连接池
const pool = mysql.createPool(dbConfig);
// 使连接池在所有路由和中间件中可用 (通过添加到 req 或 app 对象)
// 更优雅的方式是创建一个数据库模块或服务 (在 NestJS 中更常见)
app.locals.dbPool = pool; // 将连接池挂载到 app.locals 上,可以在任何地方通过 req.app.locals.dbPool 访问
// ... 其他中间件 (如 morgan, cors, body-parser) ...
// 定义路由
app.get('/users', async (req, res) => {
let connection;
try {
// 从连接池获取连接
connection = await req.app.locals.dbPool.getConnection();
console.log(`连接 ID ${connection.threadId} 从连接池获取 (Express 路由)`);
// 执行查询
const [rows] = await connection.execute('SELECT * FROM users');
// 发送 JSON 响应
res.json(rows);
} catch (err) {
console.error('查询用户出错:', err);
res.status(500).send('服务器内部出错'); // 发送 500 错误响应
} finally {
// 确保连接归还
if (connection) {
connection.release();
console.log(`连接 ID ${connection.threadId} 归还给连接池 (Express 路由)`);
}
}
});
// ... 其他路由 ...
// 在应用关闭时关闭连接池 (可选,但推荐)
process.on('SIGINT', async () => {
console.log('应用接收到 SIGINT 信号,正在关闭连接池...');
await pool.end();
console.log('连接池已关闭,应用退出。');
process.exit(0);
});
// 启动服务器
app.listen(port, () => {
console.log(`Express 应用运行在 http://localhost:${port}`);
});
// 运行这个文件: node app.js
// 访问 http://localhost:3000/users
在路由处理函数中执行查询:
在路由处理函数中,通过之前挂载到 app.locals
上的连接池获取连接,然后使用 await connection.execute()
执行 SQL 查询。由于数据库操作是异步的,路由处理函数需要声明为 async
,并在执行数据库操作前使用 await
。
处理异步查询结果并发送响应:
数据库查询结果通常在 Promise resolved 后返回。在 async
函数中,可以直接 await
查询结果,然后使用 res.json()
或 res.send()
将结果发送给客户端。务必在 try...catch...finally
结构中处理错误,并在 finally
块中释放连接回连接池。
中间件或工具函数管理数据库连接:
将数据库连接池直接挂载到 app.locals
是一种简单的方式,但更好的做法是创建一个专门的数据库模块或工具函数来管理连接的获取和释放。这使得数据库相关的逻辑更加集中和可复用。
// db.js (简单的数据库工具函数)
const pool = require('./app').locals.dbPool; // 假设 app.js 导出了 app 实例,并且连接池挂载在 app.locals
async function query(sql, params) {
let connection;
try {
connection = await pool.getConnection();
const [rows] = await connection.execute(sql, params);
return rows;
} catch (err) {
console.error('数据库查询出错:', err);
throw err;
} finally {
if (connection) {
connection.release();
}
}
}
module.exports = { query };
// 在路由中使用:
// const db = require('./db');
// app.get('/users', async (req, res) => {
// try {
// const users = await db.query('SELECT * FROM users');
// res.json(users);
// } catch (err) {
// res.status(500).send('服务器内部出错');
// }
// });
小结: 在 Express 应用中集成 MySQL 通常涉及创建连接池并在需要时获取和释放连接。使用 Promise API 和 async/await
可以优雅地处理异步数据库操作。将数据库交互逻辑封装在工具函数或模块中是更好的实践。
练习:
- 在你之前的 Express 项目中,安装
mysql2
。 - 在
app.js
中创建数据库连接池,并将其挂载到app.locals
。 - 修改你之前创建的
/api/users
GET 路由,使其从 MySQL 数据库的users
表中获取所有用户并返回 JSON 响应。 - 修改你之前创建的
/api/users
POST 路由,使其接收请求体中的用户数据,并使用INSERT
语句将新用户插入到users
表中。返回新用户的 ID 或整个用户对象,并设置 201 状态码。 - 为你的
/api/users/:userId
GET 路由添加数据库查询逻辑,根据 ID 从数据库中查找用户并返回,如果找不到则返回 404。 - (可选)创建一个
db.js
文件,封装数据库查询逻辑,并在路由中调用。