Skip to main content

Github

πŸ¦€ Building Web Applications with Axum in Rust

Axum is a web application framework that focuses on ergonomics and modularity. Built on top of Tokio (for async runtime) and Tower (for middleware and services), it provides a solid foundation for building scalable, fast, and reliable web applications in Rust.

In this post, we’ll walk through setting up an Axum project, creating routes, handling requests, adding middleware, and building APIs with JSON.

πŸ”§ Setting Up Your Project #

First, let’s create a new Rust project and add Axum as a dependency:

cargo new my-web-app
cd my-web-app
cargo add axum tokio --features tokio/full
cargo add tower-http --features full
cargo add serde serde_json --features derive

This will set up a new project with Axum, Tokio, Tower HTTP utilities, and Serde for JSON support.


🌍 Creating Your First Route #

Here’s how to create a simple HTTP server with Axum:

useaxum::{routing::get,Router,};#[tokio::main]asyncfn main(){letapp=Router::new().route("/",get(||async{"Hello, World!"}));letlistener=tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();axum::serve(listener,app).await.unwrap();}

This creates a basic web server that responds with "Hello, World!" on the root path.

Run it with:

cargo run

Visit http://localhost:3000 in your browser to see it in action.


πŸ”— Handling Path and Query Parameters #

Axum makes it easy to capture path and query parameters.

useaxum::{extract::Path,routing::get,Router};#[tokio::main]asyncfn main(){letapp=Router::new().route("/hello/:name",get(greet));letlistener=tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();axum::serve(listener,app).await.unwrap();}asyncfn greet(Path(name): Path<String>)-> String {format!("Hello, {}!",name)}

Now, visiting /hello/Alice will return:

Hello, Alice!

You can also extract query parameters using axum::extract::Query.


πŸ“¦ Returning JSON Responses #

Axum integrates with serde for JSON serialization.

useaxum::{routing::get,Json,Router};useserde::Serialize;#[derive(Serialize)]struct Message{message: String,}#[tokio::main]asyncfn main(){letapp=Router::new().route("/json",get(get_message));letlistener=tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();axum::serve(listener,app).await.unwrap();}asyncfn get_message()-> Json<Message>{Json(Message{message: "Hello from JSON!".to_string(),})}

Visiting /json will return:

{"message": "Hello from JSON!"}

πŸ›‘ Adding Middleware #

Axum builds on Tower, so you can add middleware like logging, timeouts, or request limits.

useaxum::{routing::get,Router,};usetower_http::trace::TraceLayer;#[tokio::main]asyncfn main(){letapp=Router::new().route("/",get(||async{"Hello with middleware!"})).layer(TraceLayer::new_for_http());// log requests
letlistener=tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();axum::serve(listener,app).await.unwrap();}

This logs each request/response, useful for debugging and monitoring.


πŸ”¨ Building a Small REST API #

Here’s a simple in-memory todo API with Axum:

useaxum::{extract::{Path,State},routing::{get,post},Json,Router,};useserde::{Deserialize,Serialize};usestd::sync::{Arc,Mutex};#[derive(Serialize, Deserialize, Clone)]struct Todo{id: usize,text: String,}#[derive(Clone, Default)]struct AppState{todos: Arc<Mutex<Vec<Todo>>>,}#[tokio::main]asyncfn main(){letstate=AppState::default();letapp=Router::new().route("/todos",get(list_todos).post(add_todo)).route("/todos/:id",get(get_todo)).with_state(state);letlistener=tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();axum::serve(listener,app).await.unwrap();}asyncfn list_todos(State(state): State<AppState>)-> Json<Vec<Todo>>{lettodos=state.todos.lock().unwrap().clone();Json(todos)}asyncfn add_todo(State(state): State<AppState>,Json(todo): Json<Todo>)-> Json<Todo>{letmuttodos=state.todos.lock().unwrap();todos.push(todo.clone());Json(todo)}asyncfn get_todo(Path(id): Path<usize>,State(state): State<AppState>)-> Option<Json<Todo>>{lettodos=state.todos.lock().unwrap();todos.iter().find(|t|t.id==id).cloned().map(Json)}

Endpoints:

  • GET /todos β†’ List todos
  • POST /todos β†’ Add a todo (JSON body)
  • GET /todos/:id β†’ Fetch a todo by ID

🏁 Conclusion #

Axum provides:

  • Clean, ergonomic APIs for routing and request handling.
  • Native async support via Tokio.
  • Integration with Tower for middleware.
  • Strong type safety and Rust’s memory guarantees.

It’s an excellent choice for building web servers, REST APIs, and microservices in Rust.

πŸ’‘ Next Step: Extend this project with persistent storage (SQLite, Postgres, or Redis) to turn it into a production-ready API.

Related

AltStyle γ«γ‚ˆγ£γ¦ε€‰ζ›γ•γ‚ŒγŸγƒšγƒΌγ‚Έ (->γ‚ͺγƒͺγ‚ΈγƒŠγƒ«) /