Rusty Backends

This post was written together @sekael and @zkck Rust has been a developer favorite for many years now and is consistently highly admired by developers[1]. It made a buzz when it was introduced as a complementary language to C and Assembly in the Linux kernel with version 6.8[2] and its speed and memory safety make it a popular choice for low-level and performance-critical applications. But with the whole world using the internet, we wanted to find out whether it can also do web and challenge the reigning backend champions like Java Spring or Go. To find answers, we wanted to get our hands dirty with three popular Rust web frameworks including rocket, axum, and actix, and get a feeling for their performance, features, and most importantly the developer experience. In this post, we will examine each of these frameworks by implementing the same example API endpoints in each one, connect to a MongoDB database, and run the server in a Docker container. All three, rocket, axum, and actix, cover the full range of functionality you would expect from a web framework, like routes, handlers, request and response parsing, middleware, state management, database interactions, logging, and testing. Furthermore, all of these frameworks perform well and will meet and surpass the needs of most web applications, so the choice really comes down to ergonomics and how it feels to develop within each framework. So let’s jump right in… Rocket (rocket.rs) rwf2 / Rocket A web framework for Rust. Rocket Rocket is an async web framework for Rust with a focus on usability, security extensibility, and speed. #[macro_use] extern crate rocket; #[get("//")] fn hello(name: &str, age: u8) -> String { format!("Hello, {} year old named {}!", age, name) } #[launch] fn rocket() -> _ { rocket::build().mount("/hello", routes![hello]) } Visiting localhost:8000/hello/John/58, for example, will trigger the hello route resulting in the string Hello, 58 year old named John! being sent to the browser. If an string was passed in that can't be parsed as a u8, the route won't get called, resulting in a 404 error. Documentation Rocket is extensively documented: Overview: A brief… View on GitHub Rocket is a web framework for Rust that comes with the most batteries included out of the three we looked at. It positions itself in the same league as Rails (Ruby) or Flask (Python) and aims to offer functionality for anything a web backend might need, including route handling, request and response parsing, database integrations, validation, and so on. One of its main goals is to minimize the amount of boiler plate code you will have to write, and it does so through metaprogramming, i.e. heavy use of Rust macros. This makes it easy for the developer to integrate her actual logic with the rocket framework. Let’s have a quick look on how you might define a route handler including parameter guards and validation, a database connection pool, and a JSON object. We can define a route handler simply by adding the appropriate macro to a function. #[get("/texts/")] pub async fn get_text(db: Connection, uuid: Uuid) -> (Status, Value) { // ... (Status::Ok, json!({"data": data})) } If the request parameter cannot be parsed, the appropriate response code is sent instead of calling the function, so we have the usual comfort of type safety. Request bodies are handled very similarly, and of course, parsing integrates seamlessly with serde. #[derive(Deserialize)] pub struct Message _ { rocket::build() .attach(TextsDatabase::init()) .mount("/", routes![get_text, post_text]) } We have found rocket very enjoyable to work with, especially if you want to focus on the business logic of your server and feel safe trusting the framework to take handling of the boilerplate code off your hands. If you are rather new to Rust and maybe switching over from other frameworks that make heavy use of annotations, you will feel at home with Rocket. Axum tokio-rs / axum Ergonomic and modular web framework built with Tokio, Tower, and Hyper axum axum is a web application framework that focuses on ergonomics and modularity. More information about this crate can be found in the crate documentation. High level features Route requests to handlers with a macro free API. Declaratively parse requests using extractors. Simple and predictable error handling model. Generate responses with minimal boilerplate. Take full advantage of the tower and tower-http ecosystem of middleware, services, and utilities. In particular the last point is what sets axum apart from other frameworks axum doesn't have its own middleware system but instead uses tower::Service. This means axum gets timeouts, tracing, compression, authorization,

Jan 21, 2025 - 07:48
 0
Rusty Backends

This post was written together @sekael and @zkck

Rust has been a developer favorite for many years now and is consistently highly admired by developers[1]. It made a buzz when it was introduced as a complementary language to C and Assembly in the Linux kernel with version 6.8[2] and its speed and memory safety make it a popular choice for low-level and performance-critical applications. But with the whole world using the internet, we wanted to find out whether it can also do web and challenge the reigning backend champions like Java Spring or Go.

To find answers, we wanted to get our hands dirty with three popular Rust web frameworks including rocket, axum, and actix, and get a feeling for their performance, features, and most importantly the developer experience.

In this post, we will examine each of these frameworks by implementing the same example API endpoints in each one, connect to a MongoDB database, and run the server in a Docker container.

All three, rocket, axum, and actix, cover the full range of functionality you would expect from a web framework, like routes, handlers, request and response parsing, middleware, state management, database interactions, logging, and testing. Furthermore, all of these frameworks perform well and will meet and surpass the needs of most web applications, so the choice really comes down to ergonomics and how it feels to develop within each framework.

