前言
首先,我们对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: stringconstructor(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) * pageSizeconst { id } = paramsconst sql_params: Object | Boolean = !!params ? { 'table1.id': id } : 1==1let 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?.bodylet 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是一个方便的数据库操作库,可以多多使用它的一些方法