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 axum::{routing::get, Router};
/*
use ν€μλ: μΈλΆ creates (axum)μ μ μλ κΈ°λ₯μ νμ¬ μμ€μ½λ μ€μ½νλ‘ κ°μ Έμ κΈ΄ κ²½λ‘ μλ΅ κ°λ₯νκ² ν¨
μ€κ΄νΈ { ... } (Use Group): λμΌν λ£¨νΈ κ²½λ‘(axum)μμ μ¬λ¬ μμ΄ν
(routing::get, Router)μ ν μ€λ‘ λ¬Άμ΄ ν¨μ¨μ μΌλ‘ μμ
(Import)νλ λ¬Έλ²
Router: Axum νλ μμν¬μ ν΅μ¬ νμ
μΌλ‘, νΉμ HTTP κ²½λ‘(URL)μ μ΄λ₯Ό μ²λ¦¬ν νΈλ€λ¬ ν¨μλ₯Ό λ§€νν΄μ£Όλ μΉ μλ²μ μ΄μ ν μν
routing::get: HTTP λ©μλ μ€ μ‘°ν λͺ©μ μ GET μμ²λ§μ νν°λ§νμ¬ νΉμ νΈλ€λ¬μ μ°κ²°ν΄μ£Όλ λΌμ°ν
λ§€μΉ ν¬νΌ ν¨μ
*/
const LISTEN_ADDR: &str = "0.0.0.0:3001";
/*
const ν€μλ: μ»΄νμΌ μμ μ κ°μ΄ κ²°μ λλ λΆλ³μ 'μμ'λ₯Ό μ μ (νλ‘κ·Έλ¨ μ€ν μ€ μ λ λ³κ²½ λΆκ°)
LISTEN_ADDR: μμμ μ΄λ¦μΌλ‘, κ΄λ‘μ λ°λΌ μ 체 λλ¬Έμμ μΈλλ°(_) μ€λ€μ΄ν¬ μΌμ΄μ€λ‘ λͺ
λͺ
: &str: μμμ νμ
μ λͺ
μν κ²μΌλ‘, λ©λͺ¨λ¦¬μ κ³ μ λ λ¬Έμμ΄ λ°μ΄ν°μ μ£Όμμ κΈΈμ΄λ₯Ό κ°λ¦¬ν€λ κ³ μ ν¬κΈ° λ¬Έμμ΄ μ¬λΌμ΄μ€ νμ
(Rustμμ const μ μ μ νμ
λͺ
μλ νμ)
= "0.0.0.0:3001";: μΉ μλ²κ° μμ²μ κΈ°λ€λ¦΄(Listen) μ£Όμμ ν¬νΈλ₯Ό μ§μ
0.0.0.0: νΉμ IPκ° μλ, μλ² μ»΄ν¨ν°μ ν λΉλ λͺ¨λ λ€νΈμν¬ μΈν°νμ΄μ€(λμΉ΄λ)λ‘λΆν°μ μ μμ μ λΆ νμ©νκ² λ€λ μλ―Έ
3001: μΈλΆ ν΄λΌμ΄μΈνΈκ° μ΄ μΉ μλ²μ μ μνκΈ° μν΄ ν΅κ³Όν΄μΌ νλ λ¬Έ λ²νΈ(ν¬νΈ λ²νΈ)
*/
async fn hello_world() -> &'static str {
"Hello, World!"
}
/*
hello_world: λ£¨νΈ κ²½λ‘ `/`μ λν HTTP μμ²μ μ²λ¦¬νλ νΈλ€λ¬(μμ² μ²λ¦¬ ν¨μ)
async fn: λΉλκΈ° ν¨μλ₯Ό μ μΈνλ ν€μλ β ν¨μ λ΄λΆμμ I/O λκΈ° λ±μ΄ λ°μν΄λ μ€λ λλ₯Ό λΈλ‘νΉνμ§ μκ³ λ€λ₯Έ μμ
μ μ보ν μ μμ
νΈμΆ μΈ‘μμλ μ΄ ν¨μμ κ²°κ³Όλ₯Ό μ¬μ©ν λ λ°λμ `.await`λ‘ μλ£λ₯Ό κΈ°λ€λ €μΌ ν¨
Axumμ Tokio λΉλκΈ° λ°νμ μμμ λμνλ―λ‘, νΈλ€λ¬λ₯Ό async fnμΌλ‘ μμ±νλ κ²μ΄ μΌλ°μ
-> &'static str: ν¨μμ λ°ν νμ
β νλ‘κ·Έλ¨ μ 체 μλͺ
(static) λμ μ ν¨ν λ¬Έμμ΄ μ¬λΌμ΄μ€μ μ°Έμ‘°
Axumμ μ΄ λ°νκ°μ HTTP 200 OK μλ΅μΌλ‘ μλ λ³ννλ©°, Content-Typeμ text/plainμΌλ‘ μ€μ λ¨
λ¨μ ν
μ€νΈ μλ΅μ κ²½μ° IntoResponse νΈλ μ΄νΈ λλΆμ Response νμ
μ μ§μ λ§λ€ νμ μμ
"Hello, World!": ν¨μ λ³Έλ¬Έμ λ§μ§λ§ ννμ β μΈλ―Έμ½λ‘ (;)μ΄ μμΌλ―λ‘ μ΄ κ°μ΄ ν¨μμ λ°νκ°μ΄ λ¨
*/
#[tokio::main]
async fn main() {
let app = Router::new().route("/", get(hello_world));
let listener = tokio::net::TcpListener::bind(LISTEN_ADDR)
.await
.expect("failed to bind TCP listener");
println!("Server listening on http://localhost:3001");
axum::serve(listener, app).await.expect("server error");
}
/*
#[tokio::main]: μ μ°¨μ λ§€ν¬λ‘(Procedural Macro) μμ± β async fn main()μ μΌλ° fn main()μΌλ‘ μλ λ³νν΄ μ€
λ³νλ mainμ (1) Tokio λ©ν°μ€λ λ λ°νμμ μμ±νκ³ (2) async λ³Έλ¬Έμ κ·Έ λ°νμμμ μ€νν λ€ μλ£λ λκΉμ§ λκΈ°ν¨
μ΄ μμ±μ΄ μμΌλ©΄ Rust νμ€ mainμ asyncλ₯Ό μ§μ μ¬μ©ν μ μμ
Router::new(): λΉμ΄ μλ λΌμ°ν°λ₯Ό μμ± β μμ§ λ±λ‘λ κ²½λ‘(route)κ° μλ μν
.route("/", get(hello_world)): λ©μλ 체μ΄λμΌλ‘ κ²½λ‘λ₯Ό λ±λ‘
"/": URL λ£¨νΈ κ²½λ‘μ λ§€μΉ (μ: http://localhost:3001/)
get(hello_world): HTTP GET λ©μλ μμ²μ΄ λ€μ΄μ€λ©΄ hello_world νΈλ€λ¬λ₯Ό νΈμΆνλλ‘ μ°κ²°
Axumμ νΈλ€λ¬μ μΈμ νμ
κ³Ό λ°ν νμ
μ μ»΄νμΌ μμ μ μΆλ‘ νμ¬ νμ
μμ μ±μ 보μ₯ν¨
let app: μμμ ꡬμ±ν λΌμ°ν°λ₯Ό app λ³μμ λ΄μ μ΄ν serveμ μ λ¬
tokio::net::TcpListener::bind(LISTEN_ADDR): μ§μ ν μ£Όμ(LISTEN_ADDR)μ TCP μμΌμ λ°μΈλ©νμ¬ μ°κ²° λκΈ° μνλ‘ λ§λ¦
λΉλκΈ° ν¨μμ΄λ―λ‘ .awaitλ‘ OSμ λ°μΈλ© μμ
μ΄ λλ λκΉμ§ νμ¬ νμ€ν¬λ₯Ό μΌμ μ μ§(suspend)ν¨
λ°ν νμ
μ Result<TcpListener, Error> β μ±κ³΅ μ Ok(listener), μ€ν¨ μ Err(μ: ν¬νΈ μ΄λ―Έ μ¬μ© μ€)
.expect("failed to bind TCP listener"): Errμ΄λ©΄ μλ¬ λ©μμ§μ ν¨κ» ν¨λ(panic) β νμ΅μ© λ°λͺ¨μμλ λ¨μ μ²λ¦¬λ‘ μΆ©λΆ
println!(...): μλ²κ° μ μμ μΌλ‘ λ°μΈλ©λμμμ μ½μμ μΆλ ₯ (κ°λ°μ νμΈμ©)
axum::serve(listener, app): listenerλ‘ λ€μ΄μ€λ TCP μ°κ²°μ λ°μ HTTP μμ²μΌλ‘ νμ±νκ³ , app λΌμ°ν°μ λ§λ νΈλ€λ¬λ‘ λΆλ°°(dispatch)
.await: μλ²κ° μ’
λ£ μ νΈ(μ: Ctrl+C)λ₯Ό λ°κ±°λ μΉλͺ
μ μ€λ₯κ° λ°μν λκΉμ§ μ΄ μ§μ μμ λκΈ°
.expect("server error"): serve μ€ μΉλͺ
μ μ€λ₯ λ°μ μ ν¨λ β λ°λͺ¨ μ±μμμ λ¨μ μλ¬ μ²λ¦¬
*/
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β


