
A magical ORM for the Nim language π
nimble install ozark
- Macro-based query builder with a fluent API
- Compile-time SQL validation & type safety
- Support for PostgreSQL and SQLite
- Transactions (
withTransaction) with automatic commit/rollback - Upserts, batch inserts, aggregates, pagination, raw SQL with bound params
- Async query execution (coming soon)
- Pluginkit-backed migration system with compile-time validated DSL
Note
Ozark is still in active development. Expect bugs and breaking changes. Contributions are welcome!
Initialize the database connection with the given parameters
import ozark/database
initOzarkDatabase("localhost", "mydb", "myuser", "mypassword", 5432.Port)Use withDB to execute queries then automatically close the connection after the block is executed. Use withDBPool to execute queries using a connection pool for better performance in concurrent scenarios.
withDB do:
# execute queries here
withDBPool do:
# execute queries hereRun a block inside a database transaction on a dedicated pooled connection. Commits on success, rolls back when the block raises, and always returns the connection to the pool.
withTransaction do:
Models.table(Users).insert({...}).exec()
Models.table(Audit).insert({...}).exec()Define a model by creating a new type that inherits from Model and specifying the fields with their types. See Ozark's Types documentation for supported field types and options.
import ozark/model
newModel Users:
id: Serial
username: Varchar(50)
name: Varchar(100)
email: Varchar(100)
newModel Subscription:
id: Serial
user_id: Users.id
plan: Varchar(50)
status: Varchar(20)
created_at: TimestampTz
updated_at: TimestampTzTO create a database table you can use the prepareTable macro.
withDB do:
Models.table(Users).prepareTable().exec()withDB do:
Models.table(Users).dropTable(cascade = true).exec()For simple queries, you can use the macro-based query builder. The query builder provides a fluent API for constructing SQL queries in a type-safe way. The generated SQL is validated at compile time to catch errors early.
import ozark/query
withDBPool do:
let id = Models.table(Users).insert({
name: "John Doe",
username: "johndoe",
email: "johndoe@example.com",
}).execGet() # returns the id of the inserted rowwithDBPool do:
let res = Models.table(Users)
.select(["name", "email"])
.where("name", "John")
.get()When things are getting too complex for the query builder, you can use rawSQL to write raw SQL queries while still benefiting from compile-time validation & type safety. The rawSQL macro allows you to write raw SQL queries with parameter binding to prevent SQL injection attacks.
Models.table(Users)
.rawSQL("SELECT * FROM users WHERE name = ?", "Alice")
.get(Users)Nim is compiled, so migrations ship as pluginkit dynamic libraries built from
your model definitions (full compile-time validation: unknown models/columns
fail the library build). Each migration lives in migrations/ with a
<name>_<YYYYMMDDHHMMSS>.nim filename (timestamp last, since Nim module
names cannot start with a digit):
# migrations/add_users_20260915120000.nim
import app/models # the app's models: migrations validate against them
import ozark/migration
newMigration addUsers:
## Add users table with unique email # <- stored as the migration description
up do:
Models.table(Users).prepareTable().exec()
down do:
Models.table(Users).dropTable().exec()nim c --app:lib --path:src migrations/add_users_20260915120000.nimimport ozark/driver/sqlite
import ozark/migration
initOzarkDatabase("app.db")
withDBPool do:
migrate("migrations") # apply pending in filename-version order
# rollback("migrations", 1)
# for s in migrationStatus("migrations"): echo s.info.version, " ", s.appliedApplied versions (with description, compiledAt build time and runAt
apply time) are tracked in the ozark_migrations table. Each migration runs
in its own transaction. Migrations support both drivers (serial/integer
DDL and ?/$N placeholders are generated per driver); hand-written
rawSQL text is shared verbatim, so keep placeholder styles portable or
split per-driver migrations.
- π Found a bug? Create a new Issue
- π Wanna help? Fork it!
MIT license. Made by Humans from OpenPeeps.
Copyright OpenPeeps & Contributors β All rights reserved.