Going in cold inside the locomotive

Loco.rs

Rails and Rust? This is what I have been waiting for. I don’t know anything about Rust, but I do know Ruby and Rails.

I have been looking for an opportunity to learn Rust and again this is what I have been waiting for. I think that Loco is a great opportunity to learn the hard way by doing something I am familiar with.

How hard can it be? Let’s find out…

Installation

Reading the getting started guide it reads:

cargo install loco-cli
cargo install sea-orm-cli # Only when DB is needed

I think that cargo is Rust’s package manager. So…

brew info cargo
Error: No available formula with the name "cargo". Did you mean cargo-c, argo, cairo, carton, cdargs or charge?
cargo is part of the rust formula:
  brew install rust # BINGO!

I was right.

Now. I use asdf to version my languages and asdf-rust exists, so I am going to continue with that. But brew install rust is a great alternative.

asdf plugin-add rust https://github.com/asdf-community/asdf-rust.git

asdf list
>.... rust
  No versions installed

asdf list-all rust
>... 1.80.0
1.80.1
1.81.0

asdf install rust latest

# create a directory at your pleasure and cd into it

asdf local rust latest

cargo --version
> cargo 1.81.0 (2dbb1af80 2024-08-20)

Fantastic. Continuing with loco’s installation now that we have rust set up.

cargo install loco-cli
> ... installing and compiling a bunch of dependencies ...
> warning: be sure to add `.cargo/bin` to your PATH to be able to run the installed binaries # got this important warning!!!!
cargo install sea-orm-cli # Only when DB is needed
...

Add it to my path:


echo 'export PATH="/Users/me/.cargo/bin:$PATH"' >> ~/.zshrc

Everything ready for a new app.

Creating a new app

This were my choices:

loco new
 App name? · something
 What would you like to build? · SaaS app (with DB and user auth)
 Select a DB Provider · Sqlite
 Select your background worker type · Async (in-process tokyo async tasks)
 Select an asset serving configuration · Client (configures assets for frontend serving)

🚂 Loco app generated successfully in:
/Users/me/LocoRS/something

- assets: You've selected `clientside` for your asset serving configuration (remember to build your frontend in `frontend/`)

Starting the app:

cargo loco start
> ... more compilation and installation of stuff...
> Error: Message("one of the static path are not found, Folder `frontend/dist` fallback: `frontend/dist/index.html`") # Beautiful message

What happened is that since I wanted my project to interact with a frontend app and there is none… well, it failed. Fixing that!

cd frontend
ls -lh
> # What???

There is something there… a React app with rsbuild and things I don’t know.

asdf local nodejs latest # inside the frontend folder

npm install

npm run build # damn... that was REALLY fast

… and now we can go back where we were:

cargo loco start # all good and running

# in other terminal

curl localhost:5150/_ping
{"ok":true}% # Nice!

curl localhost:5150/_health
{"ok":true}% # Double nice!

Coding a new app

So on this part I want to focus on the potential similarities between Loco and Rails. As mentioned I don’t know anything about Rust.

I am going to “tweak” the commands because I don’t like to copy/paste everything in the tutorial to forget it right away.


cargo loco generate controller home
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.55s
     Running `target/debug/something-cli generate controller home`
Error: Message("Error: One of `kind`, `htmx`, `html`, or `api` must be specified.")

Hmmm…

cargo loco generate controller --help
    ...Actions

Options:
  -k, --kind <KIND>                The kind of controller actions to generate [possible values: api, html, htmx]
      --htmx                       Use HTMX controller actions
      --html                       Use HTML controller actions
      --api                        Use API controller actions
  -e, --environment <ENVIRONMENT>  Specify the environment [default: development]
  -h, --help                       Print help
  -V, --version                    Print version

Again and the comparison:

Loco
cargo loco generate controller home --api
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.47s
     Running `target/debug/something-cli generate controller home --api`
added: "src/controllers/home.rs"
injected: "src/controllers/mod.rs"
injected: "src/app.rs"
added: "tests/requests/home.rs"
injected: "tests/requests/mod.rs"
* Controller `Home` was added successfully.
* Tests for controller `Home` was added successfully. Run `cargo run test`.
Rails
be rails g controller home
      create
      invoke  erb
      create    app/views/home
      invoke  test_unit
      create    test/controllers/home_controller_test.rb
      invoke  helper
      create    app/helpers/home_helper.rb
      invoke    test_unit

Without checking the Loco files I can infer that what happened is pretty similar to what happened in Rails. Controller file, some tests and the configuration of the home route to its controller.

