Home > Blog > tech

Rust สำหรับ Backend สอน Axum และ Actix-web สร้าง API ที่เร็วและปลอดภัย 2026

rust backend axum actix guide
Rust Backend Axum Actix Guide 2026
2026-04-11 | tech | 3400 words

Rust กำลังเปลี่ยนโฉมหน้าของ Backend Development ในปี 2026 ด้วยจุดเด่นด้าน Memory Safety ที่ไม่ต้องพึ่ง Garbage Collector, Zero-Cost Abstractions ที่ทำให้ code สะอาดแต่เร็วเท่ากับเขียน low-level, และ Fearless Concurrency ที่ช่วยให้เขียนโปรแกรม concurrent ได้อย่างมั่นใจว่าจะไม่มี data race บริษัทใหญ่อย่าง Cloudflare, Discord, Shopify และ AWS ต่างเลือกใช้ Rust สำหรับ service ที่ต้องการ performance สูงสุด

บทความนี้จะพาคุณเจาะลึกสอง framework ยอดนิยมของ Rust Backend คือ Axum และ Actix-web ตั้งแต่แนวคิดพื้นฐานจนถึงการสร้าง REST API จริง พร้อมเชื่อมต่อฐานข้อมูล การ deploy ด้วย Docker และเปรียบเทียบกับ Go กับ Node.js อย่างตรงไปตรงมา

ทำไมต้อง Rust สำหรับ Backend?

ก่อนจะเข้าเรื่อง framework เรามาทำความเข้าใจก่อนว่าทำไม Rust ถึงกลายเป็นตัวเลือกยอดนิยมสำหรับ backend development ในยุคปัจจุบัน

Memory Safety โดยไม่ต้องมี Garbage Collector

ภาษาอย่าง Go, Java, C# ใช้ Garbage Collector (GC) ในการจัดการ memory ซึ่งทำให้เกิด pause times ที่คาดเดาไม่ได้ ส่วน C/C++ ไม่มี GC แต่กลับมีปัญหา memory leak, dangling pointer และ buffer overflow Rust แก้ปัญหานี้ด้วยระบบ Ownership และ Borrow Checker ที่ตรวจจับ memory bug ตั้งแต่ตอน compile ทำให้ได้ performance เทียบเท่า C++ แต่ปลอดภัยเท่า managed language

Zero-Cost Abstractions

ใน Rust คุณสามารถเขียน code ที่อ่านง่ายและ abstract สูงได้ โดยไม่ต้องจ่ายค่า runtime overhead เพิ่มเลย trait, generics, iterators ทั้งหมดถูก optimize ตอน compile ให้เร็วเท่ากับเขียน manual loop ด้วยมือ นี่คือเหตุผลที่ Rust web framework ทำ benchmark ได้สูงกว่า framework ในภาษาอื่นอย่างมีนัยสำคัญ

Fearless Concurrency

การเขียน concurrent code ในภาษาอื่นมักมีปัญหา data race ที่ debug ยากมาก Rust ใช้ type system ป้องกัน data race ตั้งแต่ compile time ทำให้คุณมั่นใจได้ว่า concurrent code จะทำงานถูกต้องเสมอ ไม่มี race condition ที่จะเกิดขึ้นตอน production ตี 3

Ecosystem สำหรับ Backend ในปี 2026

ระบบนิเวศของ Rust สำหรับ backend โตขึ้นอย่างก้าวกระโดด ปัจจุบันมี crate (library) มากกว่า 150,000 รายการบน crates.io มี async runtime อย่าง Tokio ที่เสถียรมาก มี ORM หลายตัวให้เลือก และ community ที่ใหญ่ขึ้นเรื่อยๆ

Axum Framework — Modern Rust Web

Axum เป็น web framework ที่พัฒนาโดยทีม Tokio เอง ซึ่งเป็นทีมเดียวกับที่สร้าง async runtime ยอดนิยมที่สุดของ Rust ทำให้ Axum ทำงานร่วมกับ Tokio ได้อย่างลงตัวที่สุด

จุดเด่นของ Axum

ติดตั้งและสร้างโปรเจกต์ Axum

# สร้างโปรเจกต์ใหม่
cargo new my-api && cd my-api

