从零学习Node.js框架Koa 【四】Koa 与数据库(MySQL)连接,实现CRUD操作
系列文章目录
从零学习Node.js框架Koa 【一】 Koa 初探从环境搭建到第一个应用程序
从零学习Node.js框架Koa 【二】Koa 核心机制解析:中间件与 Context 的深度理解
从零学习Node.js框架Koa 【三】Koa路由与静态资源管理:处理请求与响应
从零学习Node.js框架Koa 【四】Koa 与数据库(MySQL)连接,实现CRUD操作
从零学习Node.js框架Koa 【五】Koa鉴权全解析:JWT+Redis构建安全认证系统
文章目录
- 系列文章目录
- 前言
- 一、MySQL 环境准备
- 1、MySQL安装
- 2、 数据库管理工具
- 二、Koa连接MySQL
- 1、安装依赖:
- 2、数据库连接配置
- 3、在入口文件(app.js)使用
- 4、连接池配置常用的属性说明
- 三、Koa操作MySQL
- 1、操作API execute()和query()讲解
- (1) execute
- (2) query
- (3)execute和query区别
- 2、 基本增删改查(CRUD)操作
- (1)插入数据(INSERT)
- (2)更新数据(UPDATE)
- (3) 删除数据(DELETE)
- (4) 查询数据(SELECT)
- 1、查询所有数据
- 2、条件查询数据
- 3、模糊查询
- 4、分页查询
- 4、排序查询
- 5、计数
- (1)查询所有记录总数
- (2)获取条件查询记录总数
- 四、总结
前言
Web服务端开发离不开数据的存储、数据库的操作。MySQL 作为最常用的关系型数据库,是实际项目开发的首选。而在Koa框架中也有非常适合的中间件帮助我们快速集成和操作MySQL 。本篇文章将继续介绍如何在 Koa 中集成 MySQL,完成基础的 CRUD 操作。
一、MySQL 环境准备
1、MySQL安装
以window系统为例:
打开官方网站,

选择 “Windows (x86, 64-bit), MSI Installer“ 安装包,点击《Download》,跳转到下载页面:

点击”No thanks, just start my download.“开始下载。
下载完成双击安装包,按向导操作


选择“Custom”自定义安装方式

默认安装在C盘,点击Browse按钮可选择安装目录,记住这里选择的安装目录后面需要使用。



安装完成点击“Finish” 进入配置设置

设置数据存储位置,有需要修改其他目录


接下来设置root用户密码,2个输入框密码一致,务必记住密码后续需要使用。




点击《Execute》,等待配置完成



到此MySQL 程序安装和基础配置就完成了,接下来设置环境变量。
window+i组合按键打开Window设置,搜索“高级系统设置”-打开系统属性设置窗口

点击右下角“环境变量“

在系统变量中找到path并选中,点击“编辑”

在编辑环境变量窗口中新建变量,路径输入mysql安装目录\bin,确定关闭

校验是否安装成功:
window+R,输入cmd,打开cmd窗口输入:
mysql -u root -p
进行MySQL连接测试

输入密码——回车,出现如下图所示表示连接成功

至此,MySQL安装和所有配置工作就都完成了。
2、 数据库管理工具
操作数据库既可以通过命令行也可以选择可视化MySQL管理工具,对于不熟悉命令行的新手,推荐使用可视化管理工具,常用的可视化管理工具有Navicat、Workbench、DBeaver等,根据具体需求和个人偏好选择合适的工具。
以Navicat为例简单说明使用步骤:
(1)点击左上角“连接”按钮——选择MySQL创建连接

(2)输入连接名(自定义),主机默认localhost,端口默认3306,用户名默认root,输入密码——确认,创建成功在左侧列表新增一个连接(MySQL)显示

(3)右击“MySQL连接“选择“打开链接”

打开成功,左侧列表显示默认创建好4个数据库

(4)右击MySQL新建数据库,输入数据库名、字符串选择“utf8mb4”,排序规则选择
“utf8mb4_0900_ai_ci”——确定

(5)打开新建的数据库(project),右击表选择新建表,在右边窗口添加字段,添加完保存——输入表名——确定 表创建完成。


Navicat的使用简单介绍到这,如需更详细的使用教程可以自行查找网上其他文章。
二、Koa连接MySQL
接下来讲解本文重点——Koa连接操作数据库。 在 Node.js 生态中,mysql2 库因其卓越的性能和完整的 Promise 支持成为操作 MySQL驱动首选。
1、安装依赖:
npm install mysql2
2、数据库连接配置
创建config/db.js文件配置数据库连接:
const mysql = require("mysql2/promise");
// 创建连接池
const pool = mysql.createPool({host: "localhost",//数据库服务器地址,默认localhostport: 3306,// 默认端口user: "root", //用户名,默认rootpassword: "123456",//密码database: "project", //数据库名称// 连接池配置waitForConnections: true,// 没有连接时等待connectionLimit: 10, // 连接池最大连接数queueLimit: 0, // 等待队列无限制(0表示不限制)
});// 测试数据库连接
async function testConnection() {try {const connection = await pool.getConnection();console.log("数据库连接成功");connection.release();// 释放连接回池} catch (e) {console.log(e, "数据库连接失败");}
}//执行测试
testConnection()module.exports = pool;
说明:上述代码创建了一个数据库连接池实例和测试连接是否成功方法,默认执行了连接测试方法,并导出连接池实例。
为什么用连接池?因为 “每次请求创建一个数据库连接” 的成本很高(TCP 握手、认证等步骤耗时),连接池会提前创建一批连接,请求来时直接复用,用完放回池里,大幅提升性能。
3、在入口文件(app.js)使用
//app.js
const Koa = require("koa");
const app = new Koa();
require("./config/db.js");//连接数据库const port = 3001; //端口号
app.listen(port, () => {console.log(`服务器运行在 http://localhost:${port}`);
});
运行项目:

控制台上显示“数据库连接成功”,证明连接配置成功。
4、连接池配置常用的属性说明
| 属性类别 | 属性名 | 默认值 | 说明 | 使用建议 |
|---|---|---|---|---|
| 基础连接属性 | host | localhost | 数据库服务器地址,本地连接默认使用localhost,远程连接需填写目标服务器IP | 远程连接时需确保服务器开放MySQL默认端口(3306)或自定义端口 |
| 基础连接属性 | user | 无 | 数据库登录用户名 | 推荐创建专用业务用户并分配最小权限,禁止直接使用root用户 |
| 基础连接属性 | password | 无 | 登录用户对应的密码 | 生产环境需通过环境变量(如dotenv库)管理,禁止硬编码在代码中 |
| 基础连接属性 | database | 无 | 要连接的目标数据库名称 | 需提前创建数据库,并为登录用户授权该数据库的操作权限 |
| 基础连接属性 | port | 3306 | MySQL服务监听的端口号 | 若修改过MySQL默认端口,需与服务器配置保持一致 |
| 连接池核心属性 | connectionLimit | 10 | 连接池允许的最大并发连接数 | 小型项目建议5-10,中大型项目20-50,需结合服务器性能动态调整 |
| 连接池核心属性 | waitForConnections | true | 无空闲连接时,新请求是否进入队列等待 | 生产环境建议设为true,避免请求直接失败影响用户体验 |
| 连接池核心属性 | queueLimit | 0 | 连接等待队列的最大长度,0表示无限制 | 默认保持0即可,高并发场景可设100-200,防止队列过长导致内存溢出 |
| 连接池核心属性 | idleTimeout | 60000ms(1分钟) | 空闲连接的超时时间,超时后自动关闭释放服务器资源 | 短连接业务可设30000ms(30秒),长连接业务可设5-10分钟 |
| 辅助优化属性 | enableKeepAlive | true | 是否启用TCP长连接机制,保持连接活跃 | 建议开启,减少频繁建立和断开TCP连接的性能开销 |
| 辅助优化属性 | charset | utf8mb4 | 数据库连接使用的字符集 | 必须与数据库、表的字符集一致,避免中文乱码 |
三、Koa操作MySQL
mysql2 连接池实例上有execute、query2个方法可以执行sql语句进行数据库CRUD。两者使用方法相似。
1、操作API execute()和query()讲解
(1) execute
语法:
const [results, fields]=await pool.execute(sqlStr, [...values])
其中pool为连接池实例, 第一个入参为sql原生语句字符串,值用“?”占位符代替,第二个入参为数组,数组每个元素对应占位符“?”的值。
返回值:一个二维数组[results, fields]
-
results :执行结果数据,内容随操作类型变化,查询类操作(SELECT、COUNT、SUM 等),results 是个数组,包含查询到的行数据。. 写操作(INSERT、UPDATE、DELETE),results 是一个对象,包含操作影响的行数、插入 ID 等元数据。
-
fields :字段元信息(仅查询类操作有意义),包含字段名、数据类型、表名等描述性信息(一般业务代码中用得少,调试时可查看)。
示例:
//从users表中查找id=13的用户数据const [rows] = await pool.execute(`SELECT * FROM users WHERE id = ? `,[13])console.log(rows) //[{name:'张三',sex:1,age:20}]
(2) query
语法:
const [results, fields]=await pool.query(sqlStr, [...values])
和execute用法一样
示例
//往users表中插入一条数据:字段name值为张三,字段email值为'123456@@qq.comconst [result]= await pool.query(`INSERT INTO users (name, email) VALUES (?, ?)`,['张三','123456@@qq.com'])console.log(results.insertId) //20:新增记录的自增 ID
除了和execute相同的使用方法外,query还支持sql动拼接参数法。
上述的示例可以改为:
const name='张三';const email='123456@@qq.com';const result= await pool.query(`INSERT INTO users (name, email) VALUES (${name}, ${email})`,
query 2中方法区别:?是参数化查询的占位符,主要用于防止 SQL 注入攻击,同时让代码更安全。实际开发中推荐使用占位符法。
(3)execute和query区别
尽管两者在代码使用上相似,但是内部执行机制有着本质上的区别:
-
execute:原生支持预处理语句,会先将 SQL 模板(含占位符?)发送给 MySQL 编译,后续仅传递参数执行,重复执行相同结构 SQL 时(如批量新增数据),仅编译一次 SQL,后续复用执行计划,性能更优。
-
query:无预处理机制,直接将完整 SQL 字符串发送给 MySQL 执行,每次执行都需编译 SQL,重复执行相同 SQL 时性能较差,只适合简单静态 SQL(无参数拼接,如SELECT * FROM users),或一次性执行的 SQL。
综上所述,推荐全部使用execute进行数据库操作。
2、 基本增删改查(CRUD)操作
(1)插入数据(INSERT)
新增一条用户数据:
原生SQL语句:
//向users表插入一条新数据(姓名张三(name=张三)、性别男(sex=1)、年龄25(age=25)、邮箱为12345@qq.com(email=12345@qq.com))
INSERT INTO users (name, sex,age,email) VALUES ('张三', 1,25,'12345@qq.com')
koa实现:
router.post("/api/users", async (ctx) => {let { name, sex, age, email} = ctx.request.body;//执行sqllet [result]=await pool.execute("INSERT INTO users (name, sex,age,email) VALUES (?, ?,?,?)", [name, sex, age, email]);ctx.body = {code: 200,message: "创建用户成功",data:result.insertId //返回新用户id};
});
(2)更新数据(UPDATE)
修改表中用户数据:
原生SQL语句:
//修改表uses中id=13的用户姓名为张三(name='张三')、邮箱为12345@qq.com(email=12345@qq.com)
UPDATE users SET name = '张三', email = '12345@qq.com' WHERE id = 13
koa实现:
router.put("/api/users/:id", async (ctx) => {let { id } = ctx.params;let {name,email}=ctx.request.body;//执行sqllet [result]=await pool.execute("UPDATE users SET name = ?, email = ? WHERE id = ?",[name, email, id]);ctx.body = {code: 200,message: "修改用户成功",data: result.affectedRows,//受影响的行数 (1)};
});
(3) 删除数据(DELETE)
删除一条用户数据:
原生SQL语句:
//删除表users中id=13的用户数据
DELETE FROM users WHERE id = 13
koa实现:
router.delete("/api/users/:id", async (ctx) => {let { id } = ctx.params;//执行sqllet [result]=await pool.execute("DELETE FROM users WHERE id = ?", [id]);ctx.body = {code: 200,message: "修删除用户成功",data: result.affectedRows,//受影响的行数 (1)};
});
(4) 查询数据(SELECT)
已知当前数据库含有3条用户数据

1、查询所有数据
原生SQL语句:
//查询表users 所有行数据
SELECT * FROM users
koa实现:
router.get("/api/users", async (ctx) => {//执行sqllet [result] = await pool.execute("SELECT * FROM users");ctx.body = {code: 200,message: "查询用户列表成功",data: result,};
});
执行结果:

2、条件查询数据
单条件查询:查找年龄大于25的用户
原生SQL语句:
//查询表users中年龄大于25的用户
SELECT * FROM users WHERE age > 25
koa实现:
router.get("/api/users", async (ctx) => {const {age}=ctx.query;//执行sqllet [result] = await pool.execute("SELECT * FROM users WHERE age > ?",[Number(age)]);ctx.body = {code: 200,message: "查询用户列表成功",data: result,};
});
执行结果:

多条件查询:查找年龄大于25并且是女性用户
原生SQL语句:
//查询表uses中年龄大于25且为女性的用户
SELECT * FROM users WHERE age > 25 AND sex = 2
koa实现:
router.get("/", async (ctx) => {const {age,sex}=ctx.query;//执行sqllet [result] = await pool.execute("SELECT * FROM users WHERE age > ? AND sex = ?",[Number(age),Number(sex)]);ctx.body = {code: 200,message: "查询用户列表成功",data: result,};
});
执行结果:

3、模糊查询
支持姓名或邮箱模糊搜索实现:
原生SQL语句:
//查询表users中姓名带“张”或邮箱带“58”用户
SELECT * FROM users WHERE name LIKE '%张%' OR email LIKE '%58%'
koa实现:
router.get("/", async (ctx) => {const {name,email}=ctx.query;//执行sqllet [result] = await pool.execute("SELECT * FROM users WHERE name LIKE ? OR email LIKE ?",[`%${name}%`,`%${email}%`]);ctx.body = {code: 200,message: "查询用户列表成功",data: result,};
});
执行测试:
查询张姓用户

邮箱关键词模糊搜索

说明:模糊查询通过 LIKE 搭配通配符实现,常用通配符:
- ( % ):匹配任意长度的字符串(包括 0 个字符),例如 ‘李%’ 匹配 “李” 开头的字符串(“李白”“李世民” 等),“%白“匹配“白”为后缀的字符串(“王自白”,"明白"等),“%李%“匹配含有“李”关键字字符串不管在什么位置;
- ( _ ):匹配单个字符,例如 ‘李_’ 匹配 “李” 开头且长度为 2 的字符串(“李白”“李四” 等,不匹配 “李世民”)。
4、分页查询
原生SQL语句:
//查询表users中第二条至第三条数据(每页2条,跳过前1条,从第2条开始取)
SELECT * FROM users LIMIT 2 OFFSET 1;
koa实现:
router.get("/", async (ctx) => {//执行sqllet [result] = await pool.execute("SELECT * FROM users LIMIT ? OFFSET ?",[BigInt(2),BigInt(1)]);ctx.body = {code: 200,message: "查询用户列表成功",data: result,};
});
执行结果:

ps:注意分页参数必须是BigInt类型不然执行会报错
4、排序查询
原生SQL语句:
//查询用户表所有记录并按年龄降序排列
SELECT * FROM users ORDER BY age DESC
Koa实现:
router.get("/", async (ctx) => {//执行sqllet [result] = await pool.execute("SELECT * FROM users ORDER BY age DESC");ctx.body = {code: 200,message: "查询用户列表成功",data: result,};
});
执行结果:

说明:降序DESC,升序ASC,多字段排序“,”连接
const [result] = await pool.execute('SELECT * FROM users ORDER BY age ASC, email DESC, name ASC');
5、计数
(1)查询所有记录总数
原生SQL语句:
//查询用户表所有记录总数
SELECT COUNT(*) as total FROM users
Koa实现:
router.get("/", async (ctx) => {//执行sqllet [result] = await pool.execute("SELECT COUNT(*) as total FROM users");ctx.body = {code: 200,message: "计数成功",data: {total:result[0].total},};
});

(2)获取条件查询记录总数
原生SQL语句:
//查询用户表年龄为30的用户数量
SELECT COUNT(*) as total FROM users WHERE age = 30
Koa实现:
router.get("/", async (ctx) => {//执行sqllet [result] = await pool.execute("SELECT COUNT(*) as total FROM users WHERE age = ?",[30]);ctx.body = {code: 200,message: "计数成功",data: {total:result[0].total},};
});

注意:返回解构值result为数组,获取总数为result[0].total
四、总结
通过本文的详细讲解,我们完成了从MySQL环境搭建到Koa框架集成数据库的完整流程。我们不仅掌握了MySQL的安装配置和可视化工具的使用,更重要的是深入学习了如何在Koa应用中通过mysql2库高效地操作数据库。在下一篇文章中,我们将继续学习如何实现Koa鉴权功能。
