Search
moon
sun
πŸ¦€

260524_1718_axum hello_world

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

A minimal HTTP web server built with Axum 0.8 and Tokio. The server exposes a single route that responds with plain text: Hello, World!.

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”