Last week, I decided to see the capabilities of OpenAI's image generation. However, I noticed that one has to pay to use the , while the was free, even though rate-limited. Dall.E offers samples, but I wanted to keep learning Rust. So far, I've created a . In this post, I want to describe how you can create a webapp with server-side rendering. web interface API Node.js and Python REST API The context is a runtime for asynchronous programming for Rust; is a web framework that leverages the former. I already used Axum for the previous REST API, so I decided to continue. Tokio Axum A server-side rendering webapp 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 to generate order-sensitive code with code. templating 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 <abbr title="Java Server Pages">JSP</abbr>s 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: , based on Handlebars handlebars-rust , based on Liquid liquid , based on Jinja, as the next two Tera 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 , 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 axum-template in short Setting up axum-template is relatively straightforward. First, we add the dependency to Cargo: 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: 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), }); } Create a type alias Create a dedicated structure to hold the engine state Create a Jinja-specific environment 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 folder. I spent a nontrivial amount of time to realize it. src Configure Axum to use the engine Here are the base items: is a facade over the templating library Engine Templates are stored in a hashtable-like structure. With the MiniJinja implementation, according to the configuration above, is simply the filename, , Key e.g. home.html The final parameter has no requirement. The library will read its attributes and use them to fill the template. S I won't go into the details of the template itself, as the is quite good. documentation The impl return It has nothing to do with templating, but this mini-project allowed me to ponder the return type. In my previous REST project, I noticed that Axum handler functions return , but I didn't think about it. It's indeed pretty simple: impl impl If your function returns a type that implements , you can write its return type as . This can help simplify your type signatures quite a lot! MyTrait -> impl MyTrait -- 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: 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. 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 } } Cannot use ; need to use the explicit type impl IntoResponse Response Explicit transform the return value to Response Using the application You can build from the source or run the Docker image, available at . The only requirement is to provide an OpenAI authentication token via an environment variable: DockerHub docker run -it --rm -p 3000:3000 -e OPENAI_TOKEN=... nfrankel/rust-dalle:0.1.0 Enjoy! 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 axum-template MiniJinja Dall.E webapp Image generation API Originally at on April 30, 2023. published A Java Geek