Getting Started topic
Getting Started
This walks through the full migration-first workflow: configure → migrate →
generate schema → write models → generate mappers → query. It mirrors the
runnable example/ app in this repository.
1. Dependencies
The packages are unpublished, so depend on them via path: (within this repo)
or git::
dependencies:
basalt:
basalt_sqlite:
dev_dependencies:
basalt_cli: # the `basalt` CLI
basalt_codegen: # build_runner derives
build_runner: ^2.4.0
Dart SDK: >=3.5.0 <4.0.0.
2. Configure the CLI
Create basalt.yaml in the directory you'll run the CLI from:
backend: basalt_sqlite # required — the backend package (no default)
database:
path: app.db # SQLite: a file path, or ':memory:'
migrations_dir: migrations
# native_types: true # opt into the backend's native type presets (see §4)
# types: # optional — customize generated column types (see §4)
The CLI itself has no dependency on any database backend. backend: is
required — naming the backend package is an explicit decision, there is no
default. On first run the CLI generates a small entrypoint under
.dart_tool/basalt/ that imports that package's adapter and re-runs itself
through it (the build_runner model), so the backend must be in your
dev_dependencies. It is regenerated automatically when the backend or the
package graph changes. Run the CLI from the project root (where
.dart_tool/package_config.json lives).
The keys under database: are adapter-specific:
# SQLite (backend: basalt_sqlite)
database:
path: app.db # or ':memory:'
# Postgres (backend: basalt_postgres) — a URL...
database:
url: postgres://user:secret@localhost:5432/shop?sslmode=disable
# ...or manual keys
database:
host: localhost # default: localhost
port: 5432 # default: 5432
database: shop # required
username: postgres # default: postgres
password: secret # default: empty
ssl: false # default: true
If DATABASE_URL is set in the environment it overrides database.url —
handy for CI with Postgres. Writing a new backend? See basalt
CLI Adapters & Tooling.
3. Create and apply a migration
dart run basalt_cli:basalt migration generate create_users
This scaffolds migrations/<version>_create_users/{up,down}.sql. Fill them in:
-- up.sql
CREATE TABLE users (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL,
age INTEGER NOT NULL,
active INTEGER NOT NULL,
manager_id INTEGER REFERENCES users(id)
);
-- down.sql
DROP TABLE users;
Apply pending migrations (each runs in a transaction, tracked in
__basalt_schema_migrations):
dart run basalt_cli:basalt migration run
See Migrations in the sidebar for the full command set.
4. Generate the typed schema
dart run basalt_cli:basalt generate-schema
generate-schema introspects the live database, so run it after applying
migrations. It writes table marker classes to schema_output in basalt.yaml
(default lib/schema.dart):
// GENERATED by `basalt generate-schema`. Do not edit by hand.
final class Users extends TableRef<Users> {
const Users._() : super('users');
static const table = Users._();
static const id = PrimaryKey<int, Users>(
table,
'id',
IntSqlType(),
);
static const name = ValueColumn<String, Users>(
table,
'name',
StringSqlType(),
);
static const age = ValueColumn<int, Users>(
table,
'age',
IntSqlType(),
);
static const managerId = Ref<int?, Users, Users>(
table,
'manager_id',
NullableSqlType(IntSqlType()),
references: Users.id,
);
@override
List<TableColumn<Object?, Object?>> get columns =>
const [id, name, age, managerId];
}
Because the generated class extends
TableRef, a column whose camelCase field name collides with an inherited member (table,tableName,alias,columns,col,aliased, or anObjectmember liketo_string) cannot be emitted —generate-schemafails with a clear error asking you to rename the column.
SQLite has no native boolean or timestamp, so a column declared bare
INTEGERcomes back asint. Columns declaredBOOLEAN/DATETIMEare restored tobool/DateTimeby the SQLite adapter's built-in preset — seebasalt_sqliteType Mapping for the details, andbasaltTypes for the codec model.
Customizing column types
By default each column maps to a built-in SqlType, adjusted by the backend
adapter's type presets: a portable tier that always applies (e.g. SQLite's
declared-BOOLEAN/DATETIME fix), and a backend-native tier that applies only
with native_types: true (e.g. Postgres json/jsonb →
Map<String, Object?> via PostgresJsonbSqlType — note the generated schema
then imports the backend package and is no longer backend-portable).
To emit your own custom SqlType (e.g. an enum codec, or a JSON type)
instead of editing the generated file by hand, add an optional types: block
to basalt.yaml — it always wins over the adapter's presets. Overrides are
matched per column with the precedence specific column > native type >
canonical type, each falling back to the presets and then the built-in
mapping:
types:
# By canonical type — keys are ColumnType names
# (integer, text, real, boolean, blob, dateTime).
canonical:
dateTime:
dart_type: "DateTime"
sql_type: "UtcDateTimeSqlType()"
import: "package:my_app/db/utc_date_time_sql_type.dart"
# By native/backend type — matched against the introspected column type
# (trimmed, lower-cased; a `varchar` key also matches `VARCHAR(255)`).
native:
jsonb:
dart_type: "Map<String, Object?>"
sql_type: "JsonMapSqlType()"
import: "package:my_app/db/json_map_sql_type.dart"
nullable: # used for NULLable columns
dart_type: "Map<String, Object?>?"
sql_type: "NullableSqlType(JsonMapSqlType())"
# By a single column — key is "table.column" (highest precedence).
columns:
users.metadata:
dart_type: "UserMeta"
sql_type: "UserMetaSqlType()"
import: "package:my_app/models/user_meta.dart"
Each entry needs dart_type and sql_type (both written verbatim —
dart_type must equal the SqlType's T), an optional import the generated
file needs for a custom symbol (omit it for built-in types), and an optional
nullable: variant used when the column allows NULL. The generator collects and
de-duplicates the imports at the top of the file. Overrides apply to
ValueColumn, PrimaryKey and Ref alike; a foreign key and the primary key
it references must resolve to the same base type (override both or neither),
otherwise generate-schema fails with a clear error.
Postgres reports
enum/array columns asUSER-DEFINED/ARRAY(the specific name isn't captured), so target those with a per-column override rather than anative:entry.
5. Write data classes and generate mappers
Annotate plain Dart classes against the schema, then run build_runner. See
basalt Annotations & Codegen for the full derive reference.
// lib/user.dart
import 'package:basalt/basalt.dart';
import 'schema.dart';
part 'user.g.dart';
@Queryable(Users.table)
@Insertable(Users.table)
@AsChangeset(Users.table)
class User {
final int id;
final String name;
final int age;
final int active;
final int? managerId;
@Relation(Users.managerId)
final User? manager;
const User(this.id, this.name, this.age, this.active, {this.managerId, this.manager});
}
dart run build_runner build
See basalt_codegen Getting Started for wiring build.yaml.
6. Query and write
import 'package:basalt/basalt.dart';
import 'package:basalt_sqlite/basalt_sqlite.dart';
import 'schema.dart';
import 'user.dart';
Future<void> main() async {
final db = SqliteConnection.open('app.db');
await db.execute(const User(1, 'Bob', 30, 1).toInsert());
final adults = await db.fetch(
from(Users.table).where(Users.age > 18).orderBy(Users.name.asc()).mapWith(UserQuery.mapper),
);
print(adults);
await db.close();
}
Next: basalt Query Builder for the full DSL, and example/ for joins,
relations, transactions, and raw SQL.
Libraries
- basalt_cli Getting Started
- CLI library for the Basalt Dart ORM. The
basaltexecutable is a thin bootstrapper (Bootstrapper) that generates and runs an entrypoint calling runBasalt with the configured backend adapter; the migration engine (MigrationRunner) lives inpackage:basalt/migration.dartand is re-exported here for convenience, as is the adapter contract frompackage:basalt/tooling.dart.
Classes
- BasaltConfig Getting Started
-
CLI configuration, loaded from
basalt.yaml. - CliRunner Getting Started
-
Builds the
basaltcommand tree around the backend adapter the bootstrap entrypoint was generated with.
Functions
-
runBasalt(
List< Getting StartedString> args, {required BasaltAdapter adapter}) → Future<int> -
Runs the basalt CLI against the given backend
adapterand returns the process exit code.