基于Koa2+Mysql+Sequelize开发API接口(入门级攻略一)

1,129 阅读9分钟

我正在参与掘金创作者训练营第5期,点击了解活动详情

前言

本篇文章从0开始搭建Koa2框架,搭配Sequelize操作Mysql数据库,以达到快速开发接口的目的。

默认已经安装好开发环境:node v15.0及以上,mysql5.7

初始化koa2项目

首先新建文件夹,这里命名为koa2-server,打开终端,进入到koa2-server目录

初始化node

yarn init -y

安装koa2

yarn add koa

运行项目

项目根目录下新建app.js,实现一个必修的Hello World

const Koa = require('koa') // 引入koa
const app = new Koa() // 声明实例

app.use(async ctx => {
  ctx.body = 'Hello World';
});

app.listen(3000)

打开终端输入

node app.js

打开浏览器,请求地址

http://localhost:3000

image.png

使用yarn启动

打开package.json,修改/添加script

"scripts": {
    "start": "node app.js"
},

终端输入yarn start即可运行

热更新

安装nodemon

nodemon可以监听源文件中任何的更改并自动重启服务器,也就意味着每次改完代码都不用手动重启服务器看效果了,爆赞^_^

yarn add nodemon -D

安装完成后修改package.json里的scripts

"scripts": {
    "start": "nodemon node app.js"
},

创建第一个路由

安装koa-router

yarn add koa-router

在app.js里引入并实例化

const Koa = require('koa') // 引入koa
const app = new Koa() // 声明实例


const Router = require("koa-router")  // 引入路由
const router = new Router()

// 添加一个入口
router.get('/', ctx => {
  ctx.body = "我最帅!"
})

/**
 * router.routes() 启动路由
 * router.allowedMethods() 运行任何请求——get/post/put/delete等
 */
app.use(router.routes(), router.allowedMethods())


app.listen(3000)

运行起来看下效果吧

yarn start
image.png

添加第二个路由

接下来再添加一个路由入口,跟在第一个路由入口后面就可以啦

router.get('/user', ctx => {
  ctx.body = "这是用户页"
})

image.png

封装路由

现在看一下整体的app.js代码

const Koa = require('koa') // 引入koa
const app = new Koa() // 声明实例

const Router = require("koa-router")  // 引入路由
const router = new Router()

router.get('/', ctx => {
  ctx.body = "我最帅!"
})

router.get('/user', ctx => {
  ctx.body = "这是用户页"
})

/**
 * router.routes() 启动路由
 * router.allowedMethods() 运行任何请求——get/post/put/delete等
 */
app.use(router.routes(), router.allowedMethods())

app.listen(3000)

对于路由这一部分,我们单独拿出来放到一个文件里export出来并且在app.js里引入它,可以让入口文件更加简洁,代码的可读性也会更高。

说干就干,在根目录下新建router文件夹,在router文件夹里新建router.js(直接在根目录新建router.js也是可以的,事实上你可以放在项目任意自己喜欢的位置,当然除了node_modules...)

创建完文件后,把app.

/**
 * router.js
 */
const Router = require('koa-router')
const router = new Router();


router.get('/', ctx => { ctx.body = "我最帅!" })
router.get('/user', ctx => { ctx.body = "这是用户页" })

module.exports = router

然后在app.js引入这个文件

const Koa = require('koa') // 引入koa
const app = new Koa() // 声明实例

const router = require('./router/router')
/**
 * router.routes() 启动路由
 * router.allowedMethods() 运行任何请求——get/post/put/delete等
 */
app.use(router.routes(), router.allowedMethods())

app.listen(3000)

运行起来看看效果吧

Controller层

一个简单的测试

在前面我们创建了两个路由,请求不同的路由地址,会得到不同的结果,在这个基础上我们稍微的扩展一下。

  1. 新建一个get请求的路由,并且输入的参数为666,否则会返回失败信息
router.get('/user/:id', ctx => {
    const id = ctx.params.id
    if (id === 666) {
        ctx.body = {
            'code': 200,
            'msg': '输入正确',
            'data': []
        }
    }else {
        ctx.body = {
            'code': 200,
            'msg': '输入错误',
            'data': []
        }
    }
})
  1. 新建一个get请求的路由,通过该路由获得角色页
router.get('/role', ctx => { ctx.body = "这是用户角色页" })
  1. 新建一个post请求的路由,并且输入name为lurengao,不输入或者输入不正确则返回失败信息
router.post('/role', ctx => {
    console.log(ctx.request.body)
})

输入一个name测试一下

image.png image.png

发现打印不出来数据

因为node.js中收到post请求时会把数据以buffer的形式缓存下来,koa2中并没有对post获取参数进行封装,只能以ctx.req.addListener('data', () => {})这种方式读取buffer。

听起来很麻烦对不对,好在有一个插件帮我们解决了这个问题

yarn add koa-body -D

安装完成后需要在app.js里引入一下

const bodyParser = require('koa-body')

app.use(bodyParser({
  enableTypes: ['json', 'form', 'text'],
  multipart: true // 是否支持 multipart-formdate 的表单
}))

