M Logo
📁 Rust Backend with Axum · Part 1 of 42

Rust Web Server with Axum 0.8: Your First Server

October 25, 2026

This is Episode 1 of the Rust Backend series, where we build the backend of a blog step by step. Today: our very first web server with Axum 0.8 and Tokio. Everything from the video is below, ready to copy.

New to Rust? Start with the beginner series. The full backend series playlist is here.

1. Create the project

bash
cargo new blog_api
cd blog_api

2. Add the crates

Axum is the web framework, and Tokio runs async code.

bash
cargo add axum
cargo add tokio --features full

Your Cargo.toml now has axum 0.8 and tokio with all features.

3. A handler and a router

A handler is an async function that answers a request. The router connects a path to a handler. Put this in src/main.rs:

rust
use axum::{routing::get, Router};

async fn main() {
    let app = Router::new().route("/", get(hello));

    let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
    println!("Listening on http://localhost:3000");
    axum::serve(listener, app).await.unwrap();
}

async fn hello() -> &'static str {
    "Hello from Rust!"
}

4. Error E0752: main is not allowed to be async

Run cargo run and you get this error:

text
error[E0752]: `main` function is not allowed to be `async`
 --> src/main.rs:3:1
  |
3 | async fn main() {
  | ^^^^^^^^^^^^^^^ `main` function is not allowed to be `async`

Rust has no built-in async runtime. That is Tokio's job.

5. The fix: #[tokio::main]

Add the attribute above main. It starts the Tokio runtime and runs the async main inside it.

rust
use axum::{routing::get, Router};

#[tokio::main]
async fn main() {
    let app = Router::new().route("/", get(hello));

    let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
    println!("Listening on http://localhost:3000");
    axum::serve(listener, app).await.unwrap();
}

async fn hello() -> &'static str {
    "Hello from Rust!"
}

Run it again. You see Listening on http://localhost:3000. Open http://localhost:3000 in the browser and you get Hello from Rust!. Press Ctrl + C in the terminal to stop the server.

6. A route with a path parameter

Path is an extractor: it takes the id from the URL and turns it into a number.

Many older tutorials write the parameter with a colon, like this:

rust
.route("/posts/:id", get(get_post))

In Axum 0.8 that panics at startup:

text
Path segments must not start with `:`. For capture groups, use `{capture}`.

Path parameters now go in curly braces. This is the final, working code:

rust
use axum::{extract::Path, routing::get, Router};

#[tokio::main]
async fn main() {
    let app = Router::new()
        .route("/", get(hello))
        .route("/posts/{id}", get(get_post));

    let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
    println!("Listening on http://localhost:3000");
    axum::serve(listener, app).await.unwrap();
}

async fn hello() -> &'static str {
    "Hello from Rust!"
}

async fn get_post(Path(id): Path<u32>) -> String {
    format!("Post number {id}")
}

7. Test it with curl

Start the server with cargo run, then use a second terminal. The -i flag also shows the status code.

bash
curl -i localhost:3000/posts/5
text
HTTP/1.1 200 OK
content-type: text/plain; charset=utf-8
content-length: 13

Post number 5

Send letters instead of a number. The extractor cannot turn abc into a u32, so Axum answers 400 Bad Request for us:

bash
curl -i localhost:3000/posts/abc
text
HTTP/1.1 400 Bad Request
content-type: text/plain; charset=utf-8
content-length: 42

Invalid URL: Cannot parse `abc` to a `u32`

A path we never defined gives 404 Not Found:

bash
curl -i localhost:3000/nothing
text
HTTP/1.1 404 Not Found
content-length: 0

Recap

  • A handler is an async function that answers a request.
  • The router connects a path to a handler.
  • #[tokio::main] starts the async runtime.
  • Path extracts a value from the URL.
  • Three kinds of errors: a compile error (E0752), a panic at startup (:id), and HTTP errors (400 and 404).

Tested with Rust 1.99.1, axum 0.8.9 and tokio 1.54.0.

Next episode: JSON in and out with Serde. Subscribe so you do not miss it.

© 2026 All Rights Reserved By Mahmudul Alam