Skip to content

Set up a project

You’ll create a console app that connects to PostgreSQL, applies its first migration and inserts and reads a row. The same layout works for a server app.

  • Dart SDK 3.3 or newer.
  • PostgreSQL 13 or newer you can reach, or Docker to run one locally.
Terminal window
dart create -t console blog
cd blog
dart pub add gisila_orm

gisila_orm brings package:postgres with it. Add it explicitly as well if you’ll use its types directly (TransactionSettings, Connection):

Terminal window
dart pub add postgres

docker-compose.yml:

services:
db:
image: postgres:16-alpine
ports:
- "5432:5432"
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: blog
Terminal window
docker compose up -d

Create database.yaml in the project root for local development:

default: main
connections:
main:
host: localhost
port: 5432
database: blog
username: postgres
password: postgres
ssl_mode: disable

In deployed environments set DATABASE_URL instead; it takes precedence over the file. Add database.yaml to .gitignore if it will ever hold real credentials.

lib/models/blog.gisila.yaml:

Author:
columns:
name: { type: varchar, is_null: false }
email: { type: varchar, is_null: false, is_unique: true, allow_blank: false }
created_at: { type: timestamp, is_null: false, default: now }
Terminal window
dart run gisila_orm:generate

This writes lib/models/blog.gisila.g.dart (the Author model, AuthorTable, authors()), the full-schema SQL, lib/models/migrations/<millis>_initial_schema.up.sql and .gisila/schema_snapshots/project.gisila.yaml. Commit all of them.

Terminal window
dart run gisila_orm:migrate up
DATABASE_URL not set — using database.yaml. Set DATABASE_URL for panel/production deploys.
Applied 1 migration(s) in batch 1:
- 1730000000000_initial_schema

bin/blog.dart:

import 'package:blog/models/blog.gisila.g.dart';
import 'package:gisila_orm/gisila.dart';
Future<void> main() async {
final db = await Database.connect(await DatabaseConfig.fromEnvironment());
try {
await db.ping();
final ctx = db.context();
final ada = await authors()
.insert({'name': 'Ada Lovelace', 'email': 'ada@example.com'})
.onConflictDoUpdate(['email'])
.one(ctx);
print('author #${ada.id} created at ${ada.createdAt}');
final all = await authors().orderBy(AuthorTable.name).all(ctx);
print(all.map((a) => a.email).toList());
} finally {
await db.close();
}
}

The import uses your package name (blog) because the generated file lives under lib/.

Terminal window
dart run bin/blog.dart
author #1 created at 2026-11-01 10:00:00.000Z
[ada@example.com]

Run it twice: the upsert keeps one row, so the id stays 1.