Why Upgrade?
Raw Knex is great for flexibility, but as your app grows you end up writing the same boilerplate everywhere: mapping rows to objects, managing relationships, handling timestamps, and duplicating query logic. IlanaORM is built on top of Knex — so it keeps all the power while eliminating the repetition.
IlanaORM is 100% compatible with raw Knex. You can migrate incrementally — one model at a time.
Basic Queries
const users = await knex('users').select('*');
const user = await knex('users').where('id', 1).first();
const admins = await knex('users')
.where('role', 'admin')
.orderBy('created_at', 'desc')
.limit(10);
Creating Records
const [id] = await knex('users').insert({
name: 'Alice',
email: 'alice@example.com',
created_at: new Date(),
updated_at: new Date(),
});
const user = await knex('users').where('id', id).first();
Updating Records
await knex('users')
.where('id', 1)
.update({ name: 'Alicia', updated_at: new Date() });
Soft Deletes
// Soft delete
await knex('posts').where('id', 1).update({ deleted_at: new Date() });
// Query excluding deleted
const posts = await knex('posts').whereNull('deleted_at');
// Restore
await knex('posts').where('id', 1).update({ deleted_at: null });
Relationships
// Manual join
const usersWithPosts = await knex('users')
.leftJoin('posts', 'users.id', 'posts.user_id')
.select('users.*', knex.raw('COUNT(posts.id) as post_count'))
.groupBy('users.id');
// Separate query + manual mapping
const user = await knex('users').where('id', 1).first();
const posts = await knex('posts').where('user_id', user.id);
user.posts = posts;
Transactions
const trx = await knex.transaction();
try {
const [userId] = await trx('users').insert({ name: 'Alice' });
await trx('posts').insert({ user_id: userId, title: 'Hello' });
await trx.commit();
} catch (err) {
await trx.rollback();
throw err;
}
const page = 1;
const perPage = 10;
const offset = (page - 1) * perPage;
const rows = await knex('posts').limit(perPage).offset(offset);
const [{ count }] = await knex('posts').count('* as count');
const result = {
data: rows,
total: parseInt(count),
currentPage: page,
lastPage: Math.ceil(count / perPage),
};
Accessing Raw Knex When You Need It
IlanaORM doesn’t lock you out of Knex. Escape hatch when you need it:
// Get raw Knex query builder from a QB instance
const knexQuery = Post.query().where('published', true).toKnex();
// Run completely raw SQL
import { Database } from 'ilana-orm/database/connection.js';
const db = Database.connection('default');
const rows = await db.raw('SELECT * FROM posts WHERE MATCH(body) AGAINST(?)', ['search term']);
Migration Path
- Create a
Model class for each table you want to migrate
- Replace
knex('table').select(...) calls with Model.query()...
- Replace manual timestamp management with
static timestamps = true (default)
- Replace manual soft-delete filters with
static softDeletes = true
- Replace manual relation joining with
with(...) eager loading
- Keep raw Knex for complex reporting queries — use
Model.query().toKnex() to mix