DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Newsletter
Log In / Join
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Related

  • Leveling Up My GraphQL Skills: Real-Time Subscriptions
  • What Is API-First?
  • Building a REST Service That Collects HTML Form Data Using Netbeans, Jersey, Apache Tomcat, and Java
  • Porting GPU Drivers to Rust on ARM64: The Hardest Trial for Kernel-Level Computing

Trending

  • MQTT, Kafka, NATS, or Zenoh? Choosing Edge-to-Cloud Messaging by Traffic Pattern
  • The "Just Read the Code" Myth Is Killing Your AI Workflow
  • Introduction to Error Prone: How to Catch Bugs at Compile Time
  • Testing Java and Spring Applications With Kotlin: Low-Risk to Adoption

Draft This article hasn't been published, so readers can't see it.

In moderation This article is waiting for review, so readers can't see it yet.

Deleted This article has been deleted, so readers can't see it.

  1. DZone
  2. Coding
  3. Languages
  4. Server-Side Rendering in Rust: A Dall.E Use-Case

Server-Side Rendering in Rust: A Dall.E Use-Case

In this tutorial, learn how you can create a Web app with server-side rendering and discover another side of Rust: HTML templating with Axum.

By 
Nicolas Fränkel
·
May. 05, 23 · Tutorial
Post on X (opens in a new tab) Share on LinkedIn (opens in a new tab)
4.9K Views

Join the DZone community Save articles, join the discussion, and download Refcards and Trend Reports with a free account.

Join for free Already a member? Log in

Last week, I decided to see the capabilities of OpenAI's image generation. However, I noticed that one has to pay to use the web interface, while the API was free, even though rate-limited. Dall.E offers Node.js and Python samples, but I wanted to keep learning Rust. So far, I've created a REST API. In this post, I want to describe how you can create a Web app with server-side rendering.

The Context

Tokio is a runtime for asynchronous programming for Rust; Axum is a web framework that leverages the former. I already used Axum for the previous REST API, so I decided to continue.

A server-side rendering Web app is similar to a REST API. The only difference is that the former returns HTML pages, and the latter JSON payloads. From an architectural point of view, there's no difference; from a development one, however, it plays a huge role.

There's no visual requirement in JSON, so ordering is not an issue. You get a struct; you serialize it, and you are done. You can even do it manually; it's no big deal - though a bit boring. On the other hand, HTML requires a precise ordering of the tags: if you create it manually, maintenance is going to be a nightmare. We invented templating to generate order-sensitive code with code.

While templating is probably age-old, PHP was the language to popularize it. One writes regular HTML and, when necessary, adds the snippets that need to be dynamically interpreted. In the JVM world, I used JSPs and Apache Velocity, the latter, to generate RTF documents.

Templating in Axum

As I mentioned above, I want to continue using Axum. Axum doesn't offer any templating solution out-of-the-box, but it allows integrating any solution through its API.

Here is a small sample of templating libraries that I found for Rust:

  • handlebars-rust, based on Handlebars
  • liquid, based on Liquid
  • Tera, based on Jinja, as the next two
  • askama
  • MiniJinja
  • etc.

As a developer, however, I'm lazy by essence, and I wanted something integrated with Axum out of the box. A quick Google search lead me to axum-template, which seems pretty new but very dynamic. The library is an abstraction over handlebars, askama, and minijinja. You can use the API and change implementation whenever you want.

axum-template in Short

Setting up axum-template is relatively straightforward. First, we add the dependency to Cargo:

Shell
 
cargo add axum-template


Then, we create an engine depending on the underlying implementation and configure Axum to use it. Here, I'm using Jinja:

Rust
 
type AppEngine = Engine<Environment<'static>>;                 //1

#[derive(Clone, FromRef)]
struct AppState {                                              //2
    engine: AppEngine,
}

