# 1. 前言

我们知道,HTTP 的请求方法 GETPOSTPUTDELETE 分别对应着对数据的增、删、改和查。既然是对数据的操作,那肯定少不了数据库。那么在本篇文章中,我们就来介绍一下在 koa 中如何使用数据库以及如何打通 GETPOSTPUTDELETE 这四种请求方法与数据库的增删改查之间的链路。

数据库有关系型数据库和非关系型数据库两种,其中关系型数据库的典型代表是 MySQL ,非关系型数据库的典型代表通常是 MongoDB,接下来,我们就以这两种数据库为例来介绍下在 koa 中如何使用数据库。

# 2. 关系型数据库 MySQL

关系型数据库需要通过 SQL 语言来存取数据,但书写 SQL 语句需要一定的技术能力,并且不恰当的 SQL 语句还会带来 SQL 注入漏洞。为了快捷开发,社区出现了一系列的 ORM(Object Relational Mapping) 类库。ORM 的字面意思为对象关系映射,它提供了概念性的、易于理解的模型化数据的方法。通过 ORM,可以降低操作数据库的本。开发者不需要通过编写 SQL 脚本来操作数据库,直接通过访问对象的方式来查询、更新数据即可。这样做极大地提升了开发效率,降低了开发的门槛。但 ORM 也不是万能的,由于ORM 统一封装了 SQL 查询,在某些情况下 ORM 生成的 SQL 并不高效,依旧需要开发者编写 SQL 查询语句来提升性能。在 Node.js 中,一般采用 Sequelize 这个 ORM 类库来操作数据库。Sequelize 支持多种数据库,如 PostgreSQLMySQLSQLiteMSSQL

# 2.1 Sequelize 安装及使用

由于 Sequelize 属于第三方类库,所以老规矩,使用前先安装:

npm i Sequelize
1

由于我们要使用 Sequelize 来操作 MySQL 数据库,所以我们还要安装以下 MySQL 的驱动,如下:

npm i mysql2
1

以上两个都安装完成之后,我们就可以来使用 Sequelize 了。代码如下:

import * as Koa from "koa";
import * as Router from "koa-router";
import * as bodyParser from "koa-bodyparser";
// 引入 Sequelize
import { Sequelize } from "Sequelize";

/**
 * databaseName: 数据库名称
 * userName: 数据库登录名
 * password: 数据库登录密码
 * host: 数据库服务地址
 * dialect: 数据库类型,这里是mysql
 */
const sequelize = new Sequelize(databaseName, userName, password, {
  host: "localhost",
  dialect: "mysql",
});

