Model the domain.
Ship it in Rust.
Riverbase is a Rust framework for domain-driven HTTP backends. Declare commands, aggregates and queries; get typed routes, OpenAPI, OIDC auth, Casbin policy, tenant-safe reads, and an audit trail that commits with every change.
cargo add riverbase_core riverbase_proc riverbase_http --git https://github.com/riverbase-io/riverbase Built on the Rust ecosystem you already trust
riverbase_http::RiverbaseApp
Mount what you need, nothing more
Every route exists because a line in main.rs put it there. Switch a line off to see what the service stops serving.
app.serve().await?;- GET/api/openapi.jsonOpenAPI document
- POST/api/riverbase.todo/create-todo:post/todoCommand
- POST/api/riverbase.todo/update-todo:exec/todo/{id}Command
- GET/api/riverbase.todo/todo.listQuery: filter, sort, page
- GET/api/riverbase.todo/todo.item/{id}Query: one row
- GET/api/auth/infoWho is calling: OIDC, sessions, profiles
- POST/api/auth/switch-profile/{id}Switch the active profile
7 routes, each behind auth and Casbin unless declared public
riverbase_core · riverbase_proc · riverbase_http
Build a domain in six files
The todo reference service from the tutorial, in the order you would write it.
use riverbase_core::diesel_table_with_domain_fields;diesel_table_with_domain_fields! {todo_examples.todo_items (_id) {title -> Text,done -> Bool,notes -> Text,version -> Int4,}}
riverbase_core::command
What happens to a command
One request, one transaction. The state change and its audit trail commit together, then integration messages leave through the outbox. Scrub the history of one row below.
- Routecreate-todo:postaxum decodes {"payload": …} into CreateTodoPayload. Bad input stops here.
- Policycreate-todo.executeThe caller is authenticated, then Casbin checks the activity. Empty policy denies.
- EngineCommandEngineA ractor actor opens one Postgres transaction for the whole command.
- AggregateTodoAggregate::create#[domain_action] writes the row and names the event. Writes outside it fail.
- Audittodo.createdEvent, activity, response and outbox rows commit with the state, or not at all.
- Readtodo.listThe query engine serves the row, filtered by tenant and scope policy.
- _id
- 7f3a91c2
- title
- null
- done
- null
- notes
- null
- _etag
- null
- _updated
- 10:15:40
State is stored, not replayed. Each audited event records the row it produced, so the history shows exactly what changed and when.
What ships in the box
The parts most backends rebuild by hand, already wired to the same domain.
Typed end to end
- Payloads
- Rust structs generate the decoder, JSON Schema and OpenAPI from one declaration
- riverbase_core
Access
- Authorization
- Casbin on every route, deny by default; tenant and scope policy on every row
- riverbase_http
Consistency
- Outbox
- Integration messages leave after commit, at least once, to NATS, Redis or Postgres
- riverbase_core
Runtime
- Concurrency
- ractor actor pools for commands, a semaphore for reads, one transaction per command
- riverbase_core
Batteries
Cargo workspace · 6 crates
Start with three crates. Add the rest when a domain needs them.
Core
HTTP
Feature flags
Every crate is a git dependency. Turn on only the features you use:
cargo add riverbase_http --git https://github.com/riverbase-io/riverbase riverbase_core/nats-ioNATS message bus for the outbox and workersriverbase_http/websocket,sse,rxdbRealtime: WebSocket bridge, SSE, RxDB replicationriverbase_file/aws-s3S3-compatible media storageriverbase_task/nats-ioWorkers that consume NATS subjects
Five minutes to a running API
Open /api/openapi.json for the generated API document. Output shown is illustrative.
Start with the quick start.