Axum Hello World
cargo build
•
async@awaits-MacBook-Air 260412_1712_axum_helloworld % cargo build
cargo run
•
async@awaits-MacBook-Air 260412_1712_axum_helloworld % cargo run
Explain
Overview
This project demonstrates the smallest useful Axum application:
•
One GET / route
•
Plain-text response body
•
Async request handling on the Tokio runtime
•
No shared application state, middleware, or database
Axum sits on top of Hyper and Tower. You define routes with a Router, attach handlers, bind a TCP listener, and serve requests with axum::serve.
Requirements
Tool | Version used | Notes |
Rust | 1.84+ | Edition 2021 |
Cargo | 1.84+ | Bundled with Rust |
OS | Any | macOS, Linux, and Windows |
No external services (database, Redis, etc.) are required.
Project Structure
260412_1712_axum_helloworld/
├── Cargo.toml # Crate metadata and dependencies
├── README.md # This document
└── src/
└── main.rs # Application entry point and route handlers
Plain Text
복사
Dependencies
Defined in Cargo.toml:
Crate | Version | Role |
axum | 0.8 | Web framework: routing, handlers, HTTP serving |
tokio | 1.x | Async runtime (full features: I/O, macros, multi-thread scheduler) |
Axum pulls in Hyper, Tower, and HTTP types transitively. You do not need to add them explicitly for this example.
How It Works
1. Route definition
let app = Router::new().route("/", get(hello_world));
Rust
복사
•
Router::new() creates an empty router.
•
.route("/", get(...)) registers GET / and maps it to the hello_world handler.
•
Handlers are async functions. Axum runs them on the Tokio runtime.
2. Handler
async fn hello_world() -> &'static str {
"Hello, World!"
}
Rust
복사
Returning &'static str tells Axum to send a 200 OK response with Content-Type: text/plain; charset=utf-8 and the string as the body. No manual response building is required for this simple case.
3. TCP listener
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await?;
Rust
복사
The server listens on port 3000 on all network interfaces (0.0.0.0). Use 127.0.0.1:3000 if you only want local access.
4. HTTP server
axum::serve(listener, app).await?;
Rust
복사
axum::serve accepts connections on the listener and dispatches HTTP requests to the router. It supports HTTP/1.1 and HTTP/2 (when enabled via features).
Request flow
Client (browser/curl)
│
▼
TcpListener (port 3000)
│
▼
axum::serve
│
▼
Router ──► GET / ──► hello_world() ──► "Hello, World!"
Plain Text
복사
Build and Run
From the project root:
cd /Users/async/glory/Schedule/code/200518_0157_rs/axum/260412_1712_axum_helloworld
cargo run
Bash
복사
First run downloads and compiles dependencies; later runs are faster.
Expected console output:
Server listening on <http://localhost:3000>
Plain Text
복사
Press Ctrl+C to stop the server.
Release build (optional)
For optimized binaries:
cargo run --release
Bash
복사
Verify the Server
With the server running, in another terminal:
curl <http://localhost:3000>
Bash
복사
Expected response:
Hello, World!
Plain Text
복사
You can also open http://localhost:3000 in a browser; the same text should appear.
HTTP details
Item | Value |
Method | GET |
Path | / |
Status | 200 OK |
Body | Hello, World! |
Content-Type | text/plain; charset=utf-8 (inferred by Axum) |
Other paths (e.g. /foo) return 404 Not Found because no route is registered for them.
Configuration
Setting | Location | Default value |
Listen address | src/main.rs | 0.0.0.0:3000 |
Axum version | Cargo.toml | 0.8 |
Tokio features | Cargo.toml | full |
To change the port, edit the LISTEN_ADDR constant in src/main.rs and update the println! message if desired.
Common Commands
Command | Description |
cargo run | Build and run in debug mode |
cargo build | Compile without running |
cargo build --release | Optimized build |
cargo check | Type-check without full link |
cargo clean | Remove target/ build artifacts |
Troubleshooting
Address already in use
If port 3000 is taken:
Error: failed to bind TCP listener
Plain Text
복사
Fix: Stop the other process using port 3000, or change LISTEN_ADDR to another port (e.g. 0.0.0.0:8080).
On macOS/Linux, find the process:
lsof -i :3000
Bash
복사
Cannot connect from another machine
Binding to 127.0.0.1 only accepts local connections. This project uses 0.0.0.0, which accepts remote connections if your firewall allows it.
Compile errors after upgrading Axum
Axum 0.8 changed path parameter syntax from :name to {name}. This project has no path parameters, so upgrades are straightforward. See the Axum 0.8 announcement for breaking changes when extending the app.
Next Steps
Ideas for extending this server:
1.
More routes — Add GET /health for health checks.
2.
JSON responses — Return Json(...) with serde for structured APIs.
3.
Path parameters — Use /hello/{name} (Axum 0.8 syntax).
4.
Middleware — Logging, CORS, or compression via Tower layers.
5.
Shared state — Router::with_state for database pools or config.
6.
Tests — axum::Router can be tested with tower::ServiceExt without binding a real port.
References
License
This example is provided as-is for learning purposes. Add a license file if you plan to distribute or publish the crate.
axum/260412_1712_axum_helloworld/src/main.rs
// `use` brings names from external crates into the current scope.
// The curly braces `{ ... }` list multiple items from the same path (a "use group").
// - `Router`: Axum's central type for mapping HTTP paths to handler functions.
// - `routing::get`: A helper that registers a handler for HTTP GET requests only.
use axum::{routing::get, Router};
// `const` defines a compile-time constant.
// Syntax: `NAME: Type = value;`
// - `&str` is a borrowed string slice (pointer + length), not an owned `String`.
// - `"0.0.0.0:3000"` binds on all network interfaces; clients can reach port 3000.
const LISTEN_ADDR: &str = "0.0.0.0:3000";
// Handler function for the root path `/`.
//
// `async fn` declares an asynchronous function. The caller must `.await` it.
// Axum accepts async handlers because it runs on the Tokio async runtime.
//
// Return type `-> &'static str`:
// - `&'static str` is a string slice that lives for the entire program lifetime.
// - Axum converts this into an HTTP 200 response with `Content-Type: text/plain`.
// - No explicit `Response` type is needed for simple text responses (via `IntoResponse`).
async fn hello_world() -> &'static str {
// The last expression in a block (without a semicolon) is the return value.
"Hello, World!"
}
// `#[tokio::main]` is a procedural macro attribute.
// It rewrites `async fn main()` into a normal `fn main()` that:
// 1. Creates a Tokio multi-thread runtime.
// 2. Runs the async body on that runtime until it completes.
// Without this attribute, Rust's standard `main` cannot be `async` directly.
#[tokio::main]
async fn main() {
// `Router::new()` constructs an empty router (no routes yet).
// `.route(path, method_router)` chains route registration:
// - `"/"` matches the root URL path.
// - `get(hello_world)` wires the GET method to our handler function.
// Axum infers the handler's argument/return types at compile time.
let app = Router::new().route("/", get(hello_world));
// `tokio::net::TcpListener::bind` is async and returns `Result<TcpListener, Error>`.
// `.await` suspends until the OS completes the bind operation.
// `.expect("...")` unwraps `Ok` or panics on `Err` (acceptable for a tiny demo app).
let listener = tokio::net::TcpListener::bind(LISTEN_ADDR)
.await
.expect("failed to bind TCP listener");
println!("Server listening on http://localhost:3000");
// `axum::serve(listener, app)` accepts connections and dispatches HTTP requests
// to matching routes in `app`. It runs until the process receives a shutdown signal
// (e.g. Ctrl+C) or encounters a fatal error.
axum::serve(listener, app).await.expect("server error");
}
Rust
복사
Cargo.toml
[package]
name = "axum_helloworld"
version = "0.1.0"
edition = "2021"
description = "Minimal Axum Hello World web server"
[dependencies]
axum = "0.8"
tokio = { version = "1", features = ["full"] }
Rust
복사
안녕하세요
•
관련 기술 문의와 R&D 공동 연구 사업 관련 문의는 “glory@keti.re.kr”로 연락 부탁드립니다.
Hello 
•
For technical and business inquiries, please contact me at “glory@keti.re.kr”
