본문으로 건너뛰기
Kreath Archive
TechProjectsBooksAbout
TechProjectsBooksAbout

내비게이션

  • Tech
  • Projects
  • Books
  • About
  • Tags

카테고리

  • AI / ML
  • 웹 개발
  • 프로그래밍
  • 개발 도구

연결

  • GitHub
  • Email
  • RSS
© 2026 Kreath Archive. All rights reserved.Built with Next.js + MDX
홈TechProjectsBooksAbout
//
  1. 홈
  2. 테크
  3. 5장: Axum 웹 프레임워크 기초
2026년 2월 19일·프로그래밍·

5장: Axum 웹 프레임워크 기초

Axum의 Tower 기반 아키텍처, 라우팅, 핸들러, 추출자, 응답 타입, 미들웨어까지 Hello World에서 CRUD API까지 실습합니다.

10분934자10개 섹션
rust
공유
rust-backend5 / 11
1234567891011
이전4장: async/await와 Tokio 런타임다음6장: Axum 고급 패턴

학습 목표

  • Axum의 Tower 기반 아키텍처를 이해합니다
  • 라우팅, 핸들러, 추출자의 관계를 파악합니다
  • Path, Query, Json, State 추출자를 활용합니다
  • Hello World에서 CRUD API까지 단계적으로 구축합니다

Axum이란

Axum은 Tokio 팀이 개발하는 Rust 웹 프레임워크입니다. 매크로에 의존하지 않는 깔끔한 API, Tower 미들웨어 생태계와의 완벽한 호환, 타입 안전한 추출자 패턴이 특징입니다.

프로젝트 생성

프로젝트 초기화
bash
cargo new rust-api && cd rust-api
Cargo.toml
toml
[package]
name = "rust-api"
version = "0.1.0"
edition = "2024"
 
[dependencies]
axum = "0.8"
tokio = { version = "1", features = ["full"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
tower-http = { version = "0.6", features = ["cors", "trace"] }
tracing = "0.1"
tracing-subscriber = "0.3"

Hello World

src/main.rs — 최소 서버
rust
use axum::{Router, routing::get};
 
#[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!("서버 시작: http://localhost:3000");
    axum::serve(listener, app).await.unwrap();
}
 
// 핸들러: 문자열을 반환하면 자동으로 200 OK + text/plain
async fn hello() -> &'static str {
    "Hello, Rust Backend!"
}

이 몇 줄의 코드에 Axum의 핵심 개념이 모두 담겨 있습니다. Router가 경로를 정의하고, **핸들러(Handler)**가 요청을 처리하며, 반환값이 자동으로 HTTP 응답으로 변환됩니다.

핸들러 (Handler)

Axum의 핸들러는 비동기 함수입니다. 매개변수로 **추출자(Extractor)**를 받고, IntoResponse를 구현하는 값을 반환합니다.

다양한 핸들러 시그니처
rust
use axum::{Json, http::StatusCode};
use serde_json::{json, Value};
 
// 매개변수 없는 핸들러
async fn health_check() -> StatusCode {
    StatusCode::OK
}
 
// JSON 응답 반환
async fn api_info() -> Json<Value> {
    Json(json!({
        "name": "Rust API",
        "version": "0.1.0"
    }))
}
 
// 상태 코드 + JSON 조합
async fn not_found() -> (StatusCode, Json<Value>) {
    (
        StatusCode::NOT_FOUND,
        Json(json!({ "error": "리소스를 찾을 수 없습니다" })),
    )
}

핸들러의 규칙은 간단합니다.

  1. async fn이어야 합니다
  2. 매개변수는 0개 이상의 추출자입니다 (최대 16개)
  3. 반환값은 IntoResponse 트레이트를 구현해야 합니다

추출자 (Extractor)

추출자는 HTTP 요청에서 데이터를 타입 안전하게 추출하는 메커니즘입니다. Axum의 가장 강력한 특징 중 하나입니다.

Path 추출자

URL 경로 매개변수를 추출합니다.

Path 추출자
rust
use axum::extract::Path;
 
// /users/42 → id = 42
async fn get_user(Path(id): Path<u64>) -> String {
    format!("사용자 ID: {}", id)
}
 
// /users/42/posts/7 → 여러 매개변수
async fn get_user_post(
    Path((user_id, post_id)): Path<(u64, u64)>,
) -> String {
    format!("사용자 {} 의 게시물 {}", user_id, post_id)
}

Query 추출자

URL 쿼리 파라미터를 구조체로 역직렬화합니다.

Query 추출자
rust
use axum::extract::Query;
use serde::Deserialize;
 
#[derive(Deserialize)]
struct Pagination {
    page: Option<u32>,
    per_page: Option<u32>,
}
 
