12k
All articles

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.

OpenReplay Team
OpenReplay Team
Getting Started with Loco, Rust's Answer to Rails

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 need cargo install sea-orm-cli, but only if your app uses a database.
  • In Loco 1.2, cargo loco generate scaffold writes 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 BackgroundAsync mode during development. For production, you typically move them to a Redis, Postgres or SQLite queue processed by cargo 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.

ConceptWhat it meansLoco 1.2 command or location
Convention over configurationFiles live where the framework expects themGenerated layout under src/ and config/
GeneratorsThe CLI writes boilerplate codecargo loco generate scaffold, model, controller, worker
MigrationsVersioned schema changesmigration/src/, applied automatically on generate
Background jobsWork runs outside the request cyclecargo 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.

DevTools for the frontend

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

We use cookies to improve your experience. By using our site, you accept cookies.