# Cargo.toml
[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 กับ Axum

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

#[tokio::main]
async fn main() {
    // สร้าง router
    let app = Router::new()
        .route("/", get(|| async { "สวัสดี Axum!" }))
        .route("/health", get(health_check));

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

async fn health_check() -> &'static str {
    "OK"
}

Extractors — ดึงข้อมูลจาก Request

Extractor คือหัวใจของ Axum ที่ทำให้ดึงข้อมูลจาก HTTP request ได้ง่ายและ type-safe

use axum::{
    extract::{Path, Query, State, Json},
    http::StatusCode,
    response::IntoResponse,
};
use serde::{Deserialize, Serialize};

#[derive(Deserialize)]
struct Pagination {
    page: Option<u32>,
    per_page: Option<u32>,
}

#[derive(Serialize)]
struct User {
    id: u32,
    name: String,
    email: String,
}

// Path parameter
async fn get_user(Path(id): Path<u32>) -> Json<User> {
    Json(User {
        id,
        name: "สมชาย".to_string(),
        email: "somchai@example.com".to_string(),
    })
}

// Query parameter
async fn list_users(Query(params): Query<Pagination>) -> impl IntoResponse {
    let page = params.page.unwrap_or(1);
    let per_page = params.per_page.unwrap_or(10);
    Json(serde_json::json!({
        "page": page,
        "per_page": per_page,
        "users": []
    }))
}

// JSON body
#[derive(Deserialize)]
struct CreateUser {
    name: String,
    email: String,
}

async fn create_user(
    Json(payload): Json<CreateUser>
) -> (StatusCode, Json<User>) {
    let user = User {
        id: 1,
        name: payload.name,
        email: payload.email,
    };
    (StatusCode::CREATED, Json(user))
}

Routing และ State Management

use std::sync::Arc;
use tokio::sync::RwLock;

struct AppState {
    db_pool: sqlx::PgPool,
    config: AppConfig,
}

#[tokio::main]
async fn main() {
    let state = Arc::new(AppState {
        db_pool: create_pool().await,
        config: load_config(),
    });

    let app = Router::new()
        .route("/users", get(list_users).post(create_user))
        .route("/users/:id", get(get_user).put(update_user).delete(delete_user))
        .nest("/api/v1", api_routes())
        .layer(tower_http::cors::CorsLayer::permissive())
        .layer(tower_http::trace::TraceLayer::new_for_http())
        .with_state(state);

    // ...
}

// ใช้ State ใน handler
async fn list_users(
    State(state): State<Arc<AppState>>,
    Query(params): Query<Pagination>,
) -> impl IntoResponse {
    let users = sqlx::query_as!(User, "SELECT * FROM users LIMIT $1 OFFSET $2",
        params.per_page.unwrap_or(10) as i64,
        ((params.page.unwrap_or(1) - 1) * params.per_page.unwrap_or(10)) as i64
    )
    .fetch_all(&state.db_pool)
    .await
    .unwrap();
    Json(users)
}

Actix-web Framework — High Performance Actor Model

Actix-web เป็น web framework ที่สร้างบน Actor Model ของ Actix framework มีชื่อเสียงด้าน performance ที่สูงมากติดอันดับต้นๆ ของ TechEmpower Benchmark มาอย่างยาวนาน

จุดเด่นของ Actix-web

Hello World กับ Actix-web

// Cargo.toml
// [dependencies]
// actix-web = "4"
// serde = { version = "1", features = ["derive"] }
// serde_json = "1"

use actix_web::{web, App, HttpServer, HttpResponse, Responder};
use serde::{Deserialize, Serialize};

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    HttpServer::new(|| {
        App::new()
            .route("/", web::get().to(index))
            .route("/health", web::get().to(health))
    })
    .bind("0.0.0.0:8080")?
    .workers(num_cpus::get())
    .run()
    .await
}

async fn index() -> impl Responder {
    HttpResponse::Ok().body("สวัสดี Actix-web!")
}

async fn health() -> impl Responder {
    HttpResponse::Ok().json(serde_json::json!({ "status": "ok" }))
}

CRUD API กับ Actix-web

