Skip to main content

Start with the database

Code generation is optional. If you are bootstrapping a service, exploring an existing schema, or writing a small worker, connect to a driver and use the database facade directly. You can add generated models later without changing the underlying connection or query pipeline.

Install only what you need

For a local SQLite database:

dependencies:
ormed: ^0.3.0
ormed_sqlite: ^0.4.0

No build_runner, generated registry, or ormed_cli is required for this path. See Drivers for PostgreSQL, MySQL/MariaDB, and D1 connection helpers.

Connect

Driver-specific helpers return the same OrmDatabase facade:

Future<OrmDatabase> connectWithoutCodegen() {
return SqliteDatabase.connect(path: ':memory:');
}

The facade owns the connection lifecycle. Close it when the process or request scope is finished:

final db = await SqliteDatabase.connect(path: 'database/app.sqlite');
try {
// use db
} finally {
await db.close();
}

Create a schema and run SQL

Use executeSchema for an immediate schema plan and executeRaw / queryRaw when you already have SQL or need a database-specific statement:

Future<List<Map<String, Object?>>> directSchemaAndSql(OrmDatabase db) async {
await db.executeSchema((schema) {
schema.create('users', (table) {
table.id();
table.string('name');
table.boolean('active').defaultValue(true);
});
});

await db.executeRaw(
'INSERT INTO users (name, active) VALUES (?, ?)',
['Ada', true],
);
return db.queryRaw('SELECT id, name FROM users WHERE active = ?', [true]);
}

For changes that must be tracked and deployed repeatedly, use the migration runner instead of treating executeSchema as a durable migration. The same facade exposes migrate(...) when you want manually registered migration entries without a generated registry.

Query a table without a model

db.table('users') returns Query<AdHocRow>. Rows are map-backed and support the normal fluent filters, ordering, pagination, and streaming APIs:

Future<List<AdHocRow>> directTableQuery(OrmDatabase db) {
return db
.table('users')
.whereEquals('active', true)
.orderBy('name')
.get();
}

You do not need to describe every column up front. Add AdHocColumn metadata only when you want explicit Dart type hints, a primary-key declaration, a custom codec, or a column alias:

Future<List<AdHocRow>> directColumnMetadata(OrmDatabase db) {
return db
.table(
'users',
columns: const [
AdHocColumn(name: 'id', dartType: 'int', isPrimaryKey: true),
AdHocColumn(name: 'name', dartType: 'String'),
],
)
.get();
}

This keeps the common path lightweight while leaving a typed escape hatch for dynamic or legacy tables.

Transactions

The same handle wraps raw and table-builder work in one transaction:

Future<void> directTransaction(OrmDatabase db) async {
await db.transaction(() async {
await db.executeRaw(
'INSERT INTO users (name, active) VALUES (?, ?)',
['Grace', true],
);
await db.executeRaw(
'UPDATE users SET active = ? WHERE name = ?',
[false, 'Ada'],
);
});
}

Nested query-builder operations, raw SQL, schema plans, and migrations can all share the connection’s execution hooks and interceptors.

Add generated models later

When a table becomes part of your domain, introduce a model and registry:

  1. Add ormed_cli and build_runner as development dependencies.
  2. Define an annotated model and run dart run build_runner build.
  3. Pass the generated registry to OrmDatabase.connect, or move to the generated DataSource scaffold.

The driver-first facade remains useful for raw SQL, ad-hoc tables, migrations, and database-specific maintenance even after code generation is enabled.

Next steps