前言
首先,我们对nodejs、express、knex、mysql进行说明:
- Node.js:Node.js 是一个开源的、跨平台的 JavaScript 运行时环境。
- express:Node.js web application framework 基于nodejs的web应用框架
- Knex:SQL Query Builder for Javascript 适用于 Javascript 的 SQL 查询生成器
- MySQL:关系型数据库
可见,者就是一个使用JavaScript搭建的后台系统。实现总共包括以下几步:
- 安装依赖
- 编写启动文件
- 封装kenx类进行数据库操作
- 编写接口
按照以上步骤,我们对每一个过程进行介绍:
1. 安装依赖
基于该技术栈搭建一个基础的后台系统,需要安装的依赖有:
npm i ts-node --save
npm i ts-loader --save
npm i typescript --save
npm i express --save
npmp i knex --save
npm i mysql2 --save
安装这些依赖之前,首先要确定正确下载安装nodejs,并配置环境变量。查看是否具有Nodejs的命令为:node --version
。
- 安装
typescript
、ts-node
、ts-loader
是因为本实例中我们使用的是typescript编写的。您也可以不使用typescript,就不需要安装上述三个依赖; mysql2
是用来链接数据库的依赖包。据本次项目的经验,使用knex时,如果使用mysql
客户端,在部署到服务器的容器中会出现客户端版本不兼容的报错,所以我们选择了使用mysql2
。您也可以根据自己的需要和安装部署的基本条件,选择使用mysql
客户端。
2.编写服务启动文件
启动文件是用于服务启动的入口,包括监听服务启动端口、注入路由(接口文件)及后端服务的其它设置。我们假设启动文件的名称为app.ts
,则ts的启动命令就为ts-node app.ts
。启动文件的内容如下:
import express from 'express'
import bodyParser from 'body-parser'
import UserRouter from './router/user'
import RoleRouter from './router/role'
const app = express()
const BASE_ROUTER = '/router-base-url'
app
.use(bodyParser.json())
.use(bodyParser.urlencoded({ extended: false }))
app.use(`${BASE_ROUTER}`, UserRouter)
app.use(`${BASE_ROUTER}`, RoleRouter)
app.listen('8080', () => {
console.log('server started at 8080')
})
在启动文件中:
- 首先引入
express
,并初始化和监听服务启动端口,本示例中使用的是8080端口 body-parser
是express的一个中间件,用于对post请求的请求体进行解析- 我们引入了两个接口文件
./router/user
和./router/role
,并注入到框架中,使用的是app.use()
方法
这样一来,只需要执行ts-node app.ts
命令,我们就可以启动该服务了。下面我们开始编写接口文件。
3. 数据库连接
在编写接口文件之前,首先需要确定数据库的连接,使用knext
框架。
import config from "../config/db";
import knex from "knex";
const kenxConfig = {
client: 'mysql2',
connection: {
host: config.host,
port: config.port,
user: config.user,
password: config.password,
database: config.database,
timezone: '+08:00'
},
log: {
error(message: any) {
console.log('[knex] error', message)
},
warn(msg:any) {
const ignore = '.returning() is not supported by mysql and will not have any effect.'
if (msg.indexOf(ignore) === -1) {
console.info('[knex] warn', msg)
}
},
debug(msg:any) {
console.log('[knex] debug', msg)
},
deprecate(method: string, alternative: string) {
console.log(method, alternative)
}
}
}
export default knex(kenxConfig)
引入knex
包,在连接时需要指定mysql客户端、配置连接信息(包括数据库域名、端口号、用户名、密码、时区等),还可以设置数据库连接的日志信息,本次示例中我们添加了error
、warn
、debug
和deprecate
。其中:
- 在
warn
中,我们配置了.returning() is not supported by mysql and will not have any effect.
日志不打印,这是kenx的日志。
最后将knex(kenxConfig)
导出,用于在数据库查询方法中调用。
4. 数据库查询
knex
内置了很多基础方法,如数据库.select()
、.insert()
、.del()
等,可以直接使用。也可以直接编写sql传入,对于复杂的数据库语言,就需要我们编写sql语言或将多个knex方法结合使用。knex中直接使用sql语言可以使用.raw()
方法。
下列是我们所封装的knex基础类,其中只包括了.select()
、.insert()
、.update()
,其它方法可以参考Knex官网进行自行封装,我们将该文件命名为base.ts
。
import knex from "./knex";
class Base {
table: string
constructor(props: any) {
this.table = props
}
// 查找
all() {
return knex(this.table).select()
}
// 更新
update(update: Record<string,any>, params: Record<string,any>) {
return knex(this.table).where(update).update(params)
}
insert(params: Record<string, any>) {
return knex(this.table).insert(params)
}
}
export default Base
这是一个基于ES6的类,调用该类可以传入一个参数,并将其赋值给table,所以应该传入数据库表名。
knex(this.table).select()
从this.table
中查询全部数据knex(this.table).where(update).update(params)
用于对数据库update。update是更新条件的参数,params是跟新参数,均为对象格式。knex(this.table).insert(params)
向表中插入数据,params是插入参数,也为对象格式。
上述是封装的基础类,还可以封装一些复杂的方法。如下列代码封装了一个联表查询的方法queryList
,这是一个.ts文件,可以看到,该查询中分别关联了三个表table1
、table2
、table3
,并且进行的分页查询。
将该文件命名为base-query.ts
import Base from "./base";
import knex from "./knex";
class QueryModule extends Base {
constructor(props = '') {
super(props)
}
async queryList(page: number, pageSize: number, params: Record<string, any>) {
return new Promise(async(resolve, reject) => {
const start = (page - 1) * pageSize
const { id } = params
const sql_params: Object | Boolean = !!params ? { 'table1.id': id } : 1==1
let list = await knex('table1').where(sql_params).where('table1.flag', '=', 1).where('table1.statue', '=', 1)
.limit(pageSize).offset(start)
.leftJoin('table3', function() {
this.on(`table1.id`, '=', 'table3.id ')
})
.leftJoin('table2', function() {
this.on('table3.group_id', '=', 'table2.id')
})
.select('table1.*', 'table2.name as name2', 'table2.description as description2', 'table2.id as groupId')
.orderBy('table1.created_at', 'desc')
resolve(list)
})
}
}
const query = new QueryModule()
export default query
在上述方法中,我们还借助了promise,将查询到的结果通过resolve
方法返回,便于在接口实现部分进行调用。
5. 编写接口
下面的代码我们封装了一个query-list
的post请求接口。首先引入上一步封装好的类,并调用该方法即可。
import express from "express";
import queryModule from './base-query'
const router = express.Router()
router.post('/query-list', async(req:any, res:any, next: any) => {
const { page, pageSize, param } = req?.body
let data = await queryModule.queryList(page * 1, pageSize * 1, param)
const list = JSON.parse(JSON.stringify(data))
const r = new R()
res.send(r.ok(list))
})
说明:
R
类是一个封装的接口响应格式的类,可以自行封装。从代码中可以看出,该类的.ok方法是用于响应正确的结果,还有.err方法对失败结果进行响应。- 这个文件就是接口请求的controller类,你也可以将具体实现封装为单独的module进行调用,上述代码是我们简化过的一个方法。
总结
在这个过程中,你还需要:
- 使用ts的一个好处就是,可以明确的知道每个方法的参数格式、返回格式。如在封装kenx类时,每一个
.select()
、.insert()
、.del()
的参数都可以很明确的知道是对象格式Record<string,any>
。毕竟是使用经封装过的框架,对于不熟悉的人或时间太久再次阅读代码,这种格式可以很方便的提示开发者,该方法中的参数是什么格式。 - 该项目的启动需要正确配置
typescript
,才能够使用。否则无法使用import
或export
等命令。 - knex封装了很多个基础的方法,可以查看它的官方文档(地址:knex操作指南)。我们还使用了它的很多高级用法,在本示例中没有体现,如使用事务
knex.transaction
等。 - knex是一个方便的数据库操作库,可以多多使用它的一些方法