Skip to main content

Examples & Recipes

End-to-end walkthroughs demonstrating how Ormed pieces work together.

Prerequisites

What You’ll Learn

  • How a full Ormed workflow fits together end-to-end
  • How generated runtime helpers replace manual boot glue
  • How to adapt the recipe for your own app layout

Step Outcome

By the end of this page, you should have a repeatable end-to-end workflow you can copy into a new or existing service.

End-to-End SQLite Workflow

1. Dependencies (pubspec.yaml)

dependencies:
ormed: ^0.2.0
ormed_sqlite: ^0.2.0
dev_dependencies:
build_runner: ^2.10.4
ormed_cli: ^0.2.0

2. Bootstrap CLI

dart run ormed_cli:ormed init

This scaffolds lib/src/database/config.dart and lib/src/database/datasource.dart for code-first runtime wiring.

3. Model (lib/user.dart)

// #region intro-model
import 'package:ormed/ormed.dart';

part 'user.orm.dart';

(table: 'users')
class User extends Model<User> {
const User({
required this.id,
required this.email,
this.name,
this.createdAt,
});

(isPrimaryKey: true, autoIncrement: true)
final int id;

final String email;
final String? name;
final DateTime? createdAt;
}

// #endregion intro-model

4. Generate

dart run build_runner build --delete-conflicting-outputs

This generates *.orm.dart model helpers plus lib/src/database/orm_registry.g.dart.

5. Create and apply migrations

dart run ormed_cli:ormed make:migration --name create_users --create --table users
# Edit the generated file
dart run ormed_cli:ormed migrate

6. Query

This is the “put it together” step: initialize the data source, run a query, then map results.

Quick Validation Checklist

  1. build_runner succeeds and generated artifacts are present.
  2. Migration applies cleanly.
  3. Seeder runs cleanly.
  4. At least one typed query returns expected records.

Static Helpers Pattern

After await dataSource.init(), generated static helpers use the default connection automatically:

Future<void> staticHelpersPattern() async {
final dataSource = createDefaultDataSource();
await dataSource.init();

// Now static helpers work automatically!
final users = await Users.query().get();
final post = await Posts.find(1);

await dataSource.dispose();
}

QueryContext (Advanced)

Future<void> queryContextExample() async {
final registry = bootstrapOrm();
final context = QueryContext(
registry: registry,
driver: SqliteDriverAdapter.inMemory(),
);

final users = await context.query<$User>().get();
print('Users: ${users.length}');
}

Observability Example

Future<void> observabilityExample() async {
final dataSource = DataSource(
DataSourceOptions(
name: 'primary',
driver: SqliteDriverAdapter.file('database.sqlite'),
entities: generatedOrmModelDefinitions,
logging: true,
),
);
await dataSource.init();

// Log all queries
dataSource.onQuery((entry) {
print('Query: ${entry.sql}');
print('Duration: ${entry.duration}ms');
});

await dataSource.query<$User>().get();
}

Output:

{
"type": "query",
"model": "User",
"sql": "SELECT \"id\", \"email\" FROM \"users\" WHERE \"id\" = ?",
"parameters": [1],
"duration_ms": 0.42
}

Working with Relations

Eager Loading

Future<void> eagerLoadingExample(DataSource dataSource) async {
// Load posts with their authors and comments
final posts = await dataSource
.query<$Post>()
.with_(['author', 'comments'])
.whereEquals('published', true)
.orderBy('createdAt', descending: true)
.limit(10)
.get();

for (final post in posts) {
print('${post.title} by ${post.author?.name}');
print('Comments: ${post.comments?.length}');
}
}

Eager Loading Aggregates

Future<void> eagerAggregatesExample(DataSource dataSource) async {
// Load users with post counts (without loading all posts)
final users = await dataSource.query<$User>().get();

for (final user in users) {
await user.loadCount(['posts']);
print('${user.name}: ${user.getAttribute<int>("posts_count")} posts');
}

// Or with withCount (if available on query builder)
// final users = await dataSource.query<$User>()
// .withCount(['posts'])
// .get();
}

Lazy Loading