#[derive(Serialize, Deserialize, Clone)]
struct Todo {
    id: u32,
    title: String,
    completed: bool,
}

// Shared state
struct AppState {
    todos: std::sync::Mutex<Vec<Todo>>,
}

// GET /todos
async fn get_todos(data: web::Data<AppState>) -> impl Responder {
    let todos = data.todos.lock().unwrap();
    HttpResponse::Ok().json(todos.clone())
}

// POST /todos
async fn create_todo(
    data: web::Data<AppState>,
    body: web::Json<Todo>,
) -> impl Responder {
    let mut todos = data.todos.lock().unwrap();
    todos.push(body.into_inner());
    HttpResponse::Created().json({ "status": "created" })
}

// Configure routes
fn config(cfg: &mut web::ServiceConfig) {
    cfg.service(
        web::resource("/todos")
            .route(web::get().to(get_todos))
            .route(web::post().to(create_todo))
    );
}

Axum vs Actix-web เปรียบเทียบตรงๆ

หัวข้อAxumActix-web
RuntimeTokio (official)Tokio (ผ่าน actix-rt)
ArchitectureTower-based middlewareActor Model
RoutingFunction-based, no macrosMacro + manual config
StateGeneric extractorweb::Data wrapper
Performanceสูงมากสูงมาก (เล็กน้อยกว่า)
Learning Curveง่ายกว่า (idiomatic Rust)ยากกว่า (Actor concept)
EcosystemTower middleware ทั้งหมดActix middleware เฉพาะ
WebSocketผ่าน tokio-tungsteniteBuilt-in ดีมาก
Maturityใหม่กว่า แต่โตเร็วเก่ากว่า เสถียรมาก
แนะนำสำหรับโปรเจกต์ใหม่ทั่วไปHigh-perf, WebSocket heavy
คำแนะนำ 2026: ถ้าเริ่มโปรเจกต์ใหม่ แนะนำ Axum เพราะ integrate กับ Tokio ecosystem ได้ดีกว่า compile error อ่านง่ายกว่า และ community กำลังโตเร็วมาก แต่ถ้าต้องการ WebSocket heavy หรือ Actor pattern จริงๆ Actix-web ยังคงเป็นตัวเลือกที่ดีเยี่ยม

สร้าง REST API ด้วย Axum แบบจริงจัง

โครงสร้างโปรเจกต์

my-api/
├── Cargo.toml
├── .env
├── migrations/
│   └── 001_create_users.sql
├── src/
│   ├── main.rs
│   ├── config.rs
│   ├── db.rs
│   ├── error.rs
│   ├── handlers/
│   │   ├── mod.rs
│   │   └── users.rs
│   ├── models/
│   │   ├── mod.rs
│   │   └── user.rs
│   └── middleware/
│       ├── mod.rs
│       └── auth.rs
└── Dockerfile

Error Handling ที่ดี

use axum::{
    http::StatusCode,
    response::{IntoResponse, Response},
    Json,
};

#[derive(Debug)]
pub enum AppError {
    NotFound(String),
    BadRequest(String),
    Unauthorized,
    InternalError(String),
    DatabaseError(sqlx::Error),
}

impl IntoResponse for AppError {
    fn into_response(self) -> Response {
        let (status, message) = match self {
            AppError::NotFound(msg) => (StatusCode::NOT_FOUND, msg),
            AppError::BadRequest(msg) => (StatusCode::BAD_REQUEST, msg),
            AppError::Unauthorized => (StatusCode::UNAUTHORIZED, "Unauthorized".to_string()),
            AppError::InternalError(msg) => (StatusCode::INTERNAL_SERVER_ERROR, msg),
            AppError::DatabaseError(e) => {
                tracing::error!("Database error: {:?}", e);
                (StatusCode::INTERNAL_SERVER_ERROR, "Internal server error".to_string())
            }
        };
        (status, Json(serde_json::json!({ "error": message }))).into_response()
    }
}

impl From<sqlx::Error> for AppError {
    fn from(e: sqlx::Error) -> Self {
        AppError::DatabaseError(e)
    }
}

Authentication Middleware

use axum::{
    extract::Request,
    http::header,
    middleware::Next,
    response::Response,
};

