我正在参与掘金创作者训练营第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
使用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
添加第二个路由
接下来再添加一个路由入口,跟在第一个路由入口后面就可以啦
router.get('/user', ctx => {
ctx.body = "这是用户页"
})
封装路由
现在看一下整体的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层
一个简单的测试
在前面我们创建了两个路由,请求不同的路由地址,会得到不同的结果,在这个基础上我们稍微的扩展一下。
- 新建一个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': []
}
}
})
- 新建一个get请求的路由,通过该路由获得角色页
router.get('/role', ctx => { ctx.body = "这是用户角色页" })
- 新建一个post请求的路由,并且输入name为lurengao,不输入或者输入不正确则返回失败信息
router.post('/role', ctx => {
console.log(ctx.request.body)
})
输入一个name测试一下
发现打印不出来数据
因为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!
这个接口需要判断输入的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': []
}
}
})
做个简单测试
未传参数
参数错误
正确输入
仔细观察一波,总共五个路由,分别对user、role进行了处理,这时候代码量已经非常多了,等到有几十上百个路由时,再想对某个路由的逻辑进行修改就非常不方便,因此这里可以把逻辑代码抽出来,放到controller层进行处理。
并且把同一种"类别"的放在一个控制器,比如把user相关的放到UserController,role相关的放到RoleController,这样目录结构会更加清晰,方便维护。
创建Controller
- 在根目录下创建controller目录,并且在目录里新建一个BaseController.js文件
// 定一个公共类,类里有一个renderJsonSuccess方法,方便返回数据
class BaseController {
static renderJsonSuccess(code = 200, msg = '', data = []) {
return {
'code': code,
'msg': msg,
'data': data
}
}
}
module.exports = BaseController
- 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
- 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
- 修改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连接池
- 连接数据库
安装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);
});
});
};
- 使用
还记得前面封装的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)
}
这里写了一个sql查询user表里status为1的数据,看到正常查询到了数据。
但是如果表很大字段比较多,再涉及到联合查询等操作,sql将会很臃肿,所以选择引入ORM操作数据库。
Sequelize介绍与引入
- 介绍与安装
Sequelize 是一个基于 promise 的 Node.js ORM, 目前支持 Postgres, MySQL, MariaDB, SQLite 以及 Microsoft SQL Server. 它具有强大的事务支持, 关联关系, 预读和延迟加载,读取复制等功能。
安装
yarn add sequelize -D
- 配置 在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
运行,可以看到已经成功连接了数据库
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
请求一下,可以看到把所有数据都查出来了
试着给一个限制条件
const res = await UserModel.findAll({
where: {
status: 1
}
})
分页
const res = await UserModel.findAll({
where: {
status: 1
},
limit: 1
})
关联查询出用户的角色名
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
}
})
更多ORM操作可以参考官网文档
英文文档:传送门
中文文档:传送门
总结
作为入门级的第一篇文章,简单介绍了从0开始搭建Koa2框架,把结构分成路由、控制器以及模型层级,引入Sequelize操作数据库等。目前我也在学习中,打算做成一个从入门到入土系列。