基于Knex的Node.js ORM库开发:ES6类下Knex实例依赖封装问询
我之前也折腾过类似的ORM封装,正好可以给你一个清晰的ES6类实现方案,既能自动注入Knex实例,又能保持代码可读性,完全避开Knex和Bookshelf那种绕来绕去的写法。
核心思路
核心是用一个全局ORM上下文类来持有Knex实例,然后让所有模型/组件通过继承或者工厂模式自动获取这个实例,用户只需要初始化一次ORM,之后不用再手动传递Knex对象。这种方式遵循单一职责原则,上下文类管全局实例,模型类管业务逻辑,代码结构非常清晰。
具体实现步骤
1. 编写ORM核心上下文类
这个类负责接收并管理Knex实例,同时提供模型注册的能力,确保所有模型都能拿到同一个Knex实例:
class ORM { constructor(knexInstance) { if (!knexInstance) { throw new Error('请传入已初始化的Knex实例'); } // 保存Knex实例作为全局上下文 this.knex = knexInstance; // 存储已注册的模型 this.models = {}; } // 注册模型:自动给模型注入Knex实例 register(modelName, ModelClass) { if (typeof ModelClass !== 'function') { throw new Error('ModelClass必须是ES6类'); } this.models[modelName] = new ModelClass(this.knex); return this.models[modelName]; } // 可选:提供快捷获取模型的方式 get(modelName) { return this.models[modelName]; } }
2. 编写基础模型类
所有业务模型都继承这个基类,基类封装通用的CRUD方法,同时自动接收Knex实例:
class BaseModel { constructor(knex) { // 继承的模型会自动拿到Knex实例 this.knex = knex; // 子类必须重写这个属性,指定对应的数据库表名 this.tableName = ''; } // 通用方法:根据ID查询单条数据 async findById(id) { if (!this.tableName) throw new Error('请设置tableName'); return this.knex(this.tableName).where('id', id).first(); } // 通用方法:保存数据(新增或更新) async save(data) { if (!this.tableName) throw new Error('请设置tableName'); if (data.id) { await this.knex(this.tableName).where('id', data.id).update(data); return { ...data }; } else { const [insertedId] = await this.knex(this.tableName).insert(data); return { ...data, id: insertedId }; } } // 可以继续添加更多通用方法:findAll, deleteById等 }
3. 编写业务模型
业务模型只需要继承BaseModel,指定表名,然后实现自己的自定义方法即可:
class UserModel extends BaseModel { constructor(knex) { super(knex); // 指定对应的数据库表 this.tableName = 'users'; } // 自定义方法:根据邮箱查询用户 async findByEmail(email) { return this.knex(this.tableName).where('email', email).first(); } // 自定义方法:获取用户的所有订单 async getOrders(userId) { return this.knex('orders').where('user_id', userId); } }
4. 用户使用方式
用户只需要初始化一次ORM,之后直接调用模型,完全不用手动传递Knex实例:
// 1. 用户自己初始化Knex实例 import knex from 'knex'; const knexInstance = knex({ client: 'postgresql', connection: { host: 'localhost', user: 'dev', password: '123456', database: 'my_project' } }); // 2. 初始化ORM上下文 const orm = new ORM(knexInstance); // 3. 注册业务模型 orm.register('User', UserModel); // 4. 使用模型(全程不用传Knex!) async function demo() { const User = orm.get('User'); // 查询用户 const user = await User.findById(1); console.log(user); // 新增用户 const newUser = await User.save({ name: 'Alice', email: 'alice@example.com' }); console.log(newUser); // 调用自定义方法 const userByEmail = await User.findByEmail('alice@example.com'); console.log(userByEmail); } demo();
进阶优化建议
- 自动注册模型:可以在ORM类中添加方法,自动从指定目录加载所有模型并注册,不用手动一个个register
- 事务支持:在ORM类中添加
transaction方法,创建事务实例后传递给模型,让模型在事务中执行操作 - 静态语法糖:可以给ORM类添加静态初始化方法,让用户写法更简洁:
const orm = ORM.init(knexInstance)
这种方案完全基于ES6类的特性,逻辑清晰,每个部分职责明确,比Knex/Bookshelf的回调式或者复杂的原型链写法好读太多,非常适合个人项目使用。
内容的提问来源于stack exchange,提问作者Sinthorion
相关产品推荐
相关产品推荐

