Quick Start

This guide walks you through a complete Rango setup in under 5 minutes.

1. Initialize

cargo new my-app && cd my-app
rango init --backend sqlite

Add Rango to Cargo.toml:

[dependencies]
rango-orm = { version = "0.2", default-features = false, features = ["sqlite"] }
tokio = { version = "1", features = ["full"] }
anyhow = "1"
uuid = { version = "1", features = ["v4"] }   # you add these two: Rango does not re-export them
chrono = "0.4"

2. Define a model

Create src/models.rs:

#![allow(unused)]
fn main() {
use rango::prelude::*;

#[derive(Model)]
pub struct User {
    pub id: FieldUuid,
    #[field(unique)]
    pub email: FieldEmail,
    pub name: FieldVarchar<1, 100>,
    pub active: FieldBool,
    pub created_at: FieldDateTime,
}

#[derive(Model)]
pub struct Post {
    pub id: FieldUuid,
    pub author_id: ForeignKey<User>,
    pub title: FieldVarchar<1, 255>,
    pub body: FieldText,
    pub published: FieldBool,
}
}

3. Generate migrations

rango makemigrations

Output:

🔍 Scanning models in src... (backend: sqlite)
Found 2 model(s):
  - user (5 columns)
  - post (5 columns)
✅ Migration generated: migrations/0001_post_user.sql
📸 Snapshot updated.

Migration files are named NNNN_<tables>.sql, with the tables involved in alphabetical order.

4. Apply migrations

DATABASE_URL=sqlite://db.sqlite3 rango migrate

Output:

🔌 Connecting to database (sqlite)...
  ▶  Applying 0001_post_user.sql...
  ✅ 0001_post_user.sql
✅ Applied 1 migration(s).

5. Use the ORM

use rango::prelude::*;

mod models;
use models::User;

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let pool = connect_sqlite("sqlite://db.sqlite3").await?;

    // Insert
    let user = User {
        id: FieldUuid(uuid::Uuid::new_v4()),
        email: FieldEmail::new("alice@example.com")?,
        name: FieldVarchar::new("Alice")?,
        active: FieldBool(true),
        created_at: FieldDateTime(chrono::Utc::now()),
    };
    let user = insert(&pool, user).await?;

    // Query
    let active_users = User::filter(&pool)
        .eq(User::ACTIVE, true)
        .order_by(User::NAME)
        .limit(10)
        .all()
        .await?;

    for u in &active_users {
        println!("{} — {}", u.name.as_str(), u.email.as_str());
    }

    Ok(())
}

User::ACTIVE and User::NAME are typed columns generated by #[derive(Model)]: a typo or a wrong value type is a compile error. String names (.eq("active", true)) also work; they are checked against the model's schema at run time and an unknown column returns Error::Invalid.

Connecting to PostgreSQL or MySQL

connect_sqlite and connect_mysql take a URL; connect_postgres takes a DatabaseConfig (pool size, timeouts):

#![allow(unused)]
fn main() {
// PostgreSQL (default features of `rango-orm`)
let pool = connect_postgres(&DatabaseConfig::from_url("postgres://user:pass@localhost:5432/mydb")).await?;

// MySQL / MariaDB
let pool = connect_mysql("mysql://user:pass@localhost:3306/mydb").await?;
}