Series Outline Link to heading

  1. Overview
  2. Framework analysis and selection: Delving into the specifics of framework selection and application.
    1. Core Components: Database management, modularization, internal RPC, AMQP.
    2. Web and API: Web Server, GraphQL API.
    3. Application Development: Web, CLI, desktop, and mobile app development.
    4. Miscellaneous Tools: Observability, logging, machine learning.
  3. Boilerplate project: A comprehensive guide including setup instructions for selected frameworks, architectural diagrams, and development environment configurations.

Web Server Link to heading

In the realms of Java and .NET, a web application server serves as a critical component, providing an environment where web applications can run and manage web-based requests. Java has options like Apache Tomcat and WildFly, while .NET relies on servers like IIS or Kestrel. These servers handle a myriad of tasks including request processing, application lifecycle management, and security. In contrast, when it comes to Rust, a language still maturing in web development, selecting an appropriate web application server is not as straightforward. There isn’t a default go-to like in Java or .NET ecosystems. Instead, we need to explore and choose from emerging options such as Actix-Web, Rocket, or Warp, each offering unique features and performance characteristics tailored for Rust’s concurrency model and safety guarantees. This choice is pivotal to ensure efficient request handling, scalability, and robustness in Rust-based web applications.

CrateDownloadsDependentsGithub StarsGithub ContributorsGithub Used ByNotes
actix-web14M82318.9k34449.5kOne of the main frameworks. Very well documented, lots of (deep) tutorials, very capable.
axum24M68513.1k23922.8kAnother one of the big ones. Works well with hyper & tower & tonic (timeouts, tracing, compression, authorization, and more, for free). Slightly cleaner code IMHO. Still plenty of tutorials/docs.
rocket3.7M37821k27726.8kNice api, clean, some documentation, nice marketing material.
hyper110M3060 (wow!)12.7k354243kWorks with tonic, axum. Very popular. Low level, not really full framework…
poem1M462.9k771.4kGreat POV, lots of stuff built in - rather new, not very popular though. Also works with Tower.
salvo1M132.1k30733Uses Hyper as well, also very batteries included.
warp11M3968.6k17423.6kBuilds on hyper, pretty batteries included

Graphql API Link to heading

Opting for a GraphQL API in enterprise applications is a smart move for its efficient, precise data retrieval, crucial for large-scale systems. However, in Rust, the choices for GraphQL implementations are limited but growing. Libraries like Juniper and async-graphql, though fewer in number, harness Rust’s performance and safety features, making them viable options for enterprises eager to capitalize on GraphQL’s strengths within the Rust ecosystem.

CrateDownloadsDependentsGithub StarsGithub ContributorsGithub Used ByNotes
juniper1.1M625.41473.8kCode first or schema first. Works with actix-web, hyper, rocket, iron, warp
async-graphql3.3M923.1k1893.5kStatic or dynamic schema, nice async api, Works with axum, rocket, tide, warp, actix-web, and poem.

Hello, is it you I’m looking for? Link to heading

“Hello World” examples from each web framework. This will give us a quick taste of what it’s like to work with each framework, highlighting their unique syntax and features. These examples are intended to provide a basic yet insightful comparison, helping us to better understand and choose the right framework for our needs.

actix-web Link to heading

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] // or #[tokio::main]
async fn main() -> std::io::Result<()> {
    HttpServer::new(|| {
        App::new().service(greet)
    })
    .bind(("127.0.0.1", 8080))?
    .run()
    .await
}

Axum Link to heading

use axum::{response::Html, routing::get, Router};

#[tokio::main]
async fn main() {
    // build our application with a route
    let app = Router::new().route("/", get(handler));

    // run it
    let listener = tokio::net::TcpListener::bind("127.0.0.1:3000")
        .await
        .unwrap();
    println!("listening on {}", listener.local_addr().unwrap());
    axum::serve(listener, app).await.unwrap();
}

async fn handler() -> Html<&'static str> {
    Html("<h1>Hello, World!</h1>")
}

Rocket Link to heading