再请求一下,可以接收到数据了,amazing!

image.png

这个接口需要判断输入的name是否为lurengao,做个简单的判断就好啦

router.post('/role', ctx => {
    const name = ctx.request.body.name || ""
    if (!name || name !== 'lurengao') {
        ctx.body = {
            'code': 200,
            'msg': '输入错误',
            'data': []
        }
    }else {
        ctx.body = {
            'code': 200,
            'msg': '你真帅',
            'data': []
        }
    }
})

做个简单测试

未传参数 未传参数

参数错误 image.png

正确输入

image.png

仔细观察一波,总共五个路由,分别对user、role进行了处理,这时候代码量已经非常多了,等到有几十上百个路由时,再想对某个路由的逻辑进行修改就非常不方便,因此这里可以把逻辑代码抽出来,放到controller层进行处理。

并且把同一种"类别"的放在一个控制器,比如把user相关的放到UserController,role相关的放到RoleController,这样目录结构会更加清晰,方便维护。

创建Controller

  1. 在根目录下创建controller目录,并且在目录里新建一个BaseController.js文件
// 定一个公共类,类里有一个renderJsonSuccess方法,方便返回数据

class BaseController {
    static renderJsonSuccess(code = 200, msg = '', data = []) {
        return {
            'code': code,
            'msg': msg,
            'data': data
        }
    }
}

module.exports = BaseController
  1. controller目录下新建User.js文件
const BaseController = require("./BaseController");

class UserController extends BaseController {
    static async getUser(ctx) {
        ctx.body = BaseController.renderJsonSuccess(200, '这是用户页')
    }

    static async getUserDetail(ctx) {
        const id = ctx.params.id
        let msg = '输入错误'
        let code = 200
        if (parseInt(id) === 666) {
            msg = '输入正确'
        }
        ctx.body = BaseController.renderJsonSuccess(code, msg, id)
    }
}
module.exports = UserController
  1. controller目录下新建Role.js文件
const BaseController = require("./BaseController");

class RoleController extends BaseController {
    static async getRole(ctx) {
        ctx.body = BaseController.renderJsonSuccess(200, '这是用户角色页')
    }

    static async getCurrectRole(ctx) {
        const name = ctx.request.body.name || ""
        let msg  = '你真帅'
        let code = 200
        if (!name || name !== 'lurengao') {
            code = 400
            msg = '输入有误'
        }
        ctx.body = BaseController.renderJsonSuccess(code, msg, name)
    }
}

module.exports = RoleController
  1. 修改router.js
const Router = require("koa-router")  // 引入路由
const router = new Router()
const UserController = require('../controller/User')
const RoleController = require('../controller/Role')


router.get('/', ctx => { ctx.body = "我最帅!" })
router.get('/user', UserController.getUser)
router.get('/user/:id', UserController.getCurrectUser)



router.get('/role', RoleController.getRole)
router.post('/role', RoleController.getCurrectRole)
module.exports = router

这样看下来更加简洁了,如果想要修改某个功能,直接定位到对应的控制器就可以了

Sequelize引入与配置

前期准备

Mysql的安装不再赘述,这里默认安装了Mysql

创建一个数据库,就叫它koa-test吧

create database koa-test

再新建两张表,一张叫user,另外一张也叫user叫role,并且添加几条真实的数据

DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID',
  `user` varchar(255) NOT NULL COMMENT '用户名',
  `status` tinyint(3) unsigned NOT NULL DEFAULT '1' COMMENT '状态',
  `role_id` int(11) unsigned NOT NULL COMMENT '关联role',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;



INSERT INTO `user` (`id`, `user`, `status`, `role_id`) VALUES (1, '吴彦祖', 1, 1);
INSERT INTO `user` (`id`, `user`, `status`, `role_id`) VALUES (2, '彭于晏', 2, 1);
INSERT INTO `user` (`id`, `user`, `status`, `role_id`) VALUES (3, '路人高', 1, 2);
DROP TABLE IF EXISTS `role`;
CREATE TABLE `role` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID',
  `name` varchar(255) NOT NULL COMMENT '角色名',
  `status` tinyint(3) unsigned NOT NULL DEFAULT '1' COMMENT '状态',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;


INSERT INTO `role` (`id`, `name`, `status`) VALUES (1, '帅哥', 1);
INSERT INTO `role` (`id`, `name`, `status`) VALUES (2, '大帅哥', 1);

database目录

新建database目录,在目录里新建config.js

const database = {
    // 服务器地址
    hostname: '127.0.0.1',
    // 数据库名
    database: 'koa-test',
    // 用户名
    username: 'koa-test',
    // 密码
    password: 'xxxx',
    // 端口
    hostport: '3306',
}
module.exports = database;

mysql连接池

  1. 连接数据库

安装mysql2

yarn add mysql2

在database目录下创建dbquery.js

const config = require("./config");
const pool = mysql.createPool({
  host: config.hostname, // 地址
  port: config.hostport, // 端口号
  user: config.username, // 用户名
  password: config.password, // 密码
  database: config.database, // 目标数据库
});

