If you're coming from Python and have used FastAPI, learning Rust’s Axum can feel confusing at first. But the good news is: the core ideas are almost the same.
Let’s break down this simple Axum server step by step and compare it with FastAPI so it clicks instantly.
🧩 The Full Code
use axum::{ routing::get, Router, };
use std::net::SocketAddr;
#[tokio::main]
async fn main() {
// 1. Create a route
let app = Router::new()
.route("/", get(root));
// 2. Define address
let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
println!("Server running at http://{}", addr);
// 3. Run server
let listener = tokio::net::TcpListener::bind(addr).await.unwrap();
axum::serve(listener, app).await.unwrap();
}
// 4. Handler function
async fn root() -> &'static str {
"Hello, Axum! Hello guys how are you!!!"
}
🧠 Big Picture
This program does 4 simple things:
- Create a web app
- Define a route (
/) - Start a server on
127.0.0.1:3000 - Return a response when someone visits
🔹 Step 1: Importing Things
use axum::{ routing::get, Router };
use std::net::SocketAddr;
What this means:
- Bring
Router→ to create the app - Bring
get→ to define GET routes - Bring
SocketAddr→ to define IP + port
🐍 FastAPI equivalent:
from fastapi import FastAPI
🔹 Step 2: Async Main Function
#[tokio::main]
async fn main() {
What this means:
Rust normally doesn’t support async in main, so we use Tokio (an async runtime).
👉 This is like saying:
“Run this program using async engine”
🐍 FastAPI equivalent:
import asyncio
async def main():
...
asyncio.run(main())
🔹 Step 3: Create the App
let app = Router::new()
.route("/", get(root));
What this means:
- Create a new app (
Router::new()) - Add a route
/ - When someone sends a GET request, call
root
🐍 FastAPI equivalent:
app = FastAPI()
@app.get("/")
async def root():
return "Hello"
👉 Same idea, different syntax.
🔹 Step 4: Define Address
let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
What this means:
Your server will run on:
http://127.0.0.1:3000
🐍 FastAPI equivalent:
uvicorn.run(app, host="127.0.0.1", port=3000)
🔹 Step 5: Bind the Server
let listener = tokio::net::TcpListener::bind(addr).await.unwrap();
What this means:
- Open port
3000 - Start listening for incoming requests
Think of it like:
“Open the door and wait for visitors”
🐍 Python equivalent:
server.bind(("127.0.0.1", 3000))
server.listen()
🔹 Step 6: Start the Server
axum::serve(listener, app).await.unwrap();
What this means:
- Take incoming requests from
listener - Pass them to your
app(Router) - Run forever
🐍 FastAPI equivalent:
uvicorn.run(app)
🔹 Step 7: Handler Function
async fn root() -> &'static str {
"Hello, Axum! Hello guys how are you!!!"
}
What this means:
When someone visits /, this function runs and returns a response.
🐍 FastAPI equivalent:
@app.get("/")
async def root():
return "Hello, Axum!"
🔁 Full Request Flow
Here’s what happens when you open your browser:
Browser → http://127.0.0.1:3000/
↓
TcpListener (listening)
↓
Axum Server
↓
Router matches "/"
↓
root() function runs
↓
Response sent back
Now Full fastapi code:
from fastapi import FastAPI
import uvicorn
# 1. Create app
app = FastAPI()
# 2. Create route
@app.get("/")
async def root():
return "Hello, Axum! Hello guys how are you!!!"
# 3. Run server
if __name__ == "__main__":
uvicorn.run(app, host="127.0.0.1", port=3000)
🔹 Key Difference (important)
👉 In Axum (Rust):
You manually:
bind address
create listener
start server
👉 In FastAPI (Python):
uvicorn.run() does everything for you
Simple Mental Model
| Concept | Axum | FastAPI |
|---|---|---|
| App | Router::new() |
FastAPI() |
| Route | .route("/", get(...)) |
@app.get("/") |
| Handler | async fn root() |
async def root() |
| Server start | axum::serve(...) |
uvicorn.run() |
| Address | SocketAddr |
host + port |
Final Summary
This whole Axum program means:
“Create a web server that listens on port 3000, and when someone visits
/, return a simple message.”
If you're already comfortable with FastAPI, you're much closer to mastering Axum than you think. The concepts are the same — Rust just makes things more explicit and safe.
Top comments (2)
Great breakdown! The FastAPI parallel is a smart way to lower the learning curve for Python developers.
One thing worth highlighting for newcomers: while the surface API looks similar, the underlying async model is quite different. FastAPI runs on uvicorn with Python asyncio, while Axum runs on Tokio -- a multi-threaded work-stealing scheduler. In practice Axum handlers are truly parallel across CPU cores by default, whereas FastAPI is concurrent but not parallel unless you spawn separate processes.
This distinction becomes critical when moving from web services to embedded or systems contexts. I ran into this building moteDB, an embedded Rust database for AI robots. The same Tokio runtime that powers Axum gives you fine-grained control over thread pinning and I/O dispatch -- which matters on a Raspberry Pi 5 with limited cores shared between inference and storage.
For anyone going deeper: tokio::main is convenient but in production you often want tokio::runtime::Builder::new_multi_thread() with explicit worker_threads so you do not accidentally starve critical tasks.
What is the target use case for your series? Web APIs only or heading toward embedded/edge too?
Thanks for you valuable comment ^_^. For now i'm focusing on web api