// cat src/controllers/home.rs

#![allow(clippy::missing_errors_doc)]
#![allow(clippy::unnecessary_struct_initialization)]
#![allow(clippy::unused_async)]

// Guessing these are dependencies
use loco_rs::prelude::*;
use axum::debug_handler;

// index
#[debug_handler]
pub async fn index(State(_ctx): State<AppContext>) -> Result<Response> {
    format::empty() // the response
}

// May be routes configuration. What is in 'routes.rb'
pub fn routes() -> Routes {
    Routes::new()
        .prefix("homes/") // "home" was pluralized. An inflection apparently
        .add("/", get(index))
}

Rails counterpart:

# cat app/controllers/home_controller.rb
class HomeController < ApplicationController
end

The Rails controller by default doesn’t have any route. I like that Loco does. It helps me to get started…

Without restarting Loco’s the server:

curl localhost:5150/homes

<!doctype html><html><head><title>Loco SaaS Starter</title><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1"><link rel="icon" href="/favicon.ico"><script defer="defer" src="/static/js/lib-polyfill.aebdf906.js"></script><script defer="defer" src="/static/js/lib-react.ae7929d9.js"></script><script defer="defer" src="/static/js/index.6c1b7b37.js"></script><link href="/static/css/index.0441752a.css" rel="stylesheet"></head><body><div id="root"></div></body></html>%

After:

<h1 class="text-3xl font-bold mb-4">Welcome to Loco!</h1>
<p class="mb-12">
  It looks like you've just started your Loco server, and this is the fallback
  page displayed when a route doesn't exist.
</p>

<div class="text-left space-y-4">
  <div>
    <h3 class="text-xl font-semibold">Remove this Fallback Page</h3>
    <p>
      To remove this fallback page, adjust the configuration in your
      <code class="bg-gray-200 rounded px-2 py-1">config/development.yaml</code>
      file. Look for the
      <code class="bg-gray-200 rounded px-2 py-1">fallback:</code> setting and
      disable or customize it.
    </p>
  </div>
  <div>
    <h3 class="text-xl font-semibold">Scaffold Your Application</h3>
    <p>Use the Loco CLI to scaffold your application:</p>
    <pre
      class="my-1"
    ><code class="bg-gray-200 rounded px-2 py-1">cargo loco generate scaffold movie title:string</code></pre>
    <p>This creates models, controllers, and views.</p>
  </div>
</div>

It seems it doesn’t like that I used cUrl, but when opened on the browser. It is just an empty page.

Edited the code with:

pub async fn index(State(_ctx): State<AppContext>) -> Result<Response> {
    format::text("A future rustacean!")
}

and got that on the browser.

Following a little bit more the guide added this code:

pub async fn echo(req_body: String) -> String {
    req_body
}

pub fn routes() -> Routes {
    Routes::new()
        .prefix("homes/")
        .add("/", get(index))
        .add("/echo", post(echo))
}

cUrling it:

curl -v -d "{'data': 'Loco rocks'}" localhost:5150/homes/echo

< HTTP/1.1 200 OK
< content-type: text/plain; charset=utf-8
< content-length: 22
< vary: origin, access-control-request-method, access-control-request-headers
< access-control-allow-origin: *
< access-control-expose-headers: *
< content-security-policy: default-src 'self' https:; font-src 'self' https: data:; img-src 'self' https: data:; object-src 'none'; script-src 'unsafe-inline' 'self' https:; style-src 'self' https: 'unsafe-inline'
< strict-transport-security: max-age=631138519
< x-content-type-options: nosniff
< x-download-options: noopen
< x-frame-options: sameorigin
< x-permitted-cross-domain-policies: none
< x-xss-protection: 0
< x-powered-by: loco.rs
< x-request-id: fd9de454-4e11-4249-b0f5-435658c4bc1a
< date: Wed, 09 Oct 2024 20:20:09 GMT
<
* Connection #0 to host localhost left intact
{'data': 'Loco rocks'}%

Neat!

Conclusion

This was enough for me to gain a LOT of interest on this project. Knowing the structure will be easy for me to find the correct files to achieve what I want, I believe.

In terms of coding:

 ... -> Result<Response>
 // plus
 format::text("A future rustacean!")

// and

 -> String {
 ... req_body

Just tell there are different formatters and ways manipulate the response body with “types”. This file is just pointing me in the right direction of where and what to look. I am going to continue working with this framework… I am definitely convinced that it will teach me things.

Forking!

favicon vanhalt.com

© 2024 Aria template by https://x.com/xkaibi

𝕏 GitHub