Skip to content
openpeepsPublic

Latest commit

Β 

History

60 Commits

Folders and files

NameName
Last commit message
Last commit date
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Ozark ORM
A magical ORM for the Nim language πŸ‘‘

nimble install ozark

API reference
Github Actions Github Actions

😍 Key Features

  • 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!

Examples

Connecting to the database

Initialize the database connection with the given parameters

import ozark/database

initOzarkDatabase("localhost", "mydb", "myuser", "mypassword", 5432.Port)

withDB or withDBPool

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 here

withTransaction

Run 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

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: TimestampTz

Create the tables

TO create a database table you can use the prepareTable macro.

withDB do:
  Models.table(Users).prepareTable().exec()

Drop a table

withDB do:
  Models.table(Users).dropTable(cascade = true).exec()

Querying the database

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.

Insert data

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 row

Select Query

withDBPool do:
  let res = Models.table(Users)
                  .select(["name", "email"])
                  .where("name", "John")
                  .get()

Querying with raw SQL

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)

Migrations

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.nim
import 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.applied

Applied 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.

❀ Contributions & Support

🎩 License

MIT license. Made by Humans from OpenPeeps.
Copyright OpenPeeps & Contributors β€” All rights reserved.

Releases

Packages

Contributors

Languages

Generated from openpeeps/pistachio