rustyroad 1.8.1

Rusty Road is a framework written in Rust that is based on Ruby on Rails. It is designed to provide the familiar conventions and ease of use of Ruby on Rails, while also taking advantage of the performance and efficiency of Rust.
Documentation

Rust CI Crates.io Docs.rs Issues PRs License

RustyRoad is under active development. For day-to-day use, prefer the latest released version on crates.io.


In loving memory of Rusty (2014–2023), a wonderful loving pup. I am forever grateful for the time I had with him.


What is RustyRoad?

RustyRoad is a Rust CLI + generator toolkit inspired by Ruby on Rails.

It focuses on:

  • generating a consistent project structure
  • generating controllers/routes/models
  • generating and running database migrations
  • providing a few productivity-focused database commands

Under the hood, generated projects use Actix for HTTP, Tera for templates, and SQLx for database support.

If you're curious about the motivation, there's a short write-up here: https://rileyseaburg.com/posts/rust-needs-a-rails

Features

  • Project generator (rustyroad new)
  • Generators (rustyroad generate ...)
  • Database migrations (rustyroad migration ...)
  • Database inspection / queries (rustyroad db ..., rustyroad query ...)
  • MCP Server for AI agent integration (rustyroad-mcp)
  • Optional GrapesJS feature (drag-and-drop editor) via rustyroad feature add grapesjs

Install

From crates.io

cargo install rustyroad

From source

git clone --recurse-submodules https://github.com/RustyRoad/RustyRoad
cd RustyRoad
cargo build --release

Quick start

Create a new project:

rustyroad new my_project

Generate a route/controller:

rustyroad generate route users

Configuration

How rustyroad.toml is used

RustyRoad reads your database settings from a TOML file in your project root.

  • Default (dev): RustyRoad reads ./rustyroad.toml
  • If ENVIRONMENT is set and not dev: RustyRoad reads ./rustyroad.<ENVIRONMENT>.toml

Examples:

  • ENVIRONMENT=prod → reads rustyroad.prod.toml
  • ENVIRONMENT=test → reads rustyroad.test.toml

There is no special rustyroad.dev.toml—dev is the plain rustyroad.toml file.

Tip: You can also use ENV=prod as a shorthand for ENVIRONMENT=prod. If both are set, ENVIRONMENT wins.

If you're unsure what RustyRoad is going to read on your machine, run:

rustyroad config

(It prints ENVIRONMENT=..., the config filename, and a sanitized view of the parsed database settings.)

Generate an API from PostgreSQL

rustyroad pull introspects the live PostgreSQL schema and generates a complete typed database API. TypeScript remains the default target:

rustyroad pull

This writes Drizzle tables and repositories, Zod schemas, oRPC procedures, a Fastify server adapter, OpenAPI, and a Hey API configuration to ./db.

To also write a standalone Drizzle/Zod schema pair for application validation, use:

rustyroad pull --zod

This writes schema.ts and zod.ts to ./src/schemas; pass --zod-out to choose another folder. --zod-models and --zod-models-out are accepted as aliases.

Use the Rust target for the equivalent Actix + SQLx stack:

rustyroad pull --language rust

The Rust target writes ./src/db by default:

  • models.rs — SQLx row types and separate create/patch input types
  • repositories.rs — bound, typed CRUD queries for every table with a single-column primary key
  • procedures.rs — Actix handlers for list/get/create/update/delete under /api
  • api.rs — the developer-owned composition point for custom services
  • mod.rs — the module facade exported to the application

Register the generated procedures with the application's pool:

mod db;

HttpServer::new(move || {
    App::new()
        .app_data(web::Data::new(pool.clone()))
        .configure(db::configure)
})

Database-derived files are regenerated on each pull. Composition files are written once and preserved, so custom code in api.rs or api.ts survives. Pass --force only when those files should be reset.

The Rust output expects actix-web, serde, and SQLx's Postgres/runtime and database-type features. The command prints the exact cargo add invocation after generation. At present, pull introspection is PostgreSQL-only.

Migrations

RustyRoad expects migrations in this exact location (do not create a plain ./migrations/ folder):

./config/database/migrations/<timestamp>-<name>/up.sql
./config/database/migrations/<timestamp>-<name>/down.sql

Migration Commands

List migrations:

rustyroad migration list
ENV=test rustyroad migration list

Repair a legacy ledger that contains duplicate rows:

rustyroad migration repair-ledger
ENVIRONMENT=prod rustyroad migration repair-ledger --yes

The repair runs in one transaction, keeps the newest state for each migration identity, and restores the uniqueness guard. It does not execute up.sql or down.sql files.

Ledger provenance is not effect verification

rustyroad migration list reports whether a migration is recorded in the ledger, not whether its SQL or live database effects have been proven. Each new ledger row records provenance and the SHA-256 checksum of its migration SQL:

  • executed — RustyRoad successfully submitted the SQL before recording it
  • baselined — the row was adopted without executing the SQL
  • legacy — the row predates provenance tracking