So let’s jump right in…

Rocket (rocket.rs)

GitHub logo rwf2 / Rocket

A web framework for Rust.

Rocket

Build Status Rocket Homepage Current Crates.io Version Matrix: #rocket:mozilla.org

Rocket is an async web framework for Rust with a focus on usability, security extensibility, and speed.

#[macro_use] extern crate rocket;

#[get("//")]
fn hello(name: &str, age: u8) -> String {
    format!("Hello, {} year old named {}!", age, name)
}

#[launch]
fn rocket() -> _ {
    rocket::build().mount("/hello", routes![hello])
}

Visiting localhost:8000/hello/John/58, for example, will trigger the hello route resulting in the string Hello, 58 year old named John! being sent to the browser. If an string was passed in that can't be parsed as a u8, the route won't get called, resulting in a 404 error.

Documentation

Rocket is extensively documented:

Rocket is a web framework for Rust that comes with the most batteries included out of the three we looked at. It positions itself in the same league as Rails (Ruby) or Flask (Python) and aims to offer functionality for anything a web backend might need, including route handling, request and response parsing, database integrations, validation, and so on. One of its main goals is to minimize the amount of boiler plate code you will have to write, and it does so through metaprogramming, i.e. heavy use of Rust macros.

This makes it easy for the developer to integrate her actual logic with the rocket framework. Let’s have a quick look on how you might define a route handler including parameter guards and validation, a database connection pool, and a JSON object.

We can define a route handler simply by adding the appropriate macro to a function.

#[get("/texts/")]
pub async fn get_text(db: Connection<TextsDatabase>, uuid: Uuid) -> (Status, Value) {
    // ...
    (Status::Ok, json!({"data": data}))
}

If the request parameter cannot be parsed, the appropriate response code is sent instead of calling the function, so we have the usual comfort of type safety. Request bodies are handled very similarly, and of course, parsing integrates seamlessly with serde.

#[derive(Deserialize)]
pub struct Message<'m> {
    pub data: &'m str,
}

#[post("/texts", format = "application/json", data = "")]
pub async fn post_text(db: Connection<TextsDatabase>, msg: Json<Message<'_>>) -> (Status, Value) {
    // ...
}

Adding a database is as simple as annotating the appropriate struct and initializing it at startup. Of course many popular DBMS's are supported.

#[derive(Database)]
#[database("texts")]
pub struct TextsDatabase(mongodb::Client);

Rocket goes heavy on the nerdy space vocabulary - which adds to its charm. Thus, to get your web server off the ground, you simply launch it. Notice again, how annotations are used to get the rocket going.

#[launch]
fn rocket() -> _ {
    rocket::build()
        .attach(TextsDatabase::init())
        .mount("/", routes![get_text, post_text])
}

We have found rocket very enjoyable to work with, especially if you want to focus on the business logic of your server and feel safe trusting the framework to take handling of the boilerplate code off your hands. If you are rather new to Rust and maybe switching over from other frameworks that make heavy use of annotations, you will feel at home with Rocket.

Axum

GitHub logo tokio-rs / axum

Ergonomic and modular web framework built with Tokio, Tower, and Hyper

axum

axum is a web application framework that focuses on ergonomics and modularity.

Build status Crates.io Documentation

More information about this crate can be found in the crate documentation.

High level features

  • Route requests to handlers with a macro free API.
  • Declaratively parse requests using extractors.
  • Simple and predictable error handling model.
  • Generate responses with minimal boilerplate.
  • Take full advantage of the tower and tower-http ecosystem of middleware, services, and utilities.

In particular the last point is what sets axum apart from other frameworks axum doesn't have its own middleware system but instead uses tower::Service. This means axum gets timeouts, tracing, compression, authorization, and more, for free. It also enables you to share middleware with applications written using hyper or tonic.

Usage example