// /users?page=2&per_page=20
async fn list_users(Query(params): Query<Pagination>) -> String {
    let page = params.page.unwrap_or(1);
    let per_page = params.per_page.unwrap_or(10);
    format!("페이지 {}, {} 개씩", page, per_page)
}

Json 추출자

요청 본문을 JSON으로 역직렬화합니다.

Json 추출자
rust
use axum::Json;
use serde::{Deserialize, Serialize};
 
#[derive(Deserialize)]
struct CreateUser {
    name: String,
    email: String,
}
 
#[derive(Serialize)]
struct UserResponse {
    id: u64,
    name: String,
    email: String,
}
 
async fn create_user(Json(payload): Json<CreateUser>) -> Json<UserResponse> {
    // payload의 소유권이 이 핸들러로 이동
    let response = UserResponse {
        id: 1,
        name: payload.name,
        email: payload.email,
    };
    Json(response)
}

State 추출자

애플리케이션 상태를 핸들러에 주입합니다.

State 추출자
rust
use axum::extract::State;
use std::sync::Arc;
 
struct AppState {
    db_url: String,
}
 
async fn get_status(State(state): State<Arc<AppState>>) -> String {
    format!("DB 연결: {}", state.db_url)
}
 
// main에서 상태 등록
let state = Arc::new(AppState {
    db_url: "postgresql://localhost/mydb".to_string(),
});
 
let app = Router::new()
    .route("/status", get(get_status))
    .with_state(state);
Info

State에는 Arc로 감싼 값을 사용하는 것이 관례입니다. 각 핸들러가 상태의 참조를 공유해야 하기 때문입니다. Arc는 참조 카운팅으로 소유권을 공유하는 스마트 포인터입니다.

라우팅

라우팅 구성
rust
use axum::{Router, routing::{get, post, put, delete}};
 
fn create_router(state: Arc<AppState>) -> Router {
    Router::new()
        // 기본 라우트
        .route("/", get(root))
        .route("/health", get(health_check))
 
        // RESTful 리소스
        .route("/users", get(list_users).post(create_user))
        .route("/users/{id}", get(get_user).put(update_user).delete(delete_user))
 
        // 중첩 라우터
        .nest("/api/v1", api_v1_routes())
 
        // 상태 주입
        .with_state(state)
}
 
fn api_v1_routes() -> Router<Arc<AppState>> {
    Router::new()
        .route("/posts", get(list_posts).post(create_post))
        .route("/posts/{id}", get(get_post))
}

CRUD API 실습

지금까지 배운 내용을 종합하여 인메모리 CRUD API를 구축합니다.

src/main.rs — 완전한 CRUD API
rust
use axum::{
    Json, Router,
    extract::{Path, State},
    http::StatusCode,
    routing::{get, post, put, delete},
};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::sync::Arc;
use tokio::sync::RwLock;
 
// --- 모델 ---
 
#[derive(Debug, Clone, Serialize)]
struct User {
    id: u64,
    name: String,
    email: String,
}
 
#[derive(Debug, Deserialize)]
struct CreateUserRequest {
    name: String,
    email: String,
}
 
#[derive(Debug, Deserialize)]
struct UpdateUserRequest {
    name: Option<String>,
    email: Option<String>,
}
 
// --- 상태 ---
 
type UserStore = Arc<RwLock<HashMap<u64, User>>>;
 
struct AppState {
    users: UserStore,
    next_id: Arc<tokio::sync::Mutex<u64>>,
}
 
// --- 핸들러 ---
 
async fn list_users(
    State(state): State<Arc<AppState>>,
) -> Json<Vec<User>> {
    let users = state.users.read().await;
    let user_list: Vec<User> = users.values().cloned().collect();
    Json(user_list)
}
 
async fn get_user(
    State(state): State<Arc<AppState>>,
    Path(id): Path<u64>,
) -> Result<Json<User>, StatusCode> {
    let users = state.users.read().await;
    users
        .get(&id)
        .cloned()
        .map(Json)
        .ok_or(StatusCode::NOT_FOUND)
}
 
async fn create_user(
    State(state): State<Arc<AppState>>,
    Json(payload): Json<CreateUserRequest>,
) -> (StatusCode, Json<User>) {
    let mut next_id = state.next_id.lock().await;
    let id = *next_id;
    *next_id += 1;
 
    let user = User {
        id,
        name: payload.name,
        email: payload.email,
    };
 
    state.users.write().await.insert(id, user.clone());
 
    (StatusCode::CREATED, Json(user))
}
 
async fn update_user(
    State(state): State<Arc<AppState>>,
    Path(id): Path<u64>,
    Json(payload): Json<UpdateUserRequest>,
) -> Result<Json<User>, StatusCode> {
    let mut users = state.users.write().await;
    let user = users.get_mut(&id).ok_or(StatusCode::NOT_FOUND)?;
 
    if let Some(name) = payload.name {
        user.name = name;
    }
    if let Some(email) = payload.email {
        user.email = email;
    }
 
    Ok(Json(user.clone()))
}
 
