Kex is a query extension for Knex. It uses the concept of "model" from ORMs like Lucid, or Laravel Eloquent restricted only to make queries. It has support for scopes, plugins, relations, and many more.
Install the package:
npm i @baethon/kex
Set up Kex instance:
const knex = require('knex')({ /* ... */ })
const { Kex } = require('@baethon/kex')
const kex = new Kex({ knex })
Create first model:
const User = kex.createModel('User')
Kex models uses the Knex query builder. To start the query, use query()
method:
const usersList = await User.query()
The query object is chainable:
const activeUsers = await User.query()
.where({ active: true })
In some cases, you can omit the query()
method and start chaining using following methods:
where()
whereIn()
insert()
returning()
const activeUsers = await User.where({ active: true })
Unlike Knex, the models don't create a query when using other methods (e.g. andWhere()
).
await User.insert({ name: 'Jon' })
As in Knex, you should use returning()
when you want to receive the returning fields:
const [id] = await User.returning('id')
.insert({ name: 'Jon' })
User.where({ active: true })
.update({ active: false })
// to make an update of a single item you can use
// following query
User.find(userId)
.update({ active: true })
Scope is a function that alters the query. You can chain them as other query methods.
Scopes are declared when creating a model:
const User = kex.createModel('User', {
scopes: {
active (qb) {
qb.where('active', true)
}
}
})
const activeUsers = await User.query()
.active()
Scopes can be used in the callbacks of where()
:
const usersList = await User.where(qb => {
qb.active()
.orWhere('role', 'admin')
})
Global scope is very similar to the regular scope. The main difference is that it's applied automatically to every query.
const User = kex.createModel('User', {
globalScopes: {
active (qb) {
qb.where('active', true)
}
}
})
const activeUsers = await User.query()
It's possible to ignore the scope using withoutGlobalScope()
, or withoutGlobalScopes()
method:
const usersList = await User.query()
.withoutGlobalScope('active')
// alternative syntax:
// .withoutGlobalScopes(['active'])
Kex supports many other things:
Kex uses naming conventions taken from Lucid, or Eloquent:
- the table name is a snake_case version of the pluralized model name (e.g.
users
) - the primary key is always
id
- foreign keys (used in relations) are snake_case version of a table name (in singular form) postfixed with
_id
(e.g.user_id
) - the pivot table is a snake_case version of related table names (in singular form) joined in alphabetical order (e.g.
tag_user
)
The naming is fully customizable.
Full-blown ORMs offer fantastic features. Yet sometimes, it might be overkill for your application. What I needed is a customizable DBAL which wouldn't make too much magic behind the scenes. I wanted something like Knex, yet a little more robust.
Kex gives you the feeling of an ORM. However, it makes sure not to interfere with your work. Build a query and fetch the results. That's all. Don't worry about hydrated objects, results collections, or anything like that.
The test suite is a combination of unit tests and integration tests. The latter use by default an SQLite database, however, you can (and sometimes must) choose a different database backend.
To run tests:
yarn test
Running a single test suite with:
yarn test tests/path/to/test-file.test.js
You need to install client dependency:
mysql
for MySQL databasepg
for PostgreSQL database
Then, start the database and set env variables. The test suite supports two env variables:
DB_CLIENT
(eithermysql
orpg
)DB_URL
(e.g.mysql://user:passwd@host:3306/db_name
)
DB_CLIENT=mysql DB_URL=mysql://user:passwd@host:3306/db_name yarn test