exports.query = function (sql) {
  return new Promise((resolve, reject) => {
    pool.execute(sql, (err, results) => {
        if (err) reject(err);
        resolve(results);
    });
  });
};
  1. 使用

还记得前面封装的UserController吗,打开Controller/User.js,修改getUser方法,简单查询一下

const db = require('../database/dbquery')

static async getUser(ctx) {
    let sql = `select * from user where status=1`
    const res = await db.query(sql)
    ctx.body = BaseController.renderJsonSuccess(200, res)
}
image.png

这里写了一个sql查询user表里status为1的数据,看到正常查询到了数据。

但是如果表很大字段比较多,再涉及到联合查询等操作,sql将会很臃肿,所以选择引入ORM操作数据库。

Sequelize介绍与引入

  1. 介绍与安装

Sequelize 是一个基于 promise 的 Node.js ORM, 目前支持 PostgresMySQLMariaDBSQLite 以及 Microsoft SQL Server. 它具有强大的事务支持, 关联关系, 预读和延迟加载,读取复制等功能。

安装

yarn add sequelize -D
  1. 配置 在database目录下新建一个db.js
const Sequelize = require("sequelize")
const config = require('./config')

console.log('init sequelize...')

const sequelize = new Sequelize(config.database, config.username, config.password, {
    host: config.hostname,
    port: config.hostport,
    dialect: 'mysql',
    pool: {
        max: 5, // 连接池最大连接数量
        min: 0, // 最小连接数量
        idle: 10000 // 如果一个线程10s内没有被用过就释放
    },
    define: {
        // schema和schemaDelimiter为表前缀,不需要可以删除
        // schema: 'koa',
        // 连接字符
        // schemaDelimiter: '-',
        // 是否自动添加时间戳
        timestamps: false,
        freezeTableName: true
    }
})


// 测试是否能连通
sequelize.authenticate().then(() => {
    console.log("连接成功");
}).catch(err => {
    console.log("连接失败", err);
});


module.exports = sequelize

运行,可以看到已经成功连接了数据库

image.png

Model层

在传统MVC框架中,M为Model数据模型,提供要展示的数据。

在项目根目录下创建model目录,在该目录下分别创建User.js、Role.js

// User.js

const {Sequelize, DataTypes} = require('sequelize')
const sequelize = require('../database/db')

// 数据类型 https://www.sequelize.com.cn/core-concepts/model-basics#%E6%95%B0%E6%8D%AE%E7%B1%BB%E5%9E%8B
const User = sequelize.define('user', {
    id: {
        type: DataTypes.INTEGER,
        primaryKey: true,
        autoIncrement: true
    },
    user: DataTypes.STRING,
    role_id: DataTypes.INTEGER,
    status: DataTypes.TINYINT
})

module.exports = User
// Role.js
const {Sequelize, DataTypes} = require('sequelize')
const sequelize = require('../database/db')

// 数据类型 https://www.sequelize.com.cn/core-concepts/model-basics#%E6%95%B0%E6%8D%AE%E7%B1%BB%E5%9E%8B
const Role = sequelize.define('role_id', {
    id: {
        type: DataTypes.INTEGER,
        primaryKey: true,
        autoIncrement: true
    },
    name: DataTypes.STRING,
    status: DataTypes.TINYINT
})

module.exports = Role

在上面的代码里,我们以sequelize.define(modelName, attributes, options)的方式分别定义了User和Article模型,接下来就可以愉快的操作数据库了

打开controller/User.js,修改代码

查询所有数据

const BaseController = require("./BaseController");
const UserModel = require('../model/User')


class UserController extends BaseController {
    static async getUser(ctx) {
        // let sql = `select * from user where status=1`
        // const res = await db.query(sql)
        const res = await UserModel.findAll()
        ctx.body = BaseController.renderJsonSuccess(200, res)
    }

    static async getCurrectUser(ctx) {
        const id = ctx.params.id
        let msg = '输入错误'
        let code = 200
        if (parseInt(id) === 666) {
            msg = '输入正确'
        }
        ctx.body = BaseController.renderJsonSuccess(code, msg, id)
    }
}
module.exports = UserController

请求一下,可以看到把所有数据都查出来了 image.png

试着给一个限制条件

const res = await UserModel.findAll({
    where: {
        status: 1
    }
})

image.png

分页

const res = await UserModel.findAll({
    where: {
        status: 1
    },
    limit: 1
})
image.png

关联查询出用户的角色名

user表里有一个外键role_id,通过外键查询到用户的角色名,在model/User.js里添加一段

// 建立关系
User.hasOne(Role, {
    foreignKey: 'id',
    sourceKey: 'role_id'
})

修改controller/User.js

const res = await UserModel.findAll({
    where: {
        status: 1
    },
    include: {
        model: Role
    }
})
image.png

更多ORM操作可以参考官网文档

英文文档:传送门

中文文档:传送门

总结

作为入门级的第一篇文章,简单介绍了从0开始搭建Koa2框架,把结构分成路由、控制器以及模型层级,引入Sequelize操作数据库等。目前我也在学习中,打算做成一个从入门到入土系列。