Getting Started with Loco, Rust's Answer to Rails
Set up a Rust web app with Loco, the Rails-inspired framework. Install the CLI, generate a CRUD API scaffold, and add a background worker using Loco 1.2.
Loco is a Rails-inspired Rust web framework. It puts Axum routing, Sea-ORM models and migrations, background workers and code generators behind a single cargo loco command.
If you’ve ever spent a weekend connecting Axum, an ORM, a migration runner and a job queue before writing any business logic, Loco is designed to save you that work. This guide installs Loco, creates an API app, scaffolds a resource and adds a background job. Many older tutorials target pre-1.0 versions, so every command below uses the 1.x CLI from Loco 1.2.0. If you’re new to the language, start with Rust basics.
Key Takeaways
- Loco is a Rust web framework modelled on Ruby on Rails. It is built on Axum for routing and Sea-ORM 2.0 for models and migrations.
- Install the CLI with
cargo install loco. You also needcargo install sea-orm-cli, but only if your app uses a database. - In Loco 1.2,
cargo loco generate scaffoldwrites a migration, model, entity, DTOs and a CRUD controller, then applies the migration. There is no separate migrate step. - A new Loco app’s development server listens on port 5150.
- Background jobs run in-process in
BackgroundAsyncmode during development. For production, you typically move them to a Redis, Postgres or SQLite queue processed bycargo loco start --worker.
What Is Loco in Rust?
Loco is an opinionated, full-stack Rust framework. It handles the stack decisions a bare Axum project leaves to you. Loco’s homepage lists Axum as the HTTP layer and Sea-ORM 2.0 as the ORM. Workers, a scheduler, mailers, storage and caching are also built in. You get one project layout, one config system and one CLI, so you don’t have to pick five crates and write the code that connects them. The source lives on GitHub. Loco reached its first stable release, 1.0, in July 2026.
Which Rails Ideas Does Loco Borrow?
Loco takes four Rails habits. Each project has the same predictable layout, generators write the boilerplate, schema changes go through versioned migrations, and slow work goes to background jobs. If you know Django or Laravel, you’ve seen the same ideas under different names.
| Concept | What it means | Loco 1.2 command or location |
|---|---|---|
| Convention over configuration | Files live where the framework expects them | Generated layout under src/ and config/ |
| Generators | The CLI writes boilerplate code | cargo loco generate scaffold, model, controller, worker |
| Migrations | Versioned schema changes | migration/src/, applied automatically on generate |
| Background jobs | Work runs outside the request cycle | cargo loco generate worker, perform_later |
How Do You Install Loco and Create an App?
Installing Loco takes two cargo commands, and creating an app takes one more. As the Loco README notes, the sea-orm-cli install is only needed when your app uses a database. This example uses SQLite, so you need it:
cargo install loco
cargo install sea-orm-cli
Next, generate the app. If you pass all four flags, the wizard skips its prompts, so the command can be scripted and gives the same result every time:
loco new --name tour_app --db sqlite --bg async --assets none
cd tour_app
cargo loco start
According to the official first-app tutorial, --db sqlite picks SQLite as the database, --bg async runs background jobs in-process, and --assets none skips view scaffolding, so the app serves JSON only. The last line of the start output confirms the server is up:
listening on port 5150
By default, the development server listens on port 5150. The config files set the port through a get_env call that reads the PORT environment variable, as shown in the configuration reference, so you can change it there. Because you picked a database, the app also ships with built-in auth routes under /api/auth/*.
Older tutorials install a crate called loco-cli. Loco 1.0 removed that crate, and the CLI is now installed as loco.
How Do You Generate a Resource with a Scaffold?
A Loco scaffold produces a working, database-backed CRUD API from one command:
cargo loco generate scaffold posts title:string! content:text --no-auth
In Loco 1.2, cargo loco generate scaffold writes a migration, a Sea-ORM model and entity, typed request and response DTOs, and a CRUD controller. It then applies the migration and regenerates the entities, so you never run a separate migrate command.
The field syntax comes from the Add a model guide. A type ending in !, such as title:string!, gives you a required column. A bare type like content:text gives you a nullable one, and ^ marks a field as unique. Scaffolded routes expect a JWT by default, so an anonymous request returns 401. The --no-auth flag makes the routes public, which keeps this demo simple.
The parts of the generated project used in this guide (this is a partial view):
tour_app/
├── config/development.yaml
├── migration/src/
└── src/
├── app.rs
├── controllers/posts.rs
├── dtos/
├── models/_entities/
└── workers/
To test the new endpoint, restart the server and send a POST request:
curl -X POST -H "Content-Type: application/json" \
-d '{"title":"Tour post","content":"hi"}' localhost:5150/api/posts
The JSON response starts with {"id":1, and then echoes back the stored post.
How Do You Add a Background Job in Loco?
A Loco background job is a worker module with a typed arguments struct. You enqueue it with perform_later. Generate one:
cargo loco generate worker notifier
added: "src/workers/notifier.rs"
injected: "src/workers/mod.rs"
injected: "src/app.rs"
As the background worker guide explains, the generator always calls the struct Worker and puts it in its own module, next to an empty WorkerArgs struct that you fill in. That means the generated notifier worker is workers::notifier::Worker. In src/workers/notifier.rs, add a field to WorkerArgs and replace the perform body. Leave build and the rest of the generated file as it is:
#[derive(Deserialize, Debug, Serialize)]
pub struct WorkerArgs {
pub post_id: i64,
}
// inside the generated `impl BackgroundWorker<WorkerArgs> for Worker`
async fn perform(&self, args: WorkerArgs) -> Result<()> {
println!("new post created: {}", args.post_id);
Ok(())
}
Then open src/controllers/posts.rs and add these lines to the scaffolded create handler, right after the line that inserts the new post (let item = item.insert(&ctx.db).await?;). Since Loco 1.0, generated primary keys are i64, so item.id fits post_id with no conversion:
crate::workers::notifier::Worker::perform_later(
&ctx,
crate::workers::notifier::WorkerArgs { post_id: item.id },
)
.await?;
Since Loco 1.0, perform_later() returns the job ID as a Result<String>, as listed in the Loco 1.0 release notes. You can log it if you want to trace jobs later.
Your config/development.yaml sets workers.mode to BackgroundAsync, which runs jobs as in-process Tokio tasks. BackgroundAsync mode needs no queue backend at all. For production, switch workers.mode to BackgroundQueue, configure a Redis, Postgres or SQLite queue, and process jobs in a separate process with cargo loco start --worker. Postgres and SQLite queues work with the default worker feature, while a Redis queue needs worker_redis, and the backend is selected in config by queue.kind. Your worker and controller code don’t change when you switch.
Who Is Loco a Good Fit For?
Loco makes Rust backend setup much faster, but it doesn’t remove the costs of writing Rust:
- Compile times. Every change goes through
rustc, so the edit-and-reload loop is slower than in Ruby, Python or PHP frameworks. - Ecosystem. There are far fewer third-party integrations, plugins and answered questions than Rails or Django have built up.
- Learning curve. Generated code still assumes you’re comfortable with ownership, async and traits, and compiler errors in handlers assume you can read them.
Loco is a good fit for developers who already know Rust and want Rails-style setup speed, plus Rails, Django or Laravel developers moving to Rust on purpose. It’s a poor choice for a first backend if you’re learning web servers and Rust at the same time. If you’re still deciding whether Rust is worth learning for web work, read whether web developers need Rust.
Conclusion
Loco gives Rust developers what Rails gives Ruby developers: one CLI, a predictable layout and built-in migrations and jobs, all on top of Axum and Sea-ORM. Use the 1.x commands from this guide rather than the pre-1.0 ones in older tutorials. Your next step is to add a second resource with cargo loco generate model, then work through the background worker guide to move your queue from BackgroundAsync to a persistent backend.
FAQs
Can I use existing Axum middleware and extractors in a Loco app?
Yes. Loco is mostly glue code around Axum, so custom extractors that implement FromRequestParts work in Loco controllers. To attach Tower layers such as a tower-http timeout or a session layer, implement Hooks::after_routes in src/app.rs, which receives the Axum router Loco has already built.
How do I switch a Loco app from SQLite to Postgres?
Set database.uri in config/development.yaml or config/production.yaml to a postgres:// connection string. The generated config reads the value through get_env with the DATABASE_URL variable, so setting that environment variable also works. Loco's defaults expect user loco, password loco and a database named after the app and environment, such as myapp_development. Adding sslmode=require to the URI turns on Postgres TLS, with no extra Cargo feature needed.
How do I deploy a Loco app to production?
Build a release binary with cargo build --release and copy it to the server along with the config/ folder. The target machine needs no Rust toolchain or cargo. For packaging, cargo loco generate deployment offers three kinds: docker, nginx and lambda. Docker writes a Dockerfile and .dockerignore, nginx writes a reverse-proxy config, and lambda writes an AWS Lambda entrypoint and adds the lambda_http dependency. Production settings live in config/production.yaml.
What is the difference between Loco's scheduler and background workers?
Workers run jobs your code enqueues with perform_later, while the scheduler runs jobs on a timetable. You declare scheduler jobs in the config YAML, each with a cron expression or plain-English schedule. The run value names a registered Loco task, or a shell command when shell is true. The --scheduler flag runs the scheduler alongside the server without a worker process.
Gain Debugging Superpowers
Unleash the power of session replay to reproduce bugs, track slowdowns and uncover frustrations in your app. Get complete visibility into your frontend with OpenReplay — the most advanced open-source session replay tool for developers.
Star on GitHub12k