pub async fn auth_middleware(
    request: Request,
    next: Next,
) -> Result<Response, AppError> {
    let auth_header = request
        .headers()
        .get(header::AUTHORIZATION)
        .and_then(|v| v.to_str().ok())
        .ok_or(AppError::Unauthorized)?;

    if !auth_header.starts_with("Bearer ") {
        return Err(AppError::Unauthorized);
    }

    let token = &auth_header[7..];
    // verify JWT token here
    verify_token(token).map_err(|_| AppError::Unauthorized)?;

    Ok(next.run(request).await)
}

SQLx — Async Database สำหรับ Rust

SQLx เป็น async SQL toolkit ที่ทำงานกับ PostgreSQL, MySQL, SQLite ได้ จุดเด่นคือ compile-time query checking ที่ตรวจสอบ SQL syntax ตั้งแต่ตอน compile

# Cargo.toml
[dependencies]
sqlx = { version = "0.8", features = ["runtime-tokio", "postgres", "macros", "chrono"] }

# .env
DATABASE_URL=postgres://user:password@localhost:5432/mydb
use sqlx::postgres::PgPoolOptions;

// สร้าง Connection Pool
pub async fn create_pool() -> sqlx::PgPool {
    PgPoolOptions::new()
        .max_connections(20)
        .connect(&std::env::var("DATABASE_URL").unwrap())
        .await
        .expect("Failed to create pool")
}

// Compile-time checked query
#[derive(sqlx::FromRow, Serialize)]
struct User {
    id: i32,
    name: String,
    email: String,
    created_at: chrono::NaiveDateTime,
}

async fn get_users(pool: &sqlx::PgPool) -> Result<Vec<User>, sqlx::Error> {
    sqlx::query_as!(User, "SELECT id, name, email, created_at FROM users ORDER BY id")
        .fetch_all(pool)
        .await
}

async fn create_user(pool: &sqlx::PgPool, name: &str, email: &str) -> Result<User, sqlx::Error> {
    sqlx::query_as!(
        User,
        "INSERT INTO users (name, email) VALUES ($1, $2) RETURNING id, name, email, created_at",
        name, email
    )
    .fetch_one(pool)
    .await
}

Migration

# ติดตั้ง sqlx-cli
cargo install sqlx-cli

# สร้าง migration
sqlx migrate add create_users

# เขียน SQL ใน migrations/xxx_create_users.sql
# CREATE TABLE users (
#     id SERIAL PRIMARY KEY,
#     name VARCHAR(255) NOT NULL,
#     email VARCHAR(255) UNIQUE NOT NULL,
#     created_at TIMESTAMP DEFAULT NOW()
# );

# Run migration
sqlx migrate run

SeaORM — Alternative ORM

SeaORM เป็น ORM อีกตัวที่นิยมในกลุ่มคนที่ต้องการ ActiveRecord pattern คล้ายกับ Rails หรือ Django ORM ข้อดีคือเขียนได้เร็วกว่า SQLx ในกรณีที่ schema ซับซ้อน

// Entity definition
use sea_orm::entity::prelude::*;

#[derive(Clone, Debug, PartialEq, DeriveEntityModel)]
#[sea_orm(table_name = "users")]
pub struct Model {
    #[sea_orm(primary_key)]
    pub id: i32,
    pub name: String,
    pub email: String,
    pub created_at: chrono::NaiveDateTime,
}

// Query
let users: Vec<user::Model> = User::find()
    .filter(user::Column::Name.contains("สมชาย"))
    .order_by_asc(user::Column::Id)
    .limit(10)
    .all(&db)
    .await?;

Configuration — การจัดการ Config

// ใช้ dotenv + config crate
use serde::Deserialize;

#[derive(Deserialize)]
pub struct AppConfig {
    pub database_url: String,
    pub server_host: String,
    pub server_port: u16,
    pub jwt_secret: String,
    pub rust_log: String,
}

impl AppConfig {
    pub fn from_env() -> Self {
        dotenvy::dotenv().ok();
        config::Config::builder()
            .add_source(config::Environment::default())
            .build()
            .unwrap()
            .try_deserialize()
            .unwrap()
    }
}