#[macro_use] extern crate rocket;

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

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

Hyper Link to heading

async fn hello(_: Request<hyper::body::Incoming>) -> Result<Response<Full<Bytes>>, Infallible> {
    Ok(Response::new(Full::new(Bytes::from("Hello, World!"))))
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
    let addr = SocketAddr::from(([127, 0, 0, 1], 3000));

    // We create a TcpListener and bind it to 127.0.0.1:3000
    let listener = TcpListener::bind(addr).await?;

    // We start a loop to continuously accept incoming connections
    loop {
        let (stream, _) = listener.accept().await?;

        // Use an adapter to access something implementing `tokio::io` traits as if they implement
        // `hyper::rt` IO traits.
        let io = TokioIo::new(stream);

        // Spawn a tokio task to serve multiple connections concurrently
        tokio::task::spawn(async move {
            // Finally, we bind the incoming connection to our `hello` service
            if let Err(err) = http1::Builder::new()
                // `service_fn` converts our function in a `Service`
                .serve_connection(io, service_fn(hello))
                .await
            {
                println!("Error serving connection: {:?}", err);
            }
        });
    }
}

Poem Link to heading

use poem::{get, handler, listener::TcpListener, web::Path, Route, Server};

#[handler]
fn hello(Path(name): Path<String>) -> String {
    format!("hello: {}", name)
}

#[tokio::main]
async fn main() -> Result<(), std::io::Error> {
    let app = Route::new().at("/hello/:name", get(hello));
    Server::new(TcpListener::bind("127.0.0.1:3000"))
      .run(app)
      .await
}

Salvo Link to heading

use salvo::prelude::*;

#[handler]
async fn hello(res: &mut Response) {
    res.render(Text::Plain("Hello World"));
}

#[tokio::main]
async fn main() {
    let mut router = Router::new().get(hello);
    let listener = TcpListener::new("0.0.0.0:443")
        .acme()
        .cache_path("temp/letsencrypt")
        .add_domain("test.salvo.rs")
        .http01_challege(&mut router).quinn("0.0.0.0:443");
    let acceptor = listener.join(TcpListener::new("0.0.0.0:80")).bind().await;
    Server::new(acceptor).serve(router).await;
}

Warp Link to heading

use warp::Filter;

#[tokio::main]
async fn main() {
    // GET /hello/warp => 200 OK with body "Hello, warp!"
    let hello = warp::path!("hello" / String)
        .map(|name| format!("Hello, {}!", name));

    warp::serve(hello)
        .run(([127, 0, 0, 1], 3030))
        .await;
}

Summary Link to heading

I strongly recommend adopting a combination of Tower and Axum. This pairing offers a compelling balance of robustness, flexibility, and compatibility, making it an optimal choice for our web framework needs. Tower provides a solid foundation with its middleware-oriented architecture, while Axum complements it by offering a versatile and reliable platform for web application development. Together, they form a synergistic duo that aligns well with our project’s objectives and technical requirements. The big benefit of this combo is the tower services/layers we come up with can be used outside our web server as well, whereas, if we use actix, that is not so.

As a secondary option though, if such modularity is not required, Actix-web stands out as a commendable alternative. Its proven performance and extensive feature set make it a viable backup choice, should we need to deviate from the Tower/Axum combination. Actix-web’s strong community support and ease of use also position it as a suitable option for our web development needs, offering a reliable fallback if circumstances necessitate a change in our primary technology stack.

Graphql Link to heading

In the realm of GraphQL libraries for Rust, async-graphql emerges as the superior choice. Despite not being the pioneering library in this space, it has overtaken Juniper in several key aspects. Through a comprehensive proof of concept, I have found that async-graphql offers a significantly more user-friendly experience, boasting a more intuitive Rust API and full compliance with GraphQL specifications. Additionally, it includes support for Apollo extensions, enhancing its functionality. Furthermore, indicators such as community engagement and metric analytics suggest a stronger and more vibrant ecosystem surrounding async-graphql, reinforcing its position as the preferred option for our GraphQL implementations.