Series Outline Link to heading
- Overview
- Framework analysis and selection: Delving into the specifics of framework selection and application.
- Core Components: Database management, modularization, internal RPC, AMQP.
- Web and API: Web Server, GraphQL API.
- Application Development: Web, CLI, desktop, and mobile app development.
- Miscellaneous Tools: Observability, logging, machine learning.
- 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.
Crate | Downloads | Dependents | Github Stars | Github Contributors | Github Used By | Notes |
---|---|---|---|---|---|---|
actix-web | 14M | 823 | 18.9k | 344 | 49.5k | One of the main frameworks. Very well documented, lots of (deep) tutorials, very capable. |
axum | 24M | 685 | 13.1k | 239 | 22.8k | Another 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. |
rocket | 3.7M | 378 | 21k | 277 | 26.8k | Nice api, clean, some documentation, nice marketing material. |
hyper | 110M | 3060 (wow!) | 12.7k | 354 | 243k | Works with tonic, axum. Very popular. Low level, not really full framework… |
poem | 1M | 46 | 2.9k | 77 | 1.4k | Great POV, lots of stuff built in - rather new, not very popular though. Also works with Tower. |
salvo | 1M | 13 | 2.1k | 30 | 733 | Uses Hyper as well, also very batteries included. |
warp | 11M | 396 | 8.6k | 174 | 23.6k | Builds 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.
Crate | Downloads | Dependents | Github Stars | Github Contributors | Github Used By | Notes |
---|---|---|---|---|---|---|
juniper | 1.1M | 62 | 5.4 | 147 | 3.8k | Code first or schema first. Works with actix-web, hyper, rocket, iron, warp |
async-graphql | 3.3M | 92 | 3.1k | 189 | 3.5k | Static 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.