gaorajs
v0.1.1
Published
Laravel/Eloquent-style ORM for TypeScript. Works in NestJS, Express, or any JS/TS project.
Downloads
218
Maintainers
Readme
GaoraJs
Laravel/Eloquent-style ORM for TypeScript. Uses Knex as the query builder with support for PostgreSQL, MySQL/MariaDB, and SQLite (install only the driver you need). Includes an Artisan-like CLI and class-based migrations.
Works in any project: NestJS, Express, Fastify, or a standalone Node/TypeScript app. No framework lock-in.
Stack
- TypeScript (strict)
- Knex (query builder; drivers are peer optional)
- commander + @clack/prompts (CLI)
- tsup (CJS, ESM, .d.ts)
- reflect-metadata (loaded by the package when you import from gaorajs; needed for @Table, @Column, etc.)
- tsx (run migrations and commands in TS without compiling)
Installation
npm install gaorajs
# Install only the driver you use (one of):
npm install pg # PostgreSQL (recommended)
npm install mysql2 # MySQL / MariaDB
npm install sqlite3 # SQLite (e.g. testing)
# Optional:
npm install reflect-metadata # required for @Table, @Column, etc. (gaorajs loads it when you import)
npm install dotenv # if you want .env loaded from gaora.configSo: one install (npm install gaorajs pg) is enough for a typical PostgreSQL app. No need to install drivers you don't use.
Multi-database and multiple connections
Gaora supports PostgreSQL, MySQL/MariaDB, and SQLite with a single API. You define one or more connections; models use the default connection or you switch with Model.on('connectionName') or DB.connection('connectionName') (Laravel-style).
Single connection (legacy): use connection + connectionName in config as before.
Config structure: connection (default connection name) and connections (array of configs, each with a name):
const config: GaoraConfig = {
connection: 'default',
connections: [
{ name: 'default', client: 'pg', connection: { host: '...', database: 'app', ... }, pool: { min: 0, max: 10 } },
{ name: 'analytics', client: 'mysql2', connection: { host: '...', database: 'analytics', ... } },
],
databasePath: 'src/database',
};Usage:
// Default connection
const user = await User.find(1);
// Use another connection for this query
const log = await Log.on('analytics').create({ event: 'click' });
// Or set connection on the model class (like Laravel $connection)
class Log extends Model {
static connectionName = 'analytics';
}
// DB facade with connection
const rows = await DB.connection('analytics').table('events').get();
const knex = DB.connection('postgres-analytics');Migrations run on the default connection (the one set in config).
Configuration
Configuration can be file-based (standalone project) or programmatic (frameworks with their own config).
Option 1: Config file (standalone project)
Copy gaora.config.example.ts to gaora.config.ts in the project root, or run gaora init:
// gaora.config.ts
import type { GaoraConfig } from 'gaorajs';
const config: GaoraConfig = {
connection: 'default',
connections: [
{
name: 'default',
client: 'mysql2',
connection: {
host: process.env.DB_HOST ?? '127.0.0.1',
port: 3306,
user: process.env.DB_USER ?? 'root',
password: process.env.DB_PASSWORD ?? '',
database: process.env.DB_DATABASE ?? 'my_app',
},
pool: { min: 0, max: 10 },
},
],
databasePath: 'src/database', // or 'database' if the project does not use src
};
export default config;The CLI (gaora migrate, make:model, make:migration) will read this file when present. Config is auto-loaded from this file on first use of a model (no need to call anything). Optionally, call init() once at app startup:
import { init } from 'gaorajs';
await init(); // loads gaora.config and registers connectionsOption 2: Environment variables only
If you prefer no config file, build the connection from env and register it manually:
import { connectionFromEnv, ConnectionManager } from 'gaorajs';
ConnectionManager.addConnection('default', connectionFromEnv(process.env));Supported variables: DB_HOST, DB_PORT, DB_USER, DB_PASSWORD, DB_DATABASE (or MYSQL_* / PG*). Set DB_DRIVER=pg or DB_DRIVER=sqlite3 for PostgreSQL or SQLite; for SQLite use DB_FILENAME or DB_DATABASE as the file path.
Option 3: Programmatic config (NestJS, Express, etc.)
In frameworks that already have config (ConfigService, dotenv, etc.), you don't need gaora.config.ts. Just register the connection at app startup:
import { ConnectionManager } from 'gaorajs';
ConnectionManager.addConnection('default', {
client: 'mysql2',
connection: {
host: process.env.DB_HOST ?? '127.0.0.1',
port: 3306,
user: process.env.DB_USER ?? 'root',
password: process.env.DB_PASSWORD ?? '',
database: process.env.DB_DATABASE ?? 'app',
},
pool: { min: 0, max: 10 },
});Usage in different environments
Standalone project (Node + TypeScript)
npm install gaorajsCreate gaora.config.ts as above or run gaora init. In your entry (e.g. src/main.ts):
import { User } from './models/User.js';
// Config is auto-loaded on first use; or call await init() once at startup
const user = await (await User.where('id', 1)).first();NestJS
Install in the project: npm i gaorajs. You don't need a gaora.config.ts file if you already use ConfigService.
Register the connection in a provider (e.g. in AppModule or a database module):
// app.module.ts or database.module.ts
import { Module, OnModuleInit } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { ConnectionManager } from 'gaorajs';
@Module({})
export class DatabaseModule implements OnModuleInit {
constructor(private config: ConfigService) {}
onModuleInit() {
ConnectionManager.addConnection('default', {
client: 'mysql2',
connection: {
host: this.config.get('DB_HOST', '127.0.0.1'),
port: this.config.get('DB_PORT', 3306),
user: this.config.get('DB_USER', 'root'),
password: this.config.get('DB_PASSWORD', ''),
database: this.config.get('DB_DATABASE', 'app'),
},
pool: { min: 0, max: 10 },
});
}
}Use models in your services or controllers:
import { User } from './user.model';
const user = await (await User.where('id', 1)).first();For migrations from Nest, run the CLI as usual: npx tsx node_modules/.bin/gaora migrate (and optionally a gaora.config.ts for the CLI only, or --config pointing to a file).
Express / Fastify / others
Same pattern: at app startup (before defining routes), call ConnectionManager.addConnection('default', { ... }) once with your config (env, custom file, etc.). Then use models in your handlers.
Models
import { Model, Table, Column, Primary, Timestamps } from 'gaorajs';
@Table('users')
@Primary('id')
@Timestamps()
export class User extends Model {
@Column()
id!: number;
@Column()
email!: string;
}
const user = await (await User.where('id', 1)).first();
const users = await (await User.where('active', true)).get();
const u = await User.find(1);Models support magic attribute access (no need to declare every property): user.name, user.email = 'x'. Use fillable / guarded for mass assignment and casts for types:
@Table('users')
@Primary('id')
@Timestamps()
export class User extends Model {
static fillable = ['name', 'email'];
static casts = { email_verified_at: 'date', meta: 'json' };
// ...
}
const u = await User.create({ name: 'Jane', email: '[email protected]' });
await u.update({ name: 'Jane Doe' });Relationships (Laravel-style)
Define relations with BelongsTo, HasOne, HasMany, and BelongsToMany. Access as properties (returns a Promise); use eager loading with with():
import { Model, BelongsTo, HasMany } from 'gaorajs';
@Table('posts')
export class Post extends Model {
user() {
return new BelongsTo(this, User, 'user_id', 'id');
}
}
@Table('users')
export class User extends Model {
posts() {
return new HasMany(this, Post, 'user_id', 'id');
}
}
const user = await (await User.where('id', 1)).first();
const posts = await user.posts; // lazy load (Promise)
const users = await (await User.query()).with('posts').get(); // eager loadRelation types: BelongsTo(owner, RelatedModel, foreignKey, ownerKey), HasOne / HasMany(owner, RelatedModel, foreignKey, localKey), BelongsToMany(owner, RelatedModel, pivotTable, foreignPivotKey, relatedPivotKey, parentKey?, relatedKey?).
DB facade (Laravel-style)
Query tables without Eloquent models. Same API as Laravel's DB::table():
import { DB } from 'gaorajs';
// Configure connection first (see Configuration)
const users = await DB.table('users').where('id', 20).get();
const user = await DB.table('users').where('id', 1).first();
const email = await DB.table('users').where('name', 'John').value('email');
const count = await DB.table('users').count();
await DB.table('users').insert({ name: 'Jane', email: '[email protected]' });
const id = await DB.table('users').insertGetId({ name: 'Jane' });
await DB.table('users').where('id', 1).update({ name: 'John Doe' });
await DB.table('users').where('id', 1).increment('votes', 1);
await DB.table('users').where('id', 1).delete();
// Raw SQL and transactions
const rows = await DB.select('SELECT * FROM users WHERE active = ?', [1]);
await DB.transaction(async (trx) => {
await trx('users').insert({ name: 'A' });
await trx('orders').insert({ user_id: 1 });
});TableQueryBuilder methods: where, orWhere, whereIn, whereNotIn, whereNull, whereNotNull, whereBetween, select, orderBy, orderByDesc, groupBy, having, limit, offset, skip, take, join, leftJoin, rightJoin, get(), first(), find(id), value(column), pluck(column, key?), count(), max(), min(), sum(), avg(), exists(), doesntExist(), insert(), insertGetId(), update(), increment(), decrement(), delete(), truncate(), selectRaw(), whereRaw(), orderByRaw(), distinct().
DB facade: DB.table(name, connection?), DB.connection(name?), DB.raw(sql, bindings?), DB.transaction(callback), DB.select(sql, bindings?), DB.statement(sql, bindings?).
Migrations (class-based, Laravel-style)
Migrations are classes with async up(schema: Schema) and async down(schema: Schema).
schema.create(name, callback)— create a new table (like LaravelSchema::create).schema.table(name, callback)— alter an existing table: add or modify columns (like LaravelSchema::table).
// Create a table
await schema.create('users', (table) => {
table.id();
table.string('name');
table.string('email').nullable().unique();
table.integer('votes').unsigned().defaultTo(0);
table.timestamps();
table.softDeletes();
});
// Add columns to an existing table
await schema.table('users', (table) => {
table.string('phone').nullable();
table.integer('score').unsigned().defaultTo(0);
});
// In down(), remove them
await schema.table('users', (table) => {
table.dropColumn('phone');
table.dropColumn('score');
});Column methods return a chainable builder: .nullable(), .default(), .unique(), .unsigned(), .index(), .comment(), .after(). Table-level: table.index(), table.unique(), table.foreign('col').references('id').on('users'), table.dropColumn().
gaora migration create create_users_table # or: make:migration create_users_table
gaora migrate # run pending
gaora migrate rollback # roll back last batch
gaora migrate fresh # drop tables and migrate
gaora migrate refresh # roll back all and migrateStubs (Laravel-style code generation)
Code generation uses stub files. The package ships with default stubs in stubs/:
migration.stub— used bymake:migrationandmake:model -mmodel.stub— used bymake:modelseeder.stub— used bymake:seederfactory.stub— for model factories (placeholder)config.stub— config template (used by init)
Placeholders like {{ className }}, {{ tableName }} are replaced when generating files. User stubs take precedence: if your project has a stubs/ folder (e.g. after running gaora stub:publish), those files are used instead of the package defaults. Customize the published stubs to match your style.
gaora stub:publish # Copy default stubs to project stubs/
gaora stub:publish --force # Overwrite existing stubsCLI (Artisan style)
gaora init— Initialize GaoraJs (gaora.config + database folder).gaora make:model User— Create a model (uses model.stub).gaora make:model User -m— Model + migration.gaora make:migration <name>/gaora migration create <name>— Create a migration (uses migration.stub).gaora make:seeder <name>— Create a seeder class (uses seeder.stub).gaora stub:publish— Publish default stubs to your project for customization.- Migrations (grouped commands):
gaora migrate— Run pending migrations.gaora migrate rollback— Roll back the last batch.gaora migrate fresh— Drop all tables and run migrations.gaora migrate refresh— Roll back all and run them again.
Options: --dir, --config, --cwd depending on the command.
If no gaora.config.ts exists, make:model, make:migration and migrate will create one with default settings (no prompts).
Running the CLI
The npm package is named gaorajs (not gaora). If the library isn't published yet or you're developing in the repo:
From the repo root (folder
gaora/):npm run gaora -- make:model User npm run gaora -- make:migration create_posts_table npm run gaora -- migrateThe
--is required to pass arguments to the script.From a test/consumer project (e.g. folder
test/):npm run gaora -- make:model User npm run gaora -- migrateAfter publishing to npm: in any project with
gaorajsinstalled you can usenpx gaorajs make:model Useror thegaorabinary if installed globally (npm install -g gaorajs).
Config API (for integrators)
import {
loadConfigFromFile,
applyConfig,
connectionFromEnv,
type GaoraConfig,
type LoadConfigOptions,
} from 'gaorajs';
// Load from file (gaora.config.ts/js)
const config = await loadConfigFromFile({ cwd: process.cwd(), configPath: 'gaora.config.ts' });
// Apply to ConnectionManager
if (config) applyConfig(config);
// Connection from process.env
const conn = connectionFromEnv(process.env);
ConnectionManager.addConnection('default', conn);GaoraJs project structure (development)
gaora/
├── bin/gaora.ts
├── src/
│ ├── core/ # Model, QueryBuilder, Schema, TableBuilder
│ ├── config/ # GaoraConfig, loadConfigFromFile, applyConfig, .env
│ ├── decorators/
│ ├── cli/
│ │ ├── logger.ts # Unified logger (success, error, info, spinner)
│ │ ├── projectRoot.ts
│ │ └── commands/ # init, make:model, make:migration, migration create, migrate*
│ ├── templates/ # Templates for generated files
│ │ ├── index.ts # getConfigTemplate, getMigrationTemplate, getModelTemplate
│ │ ├── config.ts
│ │ ├── migration.ts
│ │ └── model.ts
│ ├── database/
│ └── index.ts
├── gaora.config.example.ts
└── package.jsonScripts (library development)
npm run build— Buildsdist/(CJS, ESM, types)npm run dev— Build in watch mode