#[tokio::main]
async fn main() {
    let mut jinja = Environment::new();                        //3
    jinja.set_source(Source::from_path("templates"));          //4
    let app = Router::new()
        .route("/", get(home))
        .with_state(AppState {                                 //5
            engine: Engine::from(jinja),
        });
}


  1. Create a type alias.
  2. Create a dedicated structure to hold the engine state.
  3. Create a Jinja-specific environment.
  4. Configure the folder to read templates from. The path is relative to the location where you start the binary; it shouldn't be part of the src folder. I spent a nontrivial amount of time to realize it.
  5. Configure Axum to use the engine.

Here are the base items:

Base items

  • Engine is a facade over the templating library
  • Templates are stored in a hashtable-like structure. With the MiniJinja implementation, according to the configuration above, Key is simply the filename, e.g., home.html
  • The final S parameter has no requirement. The library will read its attributes and use them to fill the template.

I won't go into the details of the template itself, as the documentation is quite good.

The impl Return

It has nothing to do with templating, but this mini-project allowed me to ponder the impl return type. In my previous REST project, I noticed that Axum handler functions return impl, but I didn't think about it. It's indeed pretty simple:

If your function returns a type that implements MyTrait, you can write its return type as -> impl MyTrait. This can help simplify your type signatures quite a lot!

- Rust By Example

However, it has interesting consequences. If you return a single type, it works like a charm. However, if you return more than one, you either need a common trait across all returned types or to be explicit about it.

Here's the original sample:

Rust
 
async fn call(engine: AppEngine, Form(state): Form<InitialPageState>) -> impl IntoResponse {
    RenderHtml(Key("home.html".to_owned()), engine, state)
}


If the page state needs to differentiate between success and error, we must create two dedicated structures.

Rust
 
async fn call(engine: AppEngine, Form(state): Form<InitialPageState>) -> Response {                     //1
  let page_state = PageState::from(state);
  if page_state.either.is_left() {
    RenderHtml(Key("home.html".to_owned()), engine, page_state.either.left().unwrap()).into_response()  //2
  } else {
    RenderHtml(Key("home.html".to_owned()), engine, page_state.either.right().unwrap()).into_response() //2
  }
}


  1. Cannot use impl IntoResponse; need to use the explicit Response type
  2. Explicit transform the return value to Response

Using the Application

You can build from the source or run the Docker image, available at DockerHub. The only requirement is to provide an OpenAI authentication token via an environment variable:

Shell
 
docker run -it --rm -p 3000:3000 -e OPENAI_TOKEN=... nfrankel/rust-dalle:0.1.0


Enjoy!

Welcome to Rust Dall-E

Conclusion

This small project allowed me to discover another side of Rust: HTML templating with Axum. It's not the usual use case for Rust, but it's part of it anyway.

On the Dall.E side, I was not particularly impressed with the capabilities. Perhaps I didn't manage to describe the results in the right way. I'll need to up my prompt engineering skills.

In any case, I'm happy that I developed the interface, if only for fun.

The complete source code for this post can be found on GitHub.

To Go Further:

  • axum-template
  • Image generation API
HTML REST Use case Rust (programming language) Deep learning Web development tools

Published at DZone with permission of Nicolas Fränkel. See the original article here.

Opinions expressed by DZone contributors are their own.

Related

  • Leveling Up My GraphQL Skills: Real-Time Subscriptions
  • What Is API-First?
  • Building a REST Service That Collects HTML Form Data Using Netbeans, Jersey, Apache Tomcat, and Java
  • Porting GPU Drivers to Rust on ARM64: The Hardest Trial for Kernel-Level Computing

Partner Resources

×

Comments

Loading comments

The comments didn't load

Please try again in a moment.

Likes

Loading

The next likes didn't load. Please try again.

The likes didn't load

Please try again in a moment.

See who liked this

Log in or join DZone for free to see who liked this article.

Log in Join for free
  • RSS
  • X
  • Facebook

ABOUT US

  • About DZone
  • Support and feedback
  • Community research

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 215
  • Nashville, TN 37211
  • [email protected]

Let's be friends:

  • RSS
  • X
  • Facebook