Controller

Loco is a framework that wraps around axum, offering a straightforward approach to manage routes, middlewares, authentication, and more right out of the box. At any point, you can leverage the powerful axum Router and extend it with your custom middlewares and routes.

Router Capabilities

Loco router provides several capabilities:

Defining App Routes in the App Hook

In the example below, multiple controllers are added to your app within the AppRouter. During initialization, you can choose between:

  • AppRoutes::with_default_routes(): Adds default loco endpoints like ping or heathy.
  • AppRoutes::empty(): Creates an empty router without default routes.
pub struct App;
#[async_trait]
impl Hooks for App {
    fn routes() -> AppRoutes {
        AppRoutes::with_default_routes()
            .add_route(controllers::foo::routes())
            .add_route(controllers::bar::routes())
    }
    ...
}

Adding a Prefix to All Routes

You can add a prefix URL to all your routes by providing the prefix to the AppRouter instance.

Adding extra state

Your app context and state is held in AppContext and is what Loco provides and sets up for you. There are cases where you'd want to load custom data, logic, or entities when the app starts and be available to use in all controllers.

You could do that by using Axum's Extension. Here's an example for loading an LLM model, which is a time consuming task, and then providing it to a controller endpoint, where its already loaded, and fresh for use.

First, add a lifecycle hook in src/app.rs:

    // in src/app.rs, in your Hooks trait impl override the `after_routes` hook:

    async fn after_routes(router: axum::Router, _ctx: &AppContext) -> Result<axum::Router> {
        // cache should reside at: ~/.cache/huggingface/hub
        println!("loading model");
        let model = Llama::builder()
            .with_source(LlamaSource::llama_7b_code())
            .build()
            .unwrap();
        println!("model ready");
        let st = Arc::new(RwLock::new(model));

        Ok(router.layer(Extension(st)))
    }

Next, consume this state extension anywhere you like. Here's an example controller endpoint:

async fn candle_llm(Extension(m): Extension<Arc<RwLock<Llama>>>) -> impl IntoResponse {
    // use `m` from your state extension
    let prompt = "write binary search";
    ...
}

Routes in Controllers

Controllers define Loco routes capabilities. In the example below, a controller creates one GET endpoint and one POST endpoint:

use axum::routing::{get, post};
Routes::new()
    .add("/", get(hello))
    .add("/echo", post(echo))

You can also define a prefix for all routes in a controller using the prefix function.

Sending Responses

Response senders are in the format module. Here are a few ways to send responses from your routes:


// keep a best practice of returning a `Result<impl IntoResponse>` to be able to swap return types transparently
pub async fn list(...) -> Result<impl IntoResponse> // ..

// use `json`, `html` or `text` for simple responses
format::json(item)


// use `render` for a builder interface for more involved responses. you can still terminate with
// `json`, `html`, or `text`
format::render()
    .etag("foobar")?
    .json(Entity::find().all(&ctx.db).await?)

Creating a Controller with the CLI Generator

Provides a convenient code generator to simplify the creation of a starter controller connected to your project. Additionally, a test file is generated, enabling easy testing of your controller.

Generate a controller:

$ cargo loco generate controller [OPTIONS] <CONTROLLER_NAME>

After generating the controller, navigate to the created file in src/controllers to view the controller endpoints. You can also check the testing (in folder tests/requests) documentation for testing this controller.

Creating a Controller Manually

1. Create a Controller File

Start by creating a new file under the path src/controllers. For example, let's create a file named example.rs.

2. Load the File in mod.rs

Ensure that you load the newly created controller file in the mod.rs file within the src/controllers folder.

3. Register the Controller in App Hooks

In your App hook implementation (e.g., App struct), add your controller's Routes to AppRoutes:

// src/app.rs

pub struct App;
#[async_trait]
impl Hooks for App {
    fn routes() -> AppRoutes {
        AppRoutes::with_default_routes().prefix("prefix")
            .add_route(controllers::example::routes())
    }
    ...
}

Displaying Registered Controllers

To view a list of all your registered controllers, execute the following command:

$ cargo loco controller

[GET] /_health
[GET] /_ping
[POST] /auth/forgot
[POST] /auth/login
[POST] /auth/register
[POST] /auth/reset
[POST] /auth/verify
[GET] /notes/
[POST] /notes/
[GET] /notes/:id
[DELETE] /notes/:id
[POST] /notes/:id
[GET] /user/current

This command will provide you with a comprehensive overview of the controllers currently registered in your system.

Middleware

Compression

Loco leverages CompressionLayer to enable a one click solution.

To enable response compression, based on accept-encoding request header, simply edit the configuration as follows:

#...
  middlewares:
    compression:
      enable: true

Doing so will compress each response and set content-encoding response header accordingly.

Prcompressed assets

Loco leverages ServeDir::precompressed_gzip to enable a one click solution of serving pre compressed assets.

If a static assets exists on the disk as a .gz file, Loco will serve it instead of compressing it on the fly.

#...
middlewares:
  ...
  static_assets:
    ...
    precompressed: true

(More middleware docs TBD)