Rusticx ORM
Blazingly fast, async-first, multi-database ORM for Rust.
Rusticx lets you define models with a derive macro, auto-generate tables, and perform full CRUD — synchronously or asynchronously — across PostgreSQL, MySQL/MariaDB, and MongoDB from a single unified API.
Features
- Multi-database — PostgreSQL, MySQL, MariaDB, MongoDB (more planned: CockroachDB, Cassandra, SQLite)
- Async-first — built on
tokio + sqlx + mongodb async drivers
- Sync support —
SyncAdapter wrapper for blocking contexts
- Derive macro —
#[derive(Model)] generates all boilerplate from your struct
- Type-safe query builder — composable, no raw strings required for common queries
- Connection pooling — built-in via
sqlx pool for SQL, native pool for MongoDB
- Schema migration —
repo.migrate() creates the table if it doesn't exist
- Feature-gated backends — only compile what you use
Installation
[dependencies]
rusticx = { version = "0.1", features = ["postgres"] }
rusticx = { version = "0.1", features = ["mysql"] }
rusticx = { version = "0.1", features = ["mongo"] }
rusticx = { version = "0.1", features = ["full"] }
tokio = { version = "1", features = ["full"] }
serde = { version = "1", features = ["derive"] }
uuid = { version = "1", features = ["v4", "serde"] }
Quick Start
1. Define a Model
use rusticx::prelude::*;
use serde::{Deserialize, Serialize};
use uuid::Uuid;
use chrono::{DateTime, Utc};
#[derive(Debug, Clone, Serialize, Deserialize, Model)]
#[rusticx(table = "users")] pub struct User {
#[rusticx(primary_key)]
pub id: Uuid,
pub name: String,
#[rusticx(unique)]
pub email: String,
pub age: i32,
pub active: bool,
pub created_at: DateTime<Utc>,
}
Supported #[rusticx(...)] attributes
| Attribute |
Scope |
Description |
table = "name" |
struct |
Override table / collection name |
primary_key = "field" |
struct |
Override PK field name (default: id) |
primary_key |
field |
Mark this field as primary key |
nullable |
field |
Allow NULL (auto-detected for Option<T>) |
unique |
field |
Add UNIQUE constraint |
default = "expr" |
field |
SQL DEFAULT expression |
column = "name" |
field |
Override column name |
skip |
field |
Do not persist this field |
2. Async Usage (recommended)
use rusticx::prelude::*;
use std::sync::Arc;
use uuid::Uuid;
use chrono::Utc;
#[tokio::main]
async fn main() -> rusticx::Result<()> {
let adapter = PostgresAdapter::connect_url("postgres://user:pass@localhost/mydb").await?;
let repo: Repository<User, _> = Repository::new(Arc::new(adapter));
repo.migrate().await?;
let user = User {
id: Uuid::new_v4(),
name: "Alice".into(),
email: "alice@example.com".into(),
age: 30,
active: true,
created_at: Utc::now(),
};
let inserted = repo.insert(&user).await?;
println!("Inserted: {:?}", inserted);
let users = vec![
User { id: Uuid::new_v4(), name: "Bob".into(), email: "bob@example.com".into(), age: 25, active: true, created_at: Utc::now() },
User { id: Uuid::new_v4(), name: "Carol".into(), email: "carol@example.com".into(), age: 22, active: false, created_at: Utc::now() },
];
let count = repo.insert_many(&users).await?;
println!("Inserted {count} users");
let found = repo.find_by_id(inserted.id).await?;
println!("Found: {:?}", found);
let all = repo.find_all().await?;
println!("Total users: {}", all.len());
let adults = repo.find(
repo.query()
.r#where("age", CondOp::Gte, 18)
.r#where("active", CondOp::Eq, true)
.order_by("name", Direction::Asc)
.limit(10)
).await?;
let alice = repo.find_one(
repo.query().r#where("email", CondOp::Eq, "alice@example.com")
).await?;
let page_1 = repo.paginate(1, 20).await?;
let total = repo.count(None).await?;
let active_count = repo.count(Some(
repo.query().r#where("active", CondOp::Eq, true)
)).await?;
let updated = repo.update(
repo.query()
.r#where("email", CondOp::Eq, "alice@example.com")
.set("age", 31)
.set("active", false)
).await?;
println!("Updated {updated} rows");
let mut alice_model = alice.unwrap();
alice_model.age = 32;
repo.save(&alice_model).await?;
repo.delete_by_id(inserted.id).await?;
repo.delete(
repo.query().r#where("active", CondOp::Eq, false)
).await?;
repo.adapter().execute_raw(
"UPDATE users SET active = $1 WHERE age < $2",
vec![Value::Bool(false), Value::Int(18)]
).await?;
let rows = repo.adapter().query_raw(
"SELECT id, name FROM users WHERE age > $1",
vec![Value::Int(21)]
).await?;
Ok(())
}
3. Sync Usage
Use SyncAdapter to wrap any async adapter for blocking contexts — no tokio::main required.
use rusticx::prelude::*;
use std::sync::Arc;
use uuid::Uuid;
use chrono::Utc;
fn main() -> rusticx::Result<()> {
let rt = tokio::runtime::Runtime::new().unwrap();
let async_adapter = rt.block_on(
PostgresAdapter::connect_url("postgres://user:pass@localhost/mydb")
)?;
let sync_adapter = SyncAdapter::new(async_adapter)?;
let schema = TableSchema::from_model::<User>();
sync_adapter.create_table(&schema)?;
let mut row = rusticx::Row::new();
row.insert("id".into(), Value::Uuid(Uuid::new_v4()));
row.insert("name".into(), Value::Text("Dave".into()));
row.insert("email".into(), Value::Text("dave@example.com".into()));
row.insert("age".into(), Value::Int(28));
row.insert("active".into(), Value::Bool(true));
row.insert("created_at".into(), Value::DateTime(Utc::now()));
let inserted = sync_adapter.insert("users", row)?;
println!("Inserted: {:?}", inserted);
let query = QueryBuilder::table("users")
.r#where("age", CondOp::Gte, 18)
.order_by("name", Direction::Asc)
.limit(5);
let rows = sync_adapter.find(&query)?;
println!("Found {} rows", rows.len());
let count = sync_adapter.count(&QueryBuilder::table("users"))?;
println!("Total: {count}");
let update_query = QueryBuilder::table("users")
.r#where("email", CondOp::Eq, "dave@example.com")
.set("age", 29);
sync_adapter.update(&update_query)?;
let delete_query = QueryBuilder::table("users")
.r#where("active", CondOp::Eq, false);
sync_adapter.delete(&delete_query)?;
sync_adapter.execute_raw(
"TRUNCATE TABLE users",
vec![]
)?;
Ok(())
}
4. MongoDB
Same API, different adapter. migrate() creates a collection + indexes.
use rusticx::prelude::*;
use std::sync::Arc;
#[derive(Debug, Clone, Serialize, Deserialize, Model)]
#[rusticx(table = "products")]
pub struct Product {
#[rusticx(primary_key, column = "_id")]
pub id: String,
pub name: String,
pub price: f64,
pub in_stock: bool,
}
#[tokio::main]
async fn main() -> rusticx::Result<()> {
let adapter = MongoAdapter::connect_url(
"mongodb://localhost:27017",
"mydb",
).await?;
let repo: Repository<Product, _> = Repository::new(Arc::new(adapter));
repo.migrate().await?;
let cheap = repo.find(
repo.query()
.r#where("price", CondOp::Lt, 100.0)
.r#where("in_stock", CondOp::Eq, true)
.order_by("price", Direction::Asc)
).await?;
Ok(())
}
5. MySQL / MariaDB
let adapter = MysqlAdapter::connect_url("mysql://user:pass@localhost/mydb").await?;
let repo: Repository<User, _> = Repository::new(Arc::new(adapter));
repo.migrate().await?;
Query Builder Reference
repo.query()
.r#where("column", CondOp::Eq, value)
.r#where("column", CondOp::Ne, value)
.r#where("column", CondOp::Gt, value)
.r#where("column", CondOp::Gte, value)
.r#where("column", CondOp::Lt, value)
.r#where("column", CondOp::Lte, value)
.r#where("column", CondOp::Like, "%pattern%")
.r#where("column", CondOp::ILike, "%pattern%") .r#where("column", CondOp::IsNull, Value::Null)
.r#where("column", CondOp::IsNotNull, Value::Null)
.or_where("column", CondOp::Eq, value)
.order_by("column", Direction::Asc)
.order_by("column", Direction::Desc)
.limit(20)
.offset(40)
.set("column", new_value)
Connection Pool Configuration
let adapter = PostgresAdapter::connect(
PostgresConfig::new("postgres://localhost/mydb")
.max_connections(20)
.min_connections(2)
).await?;
let adapter = MysqlAdapter::connect(
MysqlConfig::new("mysql://localhost/mydb")
.max_connections(15)
).await?;
let adapter = MongoAdapter::connect(
MongoConfig::new("mongodb://localhost:27017", "mydb")
.max_pool_size(10)
).await?;
Architecture
rusticx/ ← public facade crate (feature-gated re-exports)
├── rusticx-core/ ← Model trait, Repository, QueryBuilder, Value, DatabaseAdapter
├── rusticx-macros/ ← #[derive(Model)] procedural macro
├── rusticx-sql/ ← SQL dialect compiler (DDL + DML for Postgres / MySQL)
├── rusticx-postgres/ ← PostgreSQL adapter (sqlx)
├── rusticx-mysql/ ← MySQL / MariaDB adapter (sqlx)
└── rusticx-mongo/ ← MongoDB adapter (mongodb)
Supported Databases
| Database |
Status |
Feature flag |
| PostgreSQL |
Stable |
postgres |
| MySQL |
Stable |
mysql |
| MariaDB |
Stable (MySQL driver) |
mysql |
| MongoDB |
Stable |
mongo |
| CockroachDB |
Planned (PostgreSQL wire) |
— |
| Cassandra |
Planned |
— |
| SQLite |
Planned |
— |
License
MIT — © Tarun Vishwakarma