async fn delete_user(
    State(state): State<Arc<AppState>>,
    Path(id): Path<u64>,
) -> StatusCode {
    let mut users = state.users.write().await;
    if users.remove(&id).is_some() {
        StatusCode::NO_CONTENT
    } else {
        StatusCode::NOT_FOUND
    }
}
 
// --- 메인 ---
 
#[tokio::main]
async fn main() {
    tracing_subscriber::init();
 
    let state = Arc::new(AppState {
        users: Arc::new(RwLock::new(HashMap::new())),
        next_id: Arc::new(tokio::sync::Mutex::new(1)),
    });
 
    let app = Router::new()
        .route("/users", get(list_users).post(create_user))
        .route("/users/{id}", get(get_user).put(update_user).delete(delete_user))
        .with_state(state);
 
    let listener = tokio::net::TcpListener::bind("0.0.0.0:3000")
        .await
        .unwrap();
 
    println!("CRUD API 서버 시작: http://localhost:3000");
    axum::serve(listener, app).await.unwrap();
}

테스트

cURL로 API 테스트
bash
# 사용자 생성
curl -X POST http://localhost:3000/users \
  -H "Content-Type: application/json" \
  -d '{"name": "Alice", "email": "alice@example.com"}'
 
# 사용자 목록 조회
curl http://localhost:3000/users
 
# 특정 사용자 조회
curl http://localhost:3000/users/1
 
# 사용자 수정
curl -X PUT http://localhost:3000/users/1 \
  -H "Content-Type: application/json" \
  -d '{"name": "Alice Kim"}'
 
# 사용자 삭제
curl -X DELETE http://localhost:3000/users/1

미들웨어 기초

Axum은 Tower 미들웨어 생태계를 그대로 활용합니다.

기본 미들웨어 설정
rust
use tower_http::cors::{CorsLayer, Any};
use tower_http::trace::TraceLayer;
 
let app = Router::new()
    .route("/users", get(list_users).post(create_user))
    .with_state(state)
    // 로깅 미들웨어
    .layer(TraceLayer::new_for_http())
    // CORS 미들웨어
    .layer(
        CorsLayer::new()
            .allow_origin(Any)
            .allow_methods(Any)
            .allow_headers(Any),
    );
Warning

미들웨어의 적용 순서에 주의하세요. layer()는 가장 마지막에 추가한 것이 가장 바깥쪽(먼저 실행)에 위치합니다. 위 예제에서 요청은 CORS → Trace → Handler 순서로 처리됩니다.

정리

이 장에서는 Axum 웹 프레임워크의 기초를 다루었습니다.

  • Axum은 Tower 기반, 매크로 없는 타입 안전한 웹 프레임워크입니다
  • 핸들러는 추출자를 매개변수로 받고 IntoResponse를 반환하는 비동기 함수입니다
  • Path, Query, Json, State 추출자로 요청 데이터를 타입 안전하게 추출합니다
  • 인메모리 CRUD API를 통해 라우팅, 상태 관리, 에러 처리의 기본 패턴을 확인했습니다

다음 장에서는 Axum 고급 패턴을 다룹니다. 중첩 라우터, 인증/인가 미들웨어, 요청 검증, 웹소켓, 테스트 등 프로덕션 수준의 패턴을 익힙니다.

이 글이 도움이 되셨나요?

관련 주제 더 보기

#rust

관련 글

프로그래밍

6장: Axum 고급 패턴

중첩 라우터, 인증/인가 미들웨어, 요청 검증, 웹소켓, SSE, API 테스트까지 프로덕션 수준의 Axum 패턴을 다룹니다.

2026년 2월 21일·10분
프로그래밍

4장: async/await와 Tokio 런타임

Rust의 비동기 프로그래밍 기초부터 Future 트레이트, Tokio 런타임, spawn/select/join, 채널 기반 동시성 패턴까지 체계적으로 다룹니다.

2026년 2월 17일·15분
프로그래밍

7장: SQLx 데이터베이스 연동

SQLx의 컴파일 타임 쿼리 검증, 연결 풀, CRUD 구현, 마이그레이션, 트랜잭션까지 Rust 백엔드의 데이터베이스 연동 패턴을 다룹니다.

2026년 2월 23일·11분
이전 글4장: async/await와 Tokio 런타임
다음 글6장: Axum 고급 패턴

댓글

목차

약 10분 남음
  • 학습 목표
  • Axum이란
  • 프로젝트 생성
  • Hello World
  • 핸들러 (Handler)
  • 추출자 (Extractor)
    • Path 추출자
    • Query 추출자
    • Json 추출자
    • State 추출자
  • 라우팅
  • CRUD API 실습
    • 테스트
  • 미들웨어 기초
  • 정리