<p align="center">
<a href="https://github.com/RustyRoad/RustyRoad" rel="noopener">
<img src="https://avatars.githubusercontent.com/u/138265565?s=400&u=eb116ae7b42e521b884d1288213df00032130f6a&v=4" alt="RustyRoad logo" width="200">
</a>
</p>
<h1 align="center">RustyRoad</h1>
<p align="center">
Rails-flavored scaffolding and migrations for Rust web apps (Actix + Tera + SQLx).
</p>
<div align="center">
[](https://www.rust-lang.org/)
[](https://github.com/RustyRoad/RustyRoad/actions)
[](https://crates.io/crates/rustyroad)
[](https://docs.rs/rustyroad)
[](https://github.com/RustyRoad/RustyRoad/issues)
[](https://github.com/RustyRoad/RustyRoad/pulls)
[](LICENSE)
</div>
> RustyRoad is under active development. For day-to-day use, prefer the latest released version on crates.io.
---
<sup>In loving memory of Rusty (2014–2023), a wonderful loving pup. I am forever grateful for the time I had with him.</sup>
---
## 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
```bash
cargo install rustyroad
```
### From source
```bash
git clone --recurse-submodules https://github.com/RustyRoad/RustyRoad
cd RustyRoad
cargo build --release
```
## Quick start
Create a new project:
```bash
rustyroad new my_project
```
Generate a route/controller:
```bash
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:
```bash
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:
```bash
rustyroad pull
```
This writes Drizzle tables and repositories, Zod schemas, oRPC procedures, a
Fastify server adapter, OpenAPI, and a Hey API configuration to `./db`.
Use the Rust target for the equivalent Actix + SQLx stack:
```bash
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
- `openapi/` — the same generated OpenAPI contract and Hey API configuration
Register the generated procedures with the application's pool:
```rust,ignore
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. To emit models without
repositories and HTTP procedures, pass `--schema-only`.
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:
```bash
rustyroad migration list
ENV=test rustyroad migration list
```
Repair a legacy ledger that contains duplicate rows:
```bash
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:
```bash
rustyroad migration all
```
Validate the complete migration chain without modifying a persistent database:
```bash
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:
```bash
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):
```bash
rustyroad migration run create_users_table
```
Rollback a migration:
```bash
rustyroad migration rollback create_users_table
```
Generate a migration (folder + files):
```bash
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:
```bash
# 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:
```bash
rustyroad db schema
```
Run ad-hoc queries:
```bash
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:
```bash
rustyroad-mcp --register
```
Or manually add to `~/.config/opencode/opencode.json`:
```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:
```bash
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](https://www.postgresql.org/download/windows/)
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:
```toml
[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`.