Effects remain UNVERIFIED unless separate verification evidence populates verified_at. In particular, rustyroad migration baseline never executes SQL; it records baselined provenance and causes subsequent migration runs to skip the recorded identities. Use rustyroad db schema as live evidence for tables and columns, while remembering that it does not verify data changes or every database object.

migration version-status reads _rustyroad_history, which is intentionally separate from _rustyroad_migrations. A ledger baseline therefore does not become a published schema version.

Run all migrations (up) in order:

rustyroad migration all

Validate the complete migration chain without modifying a persistent database:

ENVIRONMENT=test rustyroad migration validate
ENVIRONMENT=prod rustyroad migration validate

Validation reads the active environment configuration: rustyroad.toml for dev or rustyroad.<environment>.toml for environments such as test, staging, and prod. It never connects to the configured database_name. Instead, it creates a randomly named disposable database on the configured PostgreSQL/MySQL server (or an OS-managed temporary file for SQLite), runs every up.sql in timestamp order through a generated login scoped to that disposable database, and removes the disposable database and login after success or failure.

When validating with ENVIRONMENT=prod, disposable resources are created on the server from rustyroad.prod.toml, but the configured production database is not opened or modified. The configured administrative user must be allowed to create and drop databases and temporary roles/users.

Breaking migration warnings

RustyRoad scans generated migrations and every up.sql before migration run or migration all opens a database connection. It warns about operations that can destroy data or break foreign-key compatibility, including:

  • PostgreSQL ALTER COLUMN ... TYPE and USING conversions
  • Explicit casts such as CAST(...) and ::type
  • MySQL MODIFY COLUMN and CHANGE COLUMN
  • Dropped constraints or foreign keys

Interactive apply commands require confirmation when findings exist. Non-interactive commands stop with exit code 2. After reviewing both sides of every foreign key, validating against a disposable database, and backing up the target database, automation can acknowledge the risk explicitly:

rustyroad migration run change_customer_id_type --allow-breaking
rustyroad migration all --allow-breaking

The preflight is intentionally conservative: it identifies risky SQL but cannot prove that a cast preserves every value or that application code remains compatible.

Run a single migration by name (the name is the part after the timestamp in the folder name):

rustyroad migration run create_users_table

Rollback a migration:

rustyroad migration rollback create_users_table

Generate a migration (folder + files):

rustyroad migration generate create_users_table id:serial:primary_key email:string:not_null,unique

Auto-convert Rogue Migrations

If you (or an AI agent) accidentally created migrations in the wrong location (like ./migrations/), RustyRoad can detect and convert them:

# Preview what would be converted
rustyroad migration convert --dry-run

# Convert and keep source files
rustyroad migration convert

# Convert and remove source files
rustyroad migration convert --remove-source

RustyRoad will also warn you when running any migration command if it detects rogue migrations.

Database commands

Inspect schema:

rustyroad db schema

Run ad-hoc queries:

rustyroad query "SELECT * FROM users LIMIT 10;"
rustyroad query "SELECT COUNT(*) AS total_users FROM users;"

MCP Server (AI Agent Integration)

RustyRoad includes an MCP (Model Context Protocol) server that exposes database tools to AI agents like OpenCode, Claude, etc. This prevents agents from using raw psql commands or connecting to the wrong database.

Available Tools

  • rustyroad_query - Execute SQL queries
  • rustyroad_schema - Get database schema
  • rustyroad_migrate - Run migrations
  • rustyroad_migration_generate - Create new migrations
  • rustyroad_config - View configuration
  • rustyroad_convert_migrations - Fix rogue migrations

Setup

Register with OpenCode:

rustyroad-mcp --register

Or manually add to ~/.config/opencode/opencode.json:

{
  "mcp": {
    "rustyroad": {
      "type": "local",
      "command": ["/path/to/rustyroad-mcp"],
      "enabled": true,
      "environment": {
        "RUSTYROAD_PROJECT_DIR": "/path/to/your/project"
      }
    }
  }
}

Optional: GrapesJS

RustyRoad can scaffold an optional GrapesJS editor experience:

rustyroad feature add grapesjs

You can learn more about GrapesJS at https://grapesjs.com/ and see the example project at example-grapesjs/.

Examples

  • example/ – a basic generated app
  • example-grapesjs/ – a generated app with GrapesJS enabled

Troubleshooting

Building from source on Windows (PostgreSQL linkage)

If you build this repository from source on Windows and see errors about POSTGRES_LIB_PATH or libpq.lib:

  1. Install PostgreSQL from the official website
  2. Set POSTGRES_LIB_PATH environment variable to the directory containing libpq.lib (e.g., C:\Program Files\PostgreSQL\13\lib)
  3. For generated projects, create .cargo/config.toml in your project root:
[target.'cfg(windows)']
rustflags = ["-C", "link-arg=/LIBPATH:C:\\Program Files\\PostgreSQL\\13\\lib"]

Contributing

Contributions are welcome! Please see CONTRIBUTING.md.

License

MIT — see LICENSE.