Testing Axum Application

#[cfg(test)]
mod tests {
    use super::*;
    use axum::http::StatusCode;
    use axum_test::TestServer;

    #[tokio::test]
    async fn test_health_check() {
        let app = create_app().await;
        let server = TestServer::new(app).unwrap();

        let response = server.get("/health").await;
        assert_eq!(response.status_code(), StatusCode::OK);
    }

    #[tokio::test]
    async fn test_create_user() {
        let app = create_app().await;
        let server = TestServer::new(app).unwrap();

        let response = server
            .post("/api/users")
            .json(&serde_json::json!({
                "name": "ทดสอบ",
                "email": "test@example.com"
            }))
            .await;

        assert_eq!(response.status_code(), StatusCode::CREATED);
        let user: User = response.json();
        assert_eq!(user.name, "ทดสอบ");
    }
}

Deployment ด้วย Docker Multi-stage

# Dockerfile
FROM rust:1.82 AS builder
WORKDIR /app
COPY Cargo.toml Cargo.lock ./
RUN mkdir src && echo 'fn main() {}' > src/main.rs
RUN cargo build --release && rm -rf src

COPY src/ src/
RUN touch src/main.rs && cargo build --release

FROM debian:bookworm-slim
RUN apt-get update && apt-get install -y ca-certificates && rm -rf /var/lib/apt/lists/*
COPY --from=builder /app/target/release/my-api /usr/local/bin/
EXPOSE 3000
CMD ["my-api"]
# docker-compose.yml
services:
  api:
    build: .
    ports:
      - "3000:3000"
    environment:
      DATABASE_URL: postgres://user:pass@db:5432/mydb
      RUST_LOG: info
    depends_on:
      - db
  db:
    image: postgres:16
    environment:
      POSTGRES_USER: user
      POSTGRES_PASSWORD: pass
      POSTGRES_DB: mydb
    volumes:
      - pgdata:/var/lib/postgresql/data
volumes:
  pgdata:
Docker Tip: ใช้ multi-stage build จะได้ image ขนาดเล็กมาก (ประมาณ 20-50 MB) เทียบกับ Go ที่ได้ประมาณ 10-20 MB แต่เล็กกว่า Node.js image หลายเท่า และทำงานเร็วกว่าอย่างเห็นได้ชัด

Tracing — Observability สำหรับ Production

use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};

fn init_tracing() {
    tracing_subscriber::registry()
        .with(tracing_subscriber::EnvFilter::try_from_default_env()
            .unwrap_or_else(|_| "my_api=debug,tower_http=debug".into()))
        .with(tracing_subscriber::fmt::layer())
        .init();
}

// ใช้ใน handler
#[tracing::instrument(skip(state))]
async fn get_user(
    State(state): State<Arc<AppState>>,
    Path(id): Path<u32>,
) -> Result<Json<User>, AppError> {
    tracing::info!("Fetching user {}", id);
    let user = state.user_repo.find_by_id(id).await?;
    Ok(Json(user))
}

Rust Backend vs Go vs Node.js Benchmark

การเปรียบเทียบ performance ของ backend framework เป็นเรื่องที่หลายคนสนใจ จากข้อมูล TechEmpower Framework Benchmark Round 22 และการทดสอบเพิ่มเติมในปี 2026 ผลลัพธ์เป็นดังนี้

เกณฑ์Rust (Axum)Go (Gin)Node.js (Fastify)
JSON Serialization~850,000 req/s~550,000 req/s~180,000 req/s
Database Query~320,000 req/s~210,000 req/s~75,000 req/s
Memory Usage~8 MB~15 MB~80 MB
Latency P99~0.5 ms~1.2 ms~5 ms
Compile Time~60-120s~5-10sN/A
Lines of Codeมากกว่าปานกลางน้อยที่สุด
Learning Curveสูงมากต่ำต่ำมาก
Hire Developersยากปานกลางง่ายมาก

จะเห็นว่า Rust ชนะด้าน performance ทุกด้านอย่างขาดลอย แต่แลกมาด้วย compile time ที่นานกว่าและ learning curve ที่สูงกว่ามาก การเลือกใช้ต้องดูบริบทของโปรเจกต์ด้วย

เมื่อไหร่ควรใช้ Rust Backend?

เหมาะมาก

อาจไม่คุ้ม

Learning Curve — ความจริงที่ต้องรู้

Rust มี learning curve ที่สูงที่สุดในบรรดาภาษายอดนิยม สิ่งที่คนส่วนใหญ่ติดขัด ได้แก่

แต่ข้อดีคือ ถ้าผ่านจุดนี้ไปได้ code ที่ compile ผ่านมักจะทำงานถูกต้องตั้งแต่แรก bug ที่เกี่ยวกับ memory และ concurrency แทบจะหายไปเลย ทีมที่ใช้ Rust มักบอกว่า production incident ลดลงอย่างมีนัยสำคัญ เครื่องมือช่วยพัฒนาอย่าง IDE สมัยใหม่ กับ rust-analyzer ก็ช่วยลด friction ได้มาก

เทคนิคเพิ่มเติมสำหรับ Production

Graceful Shutdown

use tokio::signal;

async fn shutdown_signal() {
    let ctrl_c = async {
        signal::ctrl_c().await.expect("Failed to install Ctrl+C handler");
    };

    #[cfg(unix)]
    let terminate = async {
        signal::unix::signal(signal::unix::SignalKind::terminate())
            .expect("Failed to install signal handler")
            .recv()
            .await;
    };

    tokio::select! {
        _ = ctrl_c => {},
        _ = terminate => {},
    }
    tracing::info!("Shutdown signal received");
}

// ใช้กับ axum
axum::serve(listener, app)
    .with_graceful_shutdown(shutdown_signal())
    .await
    .unwrap();

Rate Limiting

use tower::limit::RateLimitLayer;
use std::time::Duration;

let app = Router::new()
    .route("/api/data", get(get_data))
    .layer(RateLimitLayer::new(100, Duration::from_secs(1)));

CORS Configuration

use tower_http::cors::{CorsLayer, Any};

let cors = CorsLayer::new()
    .allow_origin(Any)
    .allow_methods([Method::GET, Method::POST, Method::PUT, Method::DELETE])
    .allow_headers(Any);

let app = Router::new()
    .route("/api/users", get(list_users))
    .layer(cors);

แหล่งเรียนรู้ Rust Backend

การเรียนรู้ระบบซอฟต์แวร์สมัยใหม่ต้องเข้าใจทั้งฝั่ง backend, ระบบการเงิน, และ infrastructure ที่รองรับ การเลือก stack ที่เหมาะสมคือกุญแจสำคัญของความสำเร็จ

สรุป

Rust สำหรับ Backend ไม่ใช่เรื่องของ hype แต่เป็นทางเลือกที่สมเหตุสมผลสำหรับระบบที่ต้องการ performance สูงสุดและ reliability ที่ยอดเยี่ยม Axum เหมาะกับโปรเจกต์ใหม่ที่ต้องการ integration กับ Tokio ecosystem ส่วน Actix-web เหมาะกับงานที่ต้องการ battle-tested framework ที่พิสูจน์ตัวเองมาแล้ว

ถ้าคุณเป็นนักพัฒนาที่ต้องการ challenge ตัวเอง ต้องการเข้าใจ system programming ลึกขึ้น หรือกำลังทำระบบที่ทุก millisecond มีค่า Rust คือคำตอบที่คุ้มค่าแก่การลงทุนเวลาเรียนรู้ เริ่มต้นด้วย Axum สร้าง API เล็กๆ แล้วค่อยๆ ขยาย คุณจะเข้าใจว่าทำไมคนที่เขียน Rust แล้วมักไม่อยากกลับไปเขียนภาษาอื่น

สำหรับนักพัฒนาไทยที่สนใจ community Rust กำลังเติบโตอย่างรวดเร็ว มี meetup และกลุ่มออนไลน์ที่คอยช่วยเหลือ การติดตามความรู้ใหม่ๆ ผ่านแหล่งข้อมูลเทคโนโลยีและชุมชนนักพัฒนาจะช่วยให้คุณไม่ตกเทรนด์


Back to Blog | iCafe Forex | SiamLanCard | Siam2R