use axum::{
    routing::{get, post},
    http::StatusCode,
    Json, Router,
};
use serde::{Deserialize,

If you are looking for a framework that is more bare-metal Rust and comes with less batteries included, you may want to check out the axum framework developed by tokio-rs, the same team that is responsible for the tokio async library. Opting for axum means you will have to take care of some more of the boilerplate code but you are also not fighting a potentially opinionated framework to get your logic just the way you want it. Compared to rocket, axum does not depend heavily on macros or annotations, and it sets itself apart from its peers by not implementing its own middleware but rather relying on tower for this. Through the tower ecosystem axum can offer timeouts, tracing, compression, authorization, and much more, while also enabling you to share your middleware with applications written with other web libraries like hyper.

Looking at axum’s ergonomics, previous Go developers will feel right at home. The syntax looks very similar to a web server written e.g. with the gorilla/mux module in Go. So how would you go about the implementation of your first route handler including connecting to a database and starting the server? Let’s have a look.

The same two functions from above look something like this:

async fn post_text(
    State(state): State<Arc<state::MongoAppState>>,
    Json(text_payload): Json<payloads::TextPayload>,
) -> Result<
    (StatusCode, Json<payloads::InsertedResponse>),
    (StatusCode, Json<payloads::ErrorResponse>),
> {
    // ...
}

async fn get_text(
    State(state): State<Arc<state::MongoAppState>>,
    Path(text_id): Path<String>,
) -> Result<Json<payloads::TextPayload>, (StatusCode, Json<payloads::ErrorResponse>)> {
    // ...
}

No macros, but a lot more verbose. We see the same when we look at the startup code, we have to do a lot more manually, e.g., pass the DB connection along.

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let client = mongodb::Client::with_uri_str("mongodb://...").await.unwrap();

    let shared_state = std::sync::Arc::new(state::MongoAppState::new(client));

    // build our application with a single route
    let app = Router::new()
        .route("/texts", post(post_text))
        .route("/texts/:text_id", get(get_text).delete(delete_text))
        .with_state(shared_state);

    // run our app with hyper, listening globally on port 3000
    let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
    axum::serve(listener, app).await.unwrap();
    Ok(())
}

Axum is a very powerful web framework that benefits from the expertise of the tokio-rs team in creating great Rust tooling. It is being very actively developed and relies on other state-of-the-art crates for middleware. You will most likely enjoy axum the most if you are already familiar with Rust, enjoy having control over a lot of detail in implementation, or maybe if you are switching from another low-level web language like Go.

Actix (actix.rs)

GitHub logo actix / actix-web

Actix Web is a powerful, pragmatic, and extremely fast web framework for Rust.

Actix Web


Actix Web is a powerful, pragmatic, and extremely fast web framework for Rust

crates.io Documentation MSRV MIT or Apache 2.0 licensed Dependency Status
CI codecov downloads Chat on Discord

Features

  • Supports HTTP/1.x and HTTP/2
  • Streaming and pipelining
  • Powerful request routing with optional macros
  • Full Tokio compatibility
  • Keep-alive and slow requests handling
  • Client/server WebSockets support
  • Transparent content compression/decompression (br, gzip, deflate, zstd)
  • Multipart streams
  • Static assets
  • SSL support using OpenSSL or Rustls
  • Middlewares (Logger, Session, CORS, etc)
  • Integrates with the awc HTTP client
  • Runs on stable Rust 1.72+

Documentation

Example

Dependencies:

[dependencies]
actix-web = "4"

Code:

use actix_web::{get, web, App, HttpServer, Responder};
#[get("/hello/{name}")]
async fn greet(name: web::Path<String>) -> impl Responder {
    format!("Hello {name}!")
}

#[actix_web::main

Last but not least we want to take a look at actix-web. It combines aspects from both previous frameworks, rocket as well as axum, which is resembled in its ergonomics. Annotations are back on the menu and macros are more heavily used, especially in the definition of route handlers. When implementing middleware or database connections, however, you will not find the same level of abstraction as you might with rocket. Actix is extremely fast 3 and it aims to cater to both experienced Rust developers as well as newcomers who are just starting with Rust development. It ships with its own middleware, e.g. for logging, session management, or cross-origin resource sharing, and allows you to expand the framework with your own middleware that can hook into actix. Let’s have a look at how route handlers, database connections, and starting the server are handled in actix.

#[post("/texts")]
async fn post_text(client: web::Data<Client>, payload: web::Json<TextResponse>) -> impl Responder {
    // ...
}

#[get("/texts/{uuid}")]
async fn get_text(client: web::Data<Client>, uuid: web::Path<Uuid>) -> impl Responder {
    // ...
}

Again, we have a lot less boilerplate. Though launching the server falls in between the first two examples with regards to verbosity.

#[actix_web::main] // or #[tokio::main]
async fn main() -> std::io::Result<()> {
    let db_client = Client::with_uri_str("mongodb://....").await.expect("failed to connect");

    HttpServer::new(move || {
        App::new()
            .wrap(Logger::default())
            .app_data(web::Data::new(db_client.clone()))
            .service(post_text)
            .service(get_text)
    })
    .bind(("0.0.0.0", 8080))?
    .run()
    .await
}

The benchmarks speak for themselves, actix is indeed blazingly fast! And that does not come at the cost of developer experience, which actix manages to keep to a very high standard. It ships with more batteries included than axum, but does not abstract everything quite as much as rocket. If you already feel comfortable writing programs in Rust and other low-level web programming languages, you will enjoy developing in actix, and your user will enjoy the performance!

Acknolegements

Selim wrote the initial draft of this post and Selim, Zak, and I each implemented the sample API using one of the above frameworks.

Cover image: "Brown Chains" by Miguel Á. Padriñán

zkck image

Zak CookFollow

What's Your Reaction?

like

dislike

love

funny

angry

sad

wow