The Loco Guide
Guide Assumptions
This is a "long way round" tutorial. It is long and indepth on purpose, it shows you how to build things manually and automatically using generators, so that you learn the skills to build and also how things work.
What's with the name?
The name Loco
comes from locomotive, as a tribute to Rails, and loco
is easier to type than locomotive
:-). Also, in some languages it means "crazy" but that was not the original intention (or, is it crazy to build a Rails on Rust? only time will tell!).
How much Rust do I need to know?
You need to be familiar with Rust to a beginner but not more than moderate-beginner level. You need to know how to build, test, and run Rust projects, have used some popular libraries such as clap
, regex
, tokio
, axum
or other web framework, nothing too fancy. There are no crazy lifetime twisters or complex / too magical, macros in Loco that you need to know how they work.
What is Loco?
Loco is strongly inspired by Rails. If you know Rails and Rust, you'll feel at home. If you only know Rails and new to Rust, you'll find Loco refreshing. We do not assume you know Rails.
Loco is a Web or API framework for Rust. It's also a productivity suite for developers: it contains everything you need while building a hobby or your next startup. It's also strongly inspired by Rails.
- You have a variant of the MVC model, which removes the paradox of option. You deal with building your app, not making academic decisions for what abstractions to use.
- Fat models, slim controllers. Models should contain most of your logic and business implementation, controllers should just be a lightweight router that understands HTTP and moves parameters around.
- Command line driven to keep your momentum and flow. Generate stuff over copying and pasting or coding from scratch.
- Every task is "infrastructure-ready", just plug in your code and wire it in: controllers, models, views, tasks, background jobs, mailers, and more.
- Convention over configuration: decisions are already done for you -- the folder structure matter, configuration shape and values matter, and the way an app is wired matter to how an app operates and for you do be the most effective.
Creating a New Loco App
You can follow this guide for a step-by-step "bottom up" learning, or you can jump and go with the tour instead for a quicker "top down" intro.
Installing
Creating a new Loco app
Now you can create your new app (choose "SaaS app" for built-in authentication).
)
Here's a rundown of what Loco creates for you by default:
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. |
Hello, Loco!
Let's get some responses quickly. For this, we need to start up the server.
You can now switch to to myapp
:
Starting the server
And now, let's see that it's alive:
}
The built in _ping
route will tell your load balancer everything is up.
Let's see that all services that are required are up:
}
_health
route will tell you that you have configured your app properly: it can establish a connection to your Database and Redis instances successfully.
Say "Hello", Loco
Let's add a quick hello response to our service.
This is the generated controller body:
use *;
use debug_handler;
pub async
Change the index
handler body:
// replace
empty
// with this
text
Start the server:
Now, let's test it out:
Loco has powerful generators, which will make you 10x productive and drive your momentum when building apps.
If you'd like to be entertained for a moment, let's "learn the hard way" and add a new controller manually as well.
Add a file called home.rs
, and pub mod home;
it in mod.rs
:
src/
controllers/
auth.rs
home.rs <--- add this file
users.rs
mod.rs <--- 'pub mod home;' the module here
Next, set up a hello route, this is the contents of home.rs
:
// src/controllers/home.rs
use *;
// _ctx contains your database connection, as well as other app resource that you'll need
async
Finally, register this new controller routes in app.rs
:
src/
controllers/
models/
..
app.rs <---- look here
Add the following in routes()
:
// in src/app.rs
That's it. Kill the server and bring it up again:
And hit /home/hello
:
You can take a look at all of your routes with:
$ cargo loco routes
..
..
[POST] /api/auth/login
[POST] /api/auth/register
[POST] /api/auth/reset
[POST] /api/auth/verify
[GET] /home/hello <---- this is our new route!
..
..
$
/api
because it is client-side ready and we are using the --api
option in scaffolding. When using client-side routing like React Router, we want to separate backend routes from client routes: the browser will use
/home
but not /api/home
which is the backend route, and you can call /api/home
from the client with no worries. Nevertheless, the routes: /_health
and /_ping
are exceptions, they stay at the root.
MVC and You
Traditional MVC (Model-View-Controller) originated in desktop UI programming paradigms. However, its applicability to web services led to its rapid adoption. MVC's golden era was around the early 2010s, and since then, many other paradigms and architectures have emerged.
MVC is still a very strong principle and architecture to follow for simplifying projects, and this is what Loco follows too.
Although web services and APIs don't have a concept of a view because they do not generate HTML or UI responses, we claim stable, safe services and APIs indeed has a notion of a view -- and that is the serialized data, its shape, its compatibility and its version.
// a typical loco app contains all parts of MVC
src/
controllers/
users.rs
mod.rs
models/
_entities/
users.rs
mod.rs
users.rs
mod.rs
views/
users.rs
mod.rs
This is an important cognitive principle. And the principle claims that you can only create safe, compatible API responses if you treat those as a separate, independently governed thing -- hence the 'V' in MVC, in Loco.
Generating a model
A model in Loco represents data and functionality. Typically the data is stored in your database. Most, if not all, business processes of your applications would be coded on the model (as an Active Record) or as an orchestration of a few models.
Let's create a new model called Article
:
Database migrations
Keeping your schema honest is done with migrations. A migration is a singular change to your database structure: it can contain complete table additions, modifications, or index creation.
// this was generated into `migrations/` from the command:
//
// $ cargo loco generate model article title:string content:text
//
// it is automatically applied by Loco's migrator framework.
// you can also apply it manually using the command:
//
// $ cargo loco db migrate
//
You can recreate a complete database by applying migrations in-series onto a fresh database -- this is done automatically by Loco's migrator (which is derived from SeaORM).
When generating a new model, Loco will:
- Generate a new "up" database migration
- Apply the migration
- Reflect the entities from database structure and generate back your
_entities
code
You will find your new model as an entity, synchronized from your database structure in models/_entities/
:
src/models/
├── _entities
│ ├── articles.rs <-- sync'd from db schema, do not edit
│ ├── mod.rs
│ ├── prelude.rs
│ └── users.rs
├── articles.rs <-- generated for you, your logic goes here.
├── mod.rs
└── users.rs
Using playground
to interact with the database
Your examples/
folder contains:
playground.rs
- a place to try out and experiment with your models and app logic.
Let's fetch data using your models, using playground.rs
:
// located in examples/playground.rs
// use this file to experiment with stuff
use ;
// to refer to articles::ActiveModel, your imports should look like this:
use ;
async
Return a list of posts
In the example, we use the following to return a list:
let res = find.all.await.unwrap;
To see how to run more queries, go to the SeaORM docs.
To execute your playground, run:
$ cargo playground
Now, let's insert one item:
async
And run the playground again:
We're now ready to plug this into an articles
controller. First, generate a new controller:
Edit src/controllers/articles.rs
:
use *;
use crate articles;
pub async
Now, start the app:
And make a request:
}
Building a CRUD API
Next we'll see how to get a single article, delete, and edit a single article. Getting an article by ID is done using the Path
extractor from axum
.
Replace the contents of articles.rs
with this:
// this is src/controllers/articles.rs
use *;
use ;
use crate;
async
pub async
pub async
pub async
pub async
pub async
A few items to note:
Params
is a strongly typed required params data holder, and is similar in concept to Rails' strongparams, just safer.Path(id): Path<i32>
extracts the:id
component from a URL.- Order of extractors is important and follows
axum
's documentation (parameters, state, body). - It's always better to create a
load_item
helper function and use it in all singular-item routes. - While
use loco_rs::prelude::*
brings in anything you need to build a controller, you should note to importcrate::models::_entities::articles::{ActiveModel, Entity, Model}
as well asSerialize, Deserialize
for params.
#[debug_handler]
macro to handlers can help by printing out better error messages. More information about extractors can be found in the axum documentation.
You can now test that it works, start the app:
Add a new article:
}
Get a list:
} }
Adding a second model
Let's add another model, this time: Comment
. We want to create a relation - a comment belongs to a post, and each post can have multiple comments.
Instead of coding the model and controller by hand, we're going to create a comment scaffold which will generate a fully working CRUD API comments. We're also going to use the special references
type:
references:<table>
is also available. For when you want to have a different name for your column.
If you peek into the new migration, you'll discover a new database relation in the articles table:
..
..
.col
.foreign_key
..
..
Now, lets modify our API in the following way:
- Comments can be added through a shallow route:
POST comments/
- Comments can only be fetched in a nested route (forces a Post to exist):
GET posts/1/comments
- Comments cannot be updated, fetched singular, or deleted
In src/controllers/comments.rs
, remove unneeded routes and functions:
Also adjust the Params & update functions in src/controllers/comments.rs
, by updating the scaffolded code marked with <- add this
Now we need to fetch a relation in src/controllers/articles.rs
. Add the following route:
And implement the relation fetching:
// to refer to comments::Entity, your imports should look like this:
use crate;
pub async
Now start the app again:
Add a comment to Article 1
:
}
And, fetch the relation:
}
This ends our comprehensive Guide to Loco. If you made it this far, hurray!.
Tasks: export data report
Real world apps require handling real world situations. Say some of your users or customers require some kind of a report.
You can:
- Connect to your production database, issue ad-hoc SQL queries. Or use some kind of DB tool. This is unsafe, insecure, prone to errors, and cannot be automated.
- Export your data to something like Redshift, or Google, and issue a query there. This is a waste of resource, insecure, cannot be tested properly, and slow.
- Build an admin. This is time-consuming, and waste.
- Or build an adhoc task in Rust, which is quick to write, type safe, guarded by the compiler, fast, environment-aware, testable, and secure.
This is where cargo loco task
comes in.
First, run cargo loco task
to see current tasks:
Generate a new task user_report
In src/tasks/user_report.rs
you'll see the task that was generated for you. Replace it with following:
// find it in `src/tasks/user_report.rs`
use *;
use Vars;
use crate users;
You can modify this task as you see fit. Access the models with app_context
, or any other environmental resources, and fetch
variables that were given through the CLI with vars
.
Running this task is done with:
$ cargo loco task user_report var1:val1 var2:val2 ...
args: Vars
!!! user_report: listing users !!!
------------------------
done: 0 users
If you have not added an user before, the report will be empty.
To add an user check out chapter Registering a New User of A Quick Tour with Loco.
Remember: this is environmental, so you write the task once, and then execute in development or production as you wish. Tasks are compiled into the main app binary.
Authentication: authenticating your requests
If you chose the SaaS App
starter, you should have a fully configured authentication module baked into the app.
Let's see how to require authentication when adding comments.
Go back to src/controllers/comments.rs
and take a look at the add
function:
pub async
To require authentication, we need to modify the function signature in this way:
async