20 Feb 2025

Easy Peasy Rusty Squeezy

image article easy peasy rusty squeezy

Building web apps in Rust ... in a very easy way

This project showcases a web server using Axum for routing, Minijinja for templating, and Storyblok for content management—all wrapped up in a set of macros that operate by manipulating data.

Core Ingredients

  1. Axum for Routing
    Axum’s async model, powered by Tokio, efficiently routes requests. Whether you’re dealing with static files or dynamic paths, Axum handles concurrency neatly so you can serve multiple requests without skipping a beat.

  2. Minijinja for Templating
    Minijinja interprets templates in a syntax akin to Jinja from Python. Through a configured environment wrapper (EnvWrapper), templates like default.jinja or index.jinja can be rendered smoothly, keeping your views clean and compartmentalized.

  3. Storyblok Integration
    You can fetch content from Storyblok by injecting environment variables (ST_TOKEN and ST_BASE_URL) via your .env file. The macros from macros.rs use these values to request data from your Storyblok space.

Macro Magic: get_data! and extract_components!

Why Rust?

Rust's unique selling point is its ability to ensure memory safety without relying on a garbage collector. Through its innovative ownership system and strict compile-time checks, Rust empowers developers to write concurrent code with confidence, avoiding many common pitfalls:

async fn handle_request(req: Request) -> Response {
    // Your safe, concurrent code here
}

Key Advantages:

  1. Memory Safety Without Overhead: Rust's ownership model prevents data races and memory leaks at compile-time, without runtime costs.

  2. Zero-Cost Abstractions: Rust allows you to write high-level code that often compiles down to highly efficient machine instructions, rivaling hand-optimized C or C++.

  3. Fearless Concurrency: With Rust's guarantees, you can leverage powerful concurrency patterns without fear of subtle bugs.

  4. Efficient Async I/O: When combined with the Tokio runtime, Axum can efficiently handle numerous concurrent connections:

let app = Router::new()
    .route("/", get(handler))
    .layer(TraceLayer::new_for_http());

axum::Server::bind(&"0.0.0.0:8000".parse().unwrap())
    .serve(app.into_make_service())
    .await.unwrap();

Example Benchmark

Rust/Axum      | ■■■■■■■■■■■■■■■■■■■■■■  35k RPS
Node.js        | ■■■■■■■■■■■■■  20k RPS
Python/Flask   | ■■■■  10k RPS

(Fictional numbers, but they illustrate Rust’s high-performance advantage.)

Architecture Overview

+--------------------+          +-----------------------------+
|  Axum Router       | -------> |  Page & Static File Routes  |
\+-------------------+          +-----------------------------+
|
v
\+-------------------+          +-----------------------------+
|  Minijinja         | -------> |  Templates (Jinja-style)    |
\+-------------------+          +-----------------------------+
|
v
\+-----------------------------+
|  Data Macros                 |
|  - get_data!                 |
|  - extract_components!       |
\+-----------------------------+
  1. Axum Router: Manages requests, assigning each route to the proper service handler.

  2. Minijinja Environment: Stores compiled templates and renders them on-demand.

  3. Data Macros: Orchestrate the retrieval and transformation of Storyblok content.

Conclusion

This project demonstrates Rust’s power in delivering a web application that’s both performance-focused and flexible enough to work with a headless CMS like Storyblok. Axum provides the concurrency, Minijinja handles the templating, and the macros do the heavy lifting for data retrieval and parsing. Occasional jokes aside, it’s a seriously cool stack. Dive in, and enjoy the speed and safety that Rust brings to modern web development.