Future<void> lazyLoadingExample(DataSource dataSource) async {
final post = await dataSource.query<$Post>().firstOrFail();

// Lazy load when needed
await post.load(['author']);
print('Author: ${post.author?.name}');

// Load multiple if not already loaded
await post.loadMissing(['tags', 'comments']);

// Nested relation loading
await post.load(['comments.author']);
}

Lazy Loading Aggregates

Future<void> lazyLoadingAggregatesExample(DataSource dataSource) async {
final post = await dataSource.query<$Post>().firstOrFail();

await post.loadCount(['comments']);
print('Comments: ${post.getAttribute<int>("comments_count")}');

await post.loadSum(['orders'], 'total', alias: 'order_total');
print('Total: \$${post.getAttribute<num>("order_total")}');
}

Relation Mutations

Future<void> relationMutationsExample(DataSource dataSource) async {
final post = await dataSource.query<$Post>().firstOrFail();

// BelongsTo: Associate & Dissociate
final author = await dataSource
.query<$User>()
.whereEquals('email', 'john@example.com')
.firstOrFail();
post.associate('author', author);
await post.save();

post.dissociate('author');
await post.save();

// ManyToMany: Attach, Detach & Sync
await post.attach('tags', [1, 2, 3]);
await post.detach('tags', [1]);
await post.sync('tags', [4, 5, 6]);

// With pivot data
await post.attach(
'tags',
[7],
pivotData: {'created_at': DateTime.now(), 'priority': 'high'},
);
}

Batch Loading

Future<void> batchLoadingExample(DataSource dataSource) async {
final posts = await dataSource.query<$Post>().limit(10).get();

// Single query loads all authors
await Model.loadRelations(posts, 'author');

// Load multiple relations
await Model.loadRelationsMany(posts, ['author', 'tags', 'comments']);

// Load only missing relations
await Model.loadRelationsMissing(posts, ['author', 'tags']);
}

Preventing Lazy Loading

Future<void> preventLazyLoadingExample(DataSource dataSource) async {
const environment = 'development';
final post = await dataSource.query<$Post>().firstOrFail();

// Enable in development
if (environment != 'production') {
ModelRelations.preventsLazyLoading = true;
}

// Now any lazy load throws
try {
await post.load(['author']); // Throws!
} on LazyLoadingViolationException catch (e) {
print('Blocked: ${e.relationName} on ${e.modelName}');
}
}

Manual Join Recipe

Future<void> manualJoinExample(DataSource dataSource) async {
final recentPosts = await dataSource
.query<$Post>()
.join('authors', (join) {
join.on('authors.id', '=', 'posts.author_id');
join.where('authors.active', true);
})
.joinRelation('tags')
.select(['authors.name', 'rel_tags_0.label'])
.orderBy('posts.published_at', descending: true)
.limit(5)
.rows();

final summaries = recentPosts.map((row) {
final model = row.model;
final author = row.row['author_name'];
final tag = row.row['tag_label'];
return '${model.title} by $author (${tag ?? "untagged"})';
});
}

Seeding Data

Future<void> seedingDataExample(DataSource dataSource) async {
// Truncate and seed
await dataSource.repo<$User>().query().delete();

final admin = await dataSource.repo<$User>().insert(
$User(id: 0, email: 'admin@example.com', name: 'Admin'),
);

await dataSource.repo<$Post>().insertMany([
$Post(id: 0, userId: admin.id, title: 'Hello', content: '...'),
$Post(id: 0, userId: admin.id, title: 'Another', content: '...'),
]);
}

Testing Tips

  • Use SqliteDriverAdapter.inMemory() for fast tests without a real database
  • Attach listeners to context.onQuery/.onMutation to assert behavior
  • For migrations, use MigrationRunner with a fake ledger to verify ordering
  • For Postgres integration tests, use PostgresTestHarness which spins up a schema per test

Verify Recipe In CI

Run this sequence in CI to keep examples honest:

dart run build_runner build --delete-conflicting-outputs
dart run ormed_cli:ormed migrate
dart run ormed_cli:ormed seed
dart test

Read This Next