app.listen("3000", () => {
  sequelize
    .sync({ force: false })
    .then(() => {
      console.log("sequelize connect success");
      console.log("server is running at http://localhost:3000");
    })
    .catch((err) => {
      console.log(err);
    });
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

将以上代码复制进 app.ts 文件中,启动服务,如果看到控制台输出如下内容,就表示与数据库连接成功了。

sequelize connect success
server is running at http://localhost:3000
1
2

ok,连接成功后,接下来我们就可以给数据库里添加数据了。

# 2.2 定义模型

我们知道,在关系型数据库中,数据是存在数据表里的,所以想要给数据库添加数据,我们第一件事就应该是创建数据表。创建数据表之前得先定义表结构,在 Sequelize 中,把一张数据表的表结构称作表的「模型」。可以使用 define 方法来定义模型,代码如下:

import { Sequelize, DataTypes } from "Sequelize";
const UserModel = sequelize.define("user", {
  // 定义名为 user 的表
  name: DataTypes.STRING, // 定义 name 字段,类型为string
  age: DataTypes.NUMBER, // 定义 age 字段,类型为number
});
1
2
3
4
5
6

Sequelize 会默认为创建的数据表自动创建 createdAtupdatedAt 字段。同时,Sequelize 也提供了配置项来禁用或修改这些字段,代码如下:

const UserModel = sequelize.define(
  "user", // 定义名为 user 的表
  {
    name: DataTypes.STRING, // 定义 name 字段,类型为string
    age: DataTypes.NUMBER, // 定义 age 字段,类型为number
  },
  {
    timestamps: false, // 禁止创建 createdAt 和 updatedAt 字段
    updatedAt: "updateTime", // 创建 updateTime 字段,替代 updatedAt 字段
    tableName: "myUser", // 修改创建的表名为 myUser
  }
);
1
2
3
4
5
6
7
8
9
10
11
12

模型定义好之后,接下里就可以将模型同步到数据库中以创建数据表。在 Sequelize 中,可以通过 sync 方法将定义的模型同步到数据库中。既可以同步单个表,也可以同步全部表,代码如下:

sequelize
  .sync({ force: false })
  .then(() => {
    console.log("sequelize connect success");
    console.log("server is running at http://localhost:3000");
  })
  .catch((err) => {
    console.log(err);
  });
1
2
3
4
5
6
7
8
9

如果数据库中已经存在某一个表,同步时将默认不同步这个表。可以在调用 sync 方法时,开启传递参数 force进行强制同步。

注意:如果开启 force,同步时会删除已经存在的数据表。

# 2.3 sequelize 增删改查

Sequelize 中,定义的模型上会为我们提供常用的对数据表的操作方法,如增、删、改、查。接下来,我们分别来看一下:

  • Sequelize 的模型类上提供了 create 方法,该方法向数据表中增加一条记录,如下:

    const user = await UserModel.create({
      name: "nlrx",
      age: 18,
    });
    
    1
    2
    3
    4
  • 删除数据有两种方式:可以调用模型类上的 destroy 方法删除指定数据,也可以先根据指定条件查出要删除的数据,然后调用数据实例上的 destroy 方法来删除自身。如下:

    // 调用模型类上的 `destroy` 方法删除指定数据
    await UserModel.destroy({
      where: {
        name: "nlrx",
      },
    });
    
    // 调用数据实例上的 `destroy` 方法来删除自身
    const user = await UserModel.create({
      name: "nlrx",
      age: 18,
    });
    await user.destroy();
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
  • 修改数据也有两种方式:可以调用模型类上的 update 方法修改指定数据,也可以先根据指定条件查出要修改的数据,然后调用数据实例上的 update 方法来修改自身。如下:

    // 调用模型类上的 `update` 方法修改指定数据
    await UserModel.update(
      {
        age: 19, //修改的字段对应的内容
      },
      {
        where: {
          name: "nlrx", //查询条件
        },
      }
    );
    
    // 调用数据实例上的 `update` 方法来修改自身
    const user = await UserModel.create({
      name: "nlrx",
      age: 18,
    });
    await user.update({
      age: 19, //修改的字段对应的内容
    });
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
  • 数据库的主要功能就是对数据的查询,所以 sequelize 提供了非常多的数据查询方法,在这里因为篇幅限制,我们只介绍两个:查找表中全部数据 findAll 和根据查询条件查询单条数据 findOne,如下:

    // fingAll 查询全部
    await UserModel.findAll();
    
    // findOne 查询单条
    await UserModel.findOne({
      where: { name: "nlrx" },
    });
    
    1
    2
    3
    4
    5
    6
    7

# 2.4 与路由结合使用

通常情况下,koa-router 提供的 .get.post.put.del 方法分别对应着对数据库的增、删、改和查。那么接下来,我们就来介绍下如何将路由与数据库的操作对应结合使用。

我们假设数据库里有一张 user 表,表中的字段分别是 nameage,那么通过路由对 user 表的增删改查如下:

  • 增 POST

    我们通过对路由 /user 发送 POST 请求,来创建一个新用户并将创建出来的新用户返回,如下:

    router.post("/user", async (ctx: Koa.Context, next: Koa.Next) => {
      const { name, age } = ctx.request.body;
      const user = await UserModel.create({
        name,
        age: Number(age),
      });
      ctx.body = {
        msg: "添加成功",
        data: user,
      };
    });
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11

    服务启动后,我们使用 postman127.0.0.1:3000/user 发送 POST 请求,可以看到,数据已经成功被插入到数据库里了。

  • 查 GET

    我们通过对路由 /user 发送 GET 请求,并携带需要查找的用户的用户名作为参数,如下:

    router.get("/user/:name", async (ctx: Koa.Context, next: Koa.Next) => {
      const { name } = ctx.params;
      const user = await UserModel.findOne({
        where: { name },
      });
      if (user) {
        ctx.body = {
          data: user,
        };
      } else {
        ctx.body = {
          msg: "该用户不存在",
        };
      }
    });
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15

    可以看到,当我们查找用户名为 nlrx 的用户时可以被成功查到,但是当查找用户名为 aa 的用户时会返回「该用户不存在」的提示。

  • 改 PUT

    我们通过对路由 /user 发送 PUT 请求,并携带需要修改的用户的用户名作为参数,如下:

    router.put("/user/:name", async (ctx: Koa.Context, next: Koa.Next) => {
      const { name } = ctx.params;
      let res: { msg: string; data: any } = {
        msg: "",
        data: null,
      };
    
      const user = await UserModel.findOne({
        where: { name },
      });
      if (user) {
        await user.update({
          age: 19, //修改的字段对应的内容
        });
        res.msg = "修改成功";
        res.data = user;
      } else {
        res.msg = "您要修改的用户不存在";
        res.data = null;
      }
    
      ctx.body = res;
    });
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23

    在上述代码中,我们将用户名为 nlrx 的用户的 age 字段改成了 19,执行数据修改操作之前我们先查找该用户是否存在,如果存在,再执行修改操作;如果不存在,则提示「要修改的用户不存在」。修改成功之后返回了该用户被修改后的信息,我们发现该用户的 age 字段确实被改成了 19

  • 删 DELETE

    我们通过对路由 /user 发送 DELETE 请求,并携带需要删除的用户的用户名作为参数,如下:

    router.del("/user/:name", async (ctx: Koa.Context, next: Koa.Next) => {
      const { name } = ctx.params;
      let res: { msg: string; data: any } = {
        msg: "",
        data: null,
      };
      const user = await UserModel.findOne({
        where: { name },
      });
      if (user) {
        await user.destroy();
        res.msg = "删除成功";
        res.data = user;
      } else {
        res.msg = "您要删除的用户不存在";
        res.data = null;
      }
    
      ctx.body = res;
    });
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20

    同样,执行数据删除操作之前我们先查找该用户是否存在,如果存在,再执行删除操作;如果不存在,则提示「要删除的用户不存在」。删除成功之后返回了被删除用户的信息。

    删除成功后当我们再查找该用户时,发现该用户已经不存在了,说明我们真的已经将该用户删除了。