Introducing Loco: the "Rust on Rails"
- Introduction to Loco
- Overview of Loco: Core Features and Philosophies
- The Modern Web Developerβs Toolkit: What Loco Brings to the Table
- Key Features of Loco
- Getting Started with Loco
- Conclusion and Next Steps
Introduction to Loco
Loco is a Web or API framework for Rust: a "Rust on Rails". Strongly inspired by Rails, it contains everything you need to go from side project to a startup in Rust.
Loco is built by @jondot and @kaplanelad together with the Loco contributors.
The Genesis of Loco: Bridging the Gap in Web Development
In order to build Loco, we had to develop our set of: principles, guides, philosophies and opinions to answer:
- Where should we make a difference?
- How can we fully take advantage of Rust's abilities?
- How can we make hard things simple, and impossible things possible?
Believe it or not, Loco is a 3rd rewrite of building a Rails alternative which is not based on Ruby.
The first iteration was originally built on Node.js, a stack of high-productivity libraries glued together with some special sauce, made building a SaaS for a startup a breeze. It became: hyperstackjs, a "Rails on Typescript".
But Node.js and the Javascript/Typescript ecosystem has too much mental fatigue. I could not handle the churn, the ecosystem. Some times it took as little as 4 months to go back to a project and find that it needed so many upgrades for dependencies, and everything broke.
So how about a crazy idea: can this be built in Rust?
Crazy because Rust is rigid, static, safe. On the surface traits that are supposed to be opposite to what Rails and Ruby are: free, easy, flexible, productive.
You'd be surprised to know that Rust is perfect for this job. The first port of Hyperstack to Rust was called RustyRails and then changed into Loco.
Today, all the boxes were checked. I have zero mental overhead, zero churn of dependencies, zero fatigue working with Rust and Loco.
Oh, and I got 100,000req/s back with zero effort. This is Rust.
Overview of Loco: Core Features and Philosophies
A framework is all about balances. Loco creates balance over these principles:
- Safe, robust software powered by Rust
- That can be played with, experimented with, iterated on rapidly
- Offers everything Rails has which includes: data access, controllers, views, background jobs, websockets, mailers, storage, and even things that are offered from Rails gems such as authentication, i18n, pagination -- all built in.
- Has an easy and simple ergonomics and API surface area for developers.
Why Rust? Understanding the Language Behind Loco
First off, if you love Ruby, and happy with it -- stop here and go with Rails. Rails is fantastic, and there is nothing that can compare to it.
However, if Ruby is not something you use or can use, you most probably have 3 valid and popular options:
- Node.js
- Go
- Java/JVM
None of these have a proper Rails-like framework. Node.js has some options which can function as a partially implemented Rails in terms of features (e.g. Adonis and others).
But Node.js has Javascript fatigue, which, I have to say, in 2024 is only becoming exponentially worse and not better.
Rust can be more expressive and much more safe than Go, and can be more performant, simpler, requires less tweaks and quirks and lighter on systems than the JVM.
It is the perfect language for developer happiness, zero effort performance, simple and robust software that is easy to operate in production.
Language Differences: Rust vs. Ruby
You may be coming from Ruby or Rust, but not both. If that is the case,here's a quick comparison between the two to get some initial perspective.
Purpose and Design Philosophy
- Rust: Systems programming, emphasizing performance and safety.
- Ruby: Emphasizes simplicity and productivity, often used for web development.
Memory Management
- Rust: Ownership model, no garbage collector.
- Ruby: Garbage-collected, simpler but with performance overhead.
Type System
- Rust: Strong, static type system with type inference.
- Ruby: Dynamic typing, flexible but prone to runtime errors.
Performance
- Rust: High performance, comparable to C/C++.
- Ruby: Generally slower due to being interpreted.
Concurrency
- Rust: Advanced concurrency support, avoids data races.
- Ruby: Supports concurrency, but hindered by Global Interpreter Lock (GIL).
Community and Ecosystem
- Rust: Growing, with a focus on safe concurrency. Expanding Cargo and Crates.io ecosystem.
- Ruby: Large, established community. Rich library of gems, especially for web development.
The Modern Web Developerβs Toolkit: What Loco Brings to the Table
Simplicity
Rust dependencies are stable, simple, usually non-breaking because of the awesome Rust community. And then, there is just one tool for packages, linting, building and so on, and all tools are powered by Cargo.
With Loco you build just a single binary - that's your app. Deploy is just as easy as copying your binary to your server.
Safety & Performance
Loco is not a small framework but also not a big framework. We managed to do as little work as possible and then hand off to a time tested, stable and mature libraries to do the grunt work.
This recipe makes Loco super fast. On a typical M1 macbook a benchmark which includes database access is in the area of 30,000req/s.
Concurrency Made Simple
We run on Tokio and Axum on the server, and on Sidekiq-rs for background workers. We support a transparent and powerful concurrency model which can be switched:
- Async in-process, evented: powered by Tokio. Just run tokio tasks as you wish.
- Async background process: enqueue a task and get it performed on the background on the same machine or different machine. Uses a Redis queue under the hood.
You can switch the model from configuration.
What can $5 buy me?
Almost all architectural decision were made by building a "one person framework". That is a framework which lets you be hyper-productive, easy on your brain, but also easy on your budget.
For example, you can get a free hosting provider with very low resource numbers, and deploy your Loco app because a Loco app is just a 20mb binary, and using Rust means it is very light on memory and CPU.
You can have background jobs for free if you run async in tokio. Or you can spend $5 for a small Redis instance and have distributed background jobs.
Everything in Loco is optimized to get you started quick and cheap, on your own. You don't need a team, an architect, a specific cloud provider a budget or anything else in order to start other than your passion for your new project!
Key Features of Loco
Batteries included or Lean & mean: choose any
One of the key mindsets of Rust is to reason about cost. This is why building a "Rails on Rust" has to have knobs for developers to turn off anything that they consider too "heavy" or too "costly".
All around Loco, you can switch off parts of the framework in compile time using Rust features, and create your own learn, lightweight apps. For example:
- You can switch off the ORM/data stack completely, or pick specific databases to compile in
- You can remove any layer of the "MVC" model by simply deleting files (you pay no cost for not using those)
- You can switch off the authentication flows, websockets, CLI, storage providers and more
In fact, if you go through the starters wizard, you get something similar to "Loco editions" where each starter contains various hand-selected "batteries" to include.
Rust & Rails ergonomics
We do everything in Rust. If you imagine building Rails: a flexible, maleable, sometime DSL-looking framework, in Rust -- then Rust has a lot of missing pieces to provide that fantastic "playdough-like" DSL code.
One example can be dynamic loading of modules like Rails has. In Rails when you drop a file in a folder, it is picked up and becomes live on the next app restart. Instead of fighting and providing dynamic code loading for Rust, which will be foreign to Rust developers, we decided to avoid doing that completely, which means, if you have a new controller to add to your app, you specify it the Rust way: explicitly.
// specifying routes and controllers in src/app.rs
We try to balance magic and Rust-isms in Loco. We work hard on ergonomics, and when in doubt we always do what a Rust programmer would expect and not what a Ruby on Rails developer would expect.
Intuitive Design and Ease of Use
Loco sticks to the MVC (Model-View-Controller) abstraction for Web frameworks. Present in many paradigm shifts since the 90's, MVC have been able to reduce complexity for developers. This is what Rails does, too.
This means you can expect a similar intuitive folder layout like Rails has:
File/Folder | Purpose |
---|---|
src/ | Contains controllers, models, views, tasks and more |
app.rs | Main component registration point. Wire the important bits here. |
lib.rs | Various rust-specific exports of your components. |
bin/ | Has your main.rs file, you don't need to worry about it |
controllers/ | Contains controllers, all controllers are exported via mod.rs |
models/ | Contains models, models/_entities contains auto-generated SeaORM models, and models/*.rs contains your model extension logic, which are exported via mod.rs |
views/ | Contains JSON-based views. Structs which can serde and output as JSON through the API. |
workers/ | Has your background workers. |
mailers/ | Mailer logic and templates, for sending emails. |
fixtures/ | Contains data and automatic fixture loading logic. |
tasks/ | Contains your day to day business-oriented tasks such as sending emails, producing business reports, db maintenance, etc. |
tests/ | Your app-wide tests: models, requests, etc. |
config/ | A stage-based configuration folder: development, test, production |
channels/ | Contains all channels routes. |
Rapid Development and Deployment
We drive rapid development with cargo loco generate
:
Which is similar to Rails' generators, and has the same motivation: to get you started quickly. The code loco generate
creates is not just demo code, it is safe Rust. You can keep it and build on it.
We also support cargo loco deploy
which will offer multiple ways to deploy, and once you select your deployment method, will generate all the necessary files and configuration for your deploy as well as modify your app if needed. This is something Rails does not have out of the box.
SaaS authentication built in
Where usually with Rails you had to pick and specify a library like Devise, we provide the entire auth stack included with Loco. You can get both JWT or API key based authentication, which is modern, safe, and with security best-pracices baked-in.
The authentication flow is end-to-end and fully customizable, from registration to verification emails and more.
Test first design
Every component is fully testable. Rust being a static and safe programming language, achieving this key principle is much more complex than any other dynamic language, where you can monkey-patch, inject, or dynamically change code.
That said, Loco provides all needed facilities to make testing a breeze. From a convenient and ergonomic test kit for custom tailored for each app layer (models, views, workers, etc.) to snapshot testing to save on typing big robotic right-left assertion blocks at the end of each test.
A single binary to rule them all
We hold the principle that your Loco app is a single binary. Everything is embedded (other than server-side templates and assets which are held on disk on purpose since it's a different use case and are an optional feature): your code, workers, even email templates. You can deploy it to the cloud by having a Docker have just a single binary, or copy it manually to a Raspberry Pi. Having a single binary is magical in a way that it enables a diverse set of use cases.
Getting Started with Loco
Setting Up Your Development Environment
If you need, you can run Postgres and Redis (for background jobs) via Docker. Note myapp_development
is specific to an app called myapp
which we will soon choose:
Creating Your First Loco Project
Run loco new
(the loco
binary is provided by loco-cli
):
)
)
)
See that everything is OK with the doctor
command:
)
Finally, start your engines!
A taste of Loco: Example Features and Techniques
Using the Generator Framework
You can drive all of your development by generating parts of your app. For exapmle loco generate
will automatically create your entities and inject them into all the required places in your app.
You can use generate scaffold
to create a complete CRUD API based on a model description that you specify on the CLI.
Using authentication
By using the auth::JWT
extractor (extractors are extension points in Axum), you can opt-in and have authenticated routes:
async
Export user data with Tasks
Tasks are a way to encapsulate a workflow, errand, or operators use cases that need access to a running app's infrastructure such as models and data. One example of a use case is to be able to run a report and export its data, all from the CLI.
Here's how such a task might look like:
// find it in `src/tasks/user_report.rs`
And now you can list it:
And running it is simple:
Automation with background workers
You can even automate heavy tasks with a distributed background worker. Start by generating it:
The new worker code looks like this:
Now just code the logic, and run it:
If you want to run the worker on a separate machine or separate process:
Rendering Server Side Views
Loco supports Tera as a template engine out of the box.
pub async
Building JSON APIs with Serde
You automatically generated model supports Serde out of the box. Simply return it with format::json
to render JSON for your API.
pub async
Building Chats with WebSockets
Loco supports socketioxide
when you enable the channels
feature.
Connect your channel:
And implement your channel logic:
Seeding Data
Loco has a mini framework for seeding databases with initial data or fixed data, use it from anywhere you like (typically from within a Loco task):
let path = new;
.await?;
Piece of Cake Deployment
Run cargo loco deployment
and choose your preferred deployment stack from a list.
For example if you choose docker
you'll get a automagically generated Dockerfile
specially crafted for your app. Or if you choose shuttle
, Loco will change your app binary and configuration to fit a 1-command deploy to Shuttle.rs.
Sending Emails
You can send emails without any external service. It includes building your own email templates and sending them out the wire.
Like in Rails, you create mailers:
And Loco takes care of the required background jobs to actually perform the send task.
Storage and File Uploads
Loco contains a powerful framework for file uploads and blob storage which supports:
- Single storage
- Multiple storage providers
- Storage strategies and advanced logic for implementing primary/backup redundancy, automatic mirroring and others.
It is available through Context
from everywhere:
ctx.storage
.as_ref
.expect
.upload
.await?;
Conclusion and Next Steps
The Future of Web Development with Loco
Loco strives to be feature packed, and feature-parity with Rails, at least for the big features. Some other goals for Loco are:
- Be a one person framework. For those looking to build side projects easily, quickly, and cheap, and later scale those to startups with the same stable, robust, performant codebase.
- Fatigue-free maintenance. Simple dependencies, single binary, easy deployment.
- Everywhere possible - driven by tools and powertools. The Loco CLI, generators, test kits for easy testing.
- Embrace modern, sparkly, Web development practices, but also old and boring practices because boring is simple. This is why Loco supports both JSON API mode with clientside frontend as well as server-side template rendering.
Inviting Feedback and Contributions
Creating a diverse committer team is very important. It allows people of all skill levels coming from any kind of programming language to share ideas and interact. We especially want to see Rubyists and Rails devs contributing ideas and code to Loco.
Take a look at the issues, or come up with your own ideas and scratch your own itch -- we're accepting PRs!.