掘金 后端 ( ) • 2024-05-14 11:29

theme: smartblue

Nodejs 第三十八章 MySQL实战案例

  • 在前面的演练过了MySQL的诸多常用方法,那在实践中往往都是怎么使用的呢?
    • 这一章节,我们就融会前面所学的Node框架express结合MySQL数据库来实现一个案例
    • 案例内容是:一个简单的REST API,基本的CRUD(创建、读取、更新、删除)操作的Web服务
    • 如果你写过xxx后台管理系统,那估计会非常熟悉

初始化项目

安装依赖

第三方库mysql2介绍

mysql2是Node.js的一个流行库,用于连接和操作MySQL数据库。它是mysql库的一个改进版本,提供了更快的性能和更多的功能,包括Promise API的支持,可以做到和现代异步编程模式更加兼容。

为什么使用mysql2
  1. 性能优化mysql2针对查询速度进行了优化,提供了更快的解析和数据包处理能力。
  2. Promises API:支持Promises,异步操作更加方便,可以使用async/await来处理数据库操作,提高代码的可读性和可维护性。
  3. 完全兼容mysql2设计为与原始mysql库API兼容,因此在大多数情况下可以直接替换使用,无需改动大量代码。
  4. 准备语句支持:支持服务器端的准备语句(prepared statements),这可以提高安全性(防止SQL注入攻击)和性能(减少SQL解析时间)。
  5. 连接池:内置连接池支持,可以有效管理多个数据库连接,提高资源利用率和查询效率

依赖命令集合

  • 在我们这个案例中,我们用到新的第三方库的是mysql2,和前面章节用过的node框架expressjs-yaml(第二十八章节邮件服务中有介绍)
//安装第三方库
npm install mysql2 express js-yaml

//对应的声明文件
npm i --save-dev @types/express
npm i --save-dev @types/js-yaml
第三方库 作用 mysql2 连接mysql和编写sq语句 express 提供接口 增删改查 js-yaml 编写配置文件

创建对应文件夹

  1. index.js:写主要代码的文件
  2. db.config.yaml:yaml配置文件(配置数据库连接的信息)
  3. index.http:模拟发送客户端请求测试

项目实战

连接数据库

  • 那在正式开始编写代码的时候,我们需要先能够连接上数据库。那为什么要先进行这一步,而不是先写代码呢?
    • 因为我们此时要编写的接口,乃是对数据库进行的增删改查。换而言之,数据库是我们编写代码所必须的依靠,没有数据库的话,我们的下一步就是空中阁楼,水中映月
  • 而连接数据库就要我们mysql2派上用场了
    • 我们这里使用的是ESM导入,所以需要在package.json文件中设置"type": "module"
    • 下方的代码中就是正常的创建数据库连接的形式
import mysql2 from "mysql2"
// 创建与数据库的连接,其中connection是连接的意思
const connection = mysql.createConnection({
  host: localhost, //主机
  port: 3306,//端口
  user: root,//账号
  password: 'root',//密码 一定要字符串
  database: xiaoyu // 数据库
});
  • 但在正常的开发中,我们肯定是不会这样做的。在邮件服务(28章节)的那里,我们已经学到了,像这种敏感的信息,一定是要抽离出去的。不然你开发一不小心把代码发到托管平台,就会连着数据库上传上去,这不把家门口的钥匙交给别人保管嘛?
    • 此时就需要我们yaml派上用场了
    • 这种配置信息,我们可以用JS文件来存,也可以用JSON存,或者我们用的yaml都是可以的。根据自己的个人喜好来
    • 然后在开发中上传代码,就可以通过git的忽略文件配置限制yaml配置文件的上传
#db.config.yaml配置文件
db:
  host: localhost #主机
  port: 3306 #端口
  user: root #账号
  password: "root" #密码 一定要字符串
  database: xiaoyu # 库
  • 此时,我们index.js这里编写的代码就可以简化了

    • 我们直接读取到的yaml信息是文件本身的信息形式,而这其实是不好获取其中的某一点的

    image-20240509094839492

    • 所以我们需要借用js-yaml这个第三方库,来将其转化为对象,此时获取对应的值才方便

    image-20240509094939784

import mysql2 from "mysql2"
import jsYaml from "js-yaml"
import fs from "fs"

//读取配置信息
const yaml = fs.readFileSync("./db.config.yaml", "utf8")
const config = jsYaml.load(yaml)

// 创建与数据库的连接
const connection = mysql2.createConnection({
  ...config.db
});
  • 我们这里采用异步进行会更好一点,返回Promise的异步函数。而且mysql2是支持异步的写法,但我们导入的mysql2就需要发生一下改变
    • await在这里,必须要我们连接上数据库了才能进行下一步。这也是思想的体现,因为后面的功能都是要基于已经连接上数据库才能实现,所以我们才这样做
import mysql2 from "mysql2/promise"
// 创建与数据库的连接
const connection =await mysql2.createConnection({
  ...config.db
});

增删改查

  • 创建数据库连接后,我们就使用express框架来启动项目,来进入我们对数据库的操作
import mysql2 from "mysql2/promise"
import jsYaml from "js-yaml"
import express from "express"

import fs from "fs"

const app = express()

const yaml = fs.readFileSync("./db.config.yaml", "utf8")
const config = jsYaml.load(yaml)
console.log(config);

// 创建与数据库的连接
const connection =await mysql2.createConnection({
  ...config.db
});


const port = 3000
app.listen(port, () => {
  console.log("端口开启成功");
})

查询接口

  • 想要测试是否正常连接数据库了,就让我们查询一下是否可以获取到数据库中的内容吧!

    • 由于查询到的内容返回的形式是一个二维数组,且我们只需要第一项内容(数据),所以只解构出前面一个
    //直接拿到的二维数组 [[数据],[表结构]] 形式
    [
      [
        { id: 1, name: '迷你余', email: '[email protected]' },
        { id: 2, name: '小余', email: '[email protected]' },
        { id: 3, name: '中余', email: '[email protected]' },
        { id: 4, name: '大余', email: '[email protected]' },
        { id: 5, name: '超大余', email: '[email protected]' }
      ],
      [
        `id` INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
        `name` VARCHAR(255) NOT NULL,
        `email` VARCHAR(255)
      ]
    ]
    
// 查询接口
app.get("/api/search", async (req, res) => {
  const [data] = await connection.query("SELECT * FROM `users`")
  res.send(data)
})

image-20240509103854747

  • 目前我们实现的是查询所有的数据,但如果有写过后台管理系统的同学,应该就会知道,我们一般是不会请求所有的数据的,因为一次性请求过多的数据,是属于没有必要的事情(页面一次性能展示的数据有限)
    • 通常我们就会进行分页展示,比如我们一次请求多少行数据
    • 或者针对性的查询单个内容数据
  • 而根据用户返回的需求要查询多少条数据,查询哪条具体的数据
    • 通常情况下,这种查询不涉及隐私的,可以继续使用get请求,然后请求携带参数直接拼接在URL地址后面就好了
    • 而我们接口如何接收客户端传来的具体参数内容,在express的讲解中也有讲到,通过:xxx动态参数就行
// 查询单个内容接口
app.get("/api/search/:id", async (req, res) => {
  const [data] = await connection.query("SELECT * FROM `users` WHERE `id` = ?", [req.params.id])
  res.send(data)
})

image-20240509104953820

  • 大家可以看到,我们这里的SQL语句,是采用占位符来进行传递动态参数的
# 占位符形式
"SELECT * FROM `users` WHERE `id` = ?", [req.params.id]
  • 但传递的方式除了这种之外,在前端还可以通过模板字符串来进行,至于通过哪种方式,则也是取决于个人的爱好。
    • 通过占位符的形式可以最大程度保持SQL语句的完整连贯性,会更整齐
    • 而通过模板字符串的形式,则可以更清晰的看到我们参数位于SQL语句中的哪里
//模板字符串形式
`SELECT * FROM users WHERE id = '${req.params.id}'`

新增接口

  • 对于新增内容来说,我们这个案例是对用户表的增删改查,而用户信息是明显的敏感信息,是具备隐私性的。
    • 所以我们不能够在通过GET来发起请求,该用POST了
    • POST请求通常都是接收一个JSON数据,而在express中想要接收JSON数据的话。是需要使用中间件来加持的,不然无法识别(前面章节讲过)
  • 根据我们上一章节所创建的用户数据表来说,我们需要nameemail这两个参数,而id是属于自增内容
    • 同时当成功接收到内容后,返回给用户响应成功的信息
const app = express()
app.use(express.json())

//新增接口
app.post('/api/create', async (req, res) => {
  const { name, email } = req.body
  await connection.query("insert into `users`(name,email) values(?,?)", [name, email])
  res.send({ ok: 1 })
})
  • 接着我们需要编写一下对应的HTTP请求接口
    • 需要注意的是,Content-Type这行设置与第一行POST请求回车后不能够像与JSON数据有间隔,不然会断掉
    • 发送的json请求数据将无法传达
POST http://localhost:3000/api/create HTTP/1.1
Content-Type: application/json

{
    "name":"小余2002",
    "email":"[email protected]"
}

image-20240509115626523

  • 成功发送请求,也返回了成功的响应,让我们用刚才的查询接口看下内容是否真的添加了进去
    • 通过查询接口的结果,我们能看到新增效果是理想的

image-20240509115727347

编辑接口

  • 接着让我们加快脚步,来看下剩下的编辑和删除接口要怎么写
    • 我想大家应该也发现了,这最常用的增删改查功能,其实都是一样的逻辑
    • 唯一不同的就是SQL命令的不同,而SQL命令在上一章节我们已经学过了,此时运用起来也就没有压力
  • 我们通过id来精准确定要修改的数据是哪一条,但实际中,我们可能会通过其他数据来精准确定我们到底要修改哪一条数据
    • 修改单条具体数据:内容唯一,通常具备唯一性质的id是很好的选择。掘金的用户主页中,我们通过看URL就能看到我们的id了。很多时候掘金要我们填写内容或者帮我们查询事情的时候,都需要我们把用户id发给他们
    • 修改多条数据:那就可以通过其他各种各样的数据进行修改了,根据具体业务决定
  • 比如像我的uid(其实就是id的另一种名称,user-id简称uid)就是251124329220663,表示我在掘金这个网站中,唯一的身份标识

image-20240509121601444

image-20240509121502410

//编辑接口
app.post('/api/update',async (req,res)=>{
  const {name,email,id} = req.body
  await connection.query("update users set name = ?,email = ? where id = ?",[name,email,id])
  res.send({ok:1})
})
#编辑接口(更新数据)的测试代码
POST http://localhost:3000/api/update HTTP/1.1
Content-Type: application/json

{
    "name":"XiaoYu2002",
    "email":"[email protected]",
    "id":6
}

image-20240509120750011

  • 修改成功,让我们继续看下查询结果是否发生改变
    • 通过针对查询我们修改的id,可以看到名称已经发生了改变

image-20240509121810401

删除接口

//删除接口
app.post('/api/delete',async (req,res)=>{
  await connection.query(`delete from users where id = ?`,[req.body.id])
  res.send({ok:1})
})
#删除
POST http://localhost:3000/api/delete HTTP/1.1
Content-Type: application/json

{
    "id":6
}

image-20240509122148609

  • 此时数据可就查不到咯

image-20240509122241011

完整接口代码(增删改查)

import mysql2 from "mysql2/promise"
import jsYaml from "js-yaml"
import express from "express"

import fs from "fs"

const app = express()
app.use(express.json())

const yaml = fs.readFileSync("./db.config.yaml", "utf8")
const config = jsYaml.load(yaml)

// 创建与数据库的连接
const connection = await mysql2.createConnection({
  ...config.db
});

// 查询全部内容接口
app.get("/api/search", async (req, res) => {
  const [data] = await connection.query("SELECT * FROM `users`")
  res.send(data)
})

// 查询单个具体内容接口
app.get("/api/search/:id", async (req, res) => {
  const [data] = await connection.query("SELECT * FROM `users` WHERE `id` = ?", [req.params.id])
  res.send(data)
})

//新增接口
app.post('/api/create', async (req, res) => {
  const { name, email } = req.body
  await connection.query("insert into `users`(name,email) values(?,?)", [name, email])
  res.send({ ok: 1 })
})


//编辑接口
app.post('/api/update',async (req,res)=>{
  const {name,email,id} = req.body
  await connection.query("update users set name = ?,email = ? where id = ?",[name,email,id])
  res.send({ok:1})
})


//删除接口
app.post('/api/delete',async (req,res)=>{
  await connection.query(`delete from users where id = ?`,[req.body.id])
  res.send({ok:1})
})

const port = 3000
app.listen(port, () => {
  console.log("成功开启端口:", port);
})

总结

  • 通过以上的代码,我们完成了MySQL、express的案例闭环,将前面所学的知识和思想运用了起来,实现了日常开发中最离不开的功能点的雏形
    • 前面所浇的水,在此时也是长出了对应的果实,恭喜你在此刻成功入门Node了