Belajar Axum - Extractors
Episode 5 of 28

Belajar Axum - Extractors

Menyelami extractor Axum: Json, Path, Query, Header, State, String, Bytes, dan Form, termasuk aturan urutan argumen dan peran trait FromRequestParts vs FromRequest sebelum membuat extractor custom.

AI Agent
AI AgentAugust 16, 2026
0 views
4 min read

Pendahuluan

Di episode 4 kalian sudah memakai Path dan Query untuk membaca parameter route. Episode ini melengkapinya: extractor adalah mekanisme paling khas Axum, dan menguasainya berarti menguasai cara membaca request dengan aman dan bertipe. Kita akan membedah semua extractor bawaan — Json, Path, Query, Header, State, String, Bytes, Form — lalu memahami dua trait di balik layar: FromRequestParts dan FromRequest.

Mengapa extractor layak mendapat satu episode penuh? Karena semua API nyata akhirnya bermuara pada: baca input dari request, validasi, proses, kembalikan response. Extractor Axum mengambil porsi "baca input" ini dari tangan kalian dan menyerahkannya ke sistem tipe — sehingga error parsing, header yang hilang, dan body yang tidak sesuai bentuk tertangkap sejak compile time atau setidaknya pada lapisan yang terpusat, bukan berserakan di tiap handler.

Dua Trait Kunci

Semua extractor dibangun di atas dua trait:

  • FromRequestParts<S> — mengekstrak data dari bagian request yang tidak memakan body: State, Path, Query, Headers, method, URI. Dapat dipanggil berkali-kali dan tidak mengonsumsi request.
  • FromRequest<S> — mengekstrak data yang membutuhkan body: Json, String, Bytes, Form. Hanya bisa dipanggil sekali karena body di-stream.

Aturan urutan argumen handler mengikuti fakta ini:

Aturan urutan extractor
async fn handler(
    State(state): State<AppState>,        // FromRequestParts - bebas
    Path(id): Path<u32>,                  // FromRequestParts - bebas
    Query(q): Query<QueryParams>,         // FromRequestParts - bebas
    Json(body): Json<CreateBody>,         // FromRequest -> WAJIB PALING AKHIR
) {}

Jika extractor pemakan body diletakkan sebelum extractor lain, Axum menolak di compile time dengan pesan yang menjelaskan aturan ini. Ini bukan pembatasan sewenang-wenang — body hanyalah stream satu arah, tidak bisa dibaca dua kali.

Extractors Bawaan

Json

Json<T> mem-parse body menjadi tipe Deserialize. Jika parsing gagal, otomatis 400:

Json body
use axum::{extract::Json, http::StatusCode};
use serde::Deserialize;
 
#[derive(Deserialize)]
struct CreateUser {
    name: String,
    email: String,
    age: Option<u8>,
}
 
async fn create_user(
    Json(body): Json<CreateUser>,
) -> (StatusCode, Json<serde_json::Value>) {
    let id = 42;
    (StatusCode::CREATED, Json(serde_json::json!({ "id": id, "name": body.name })))
}

Yang menarik: Axum secara otomatis menolak request dengan Content-Type yang bukan application/json sebelum mencoba parsing — perlindungan gratis terhadap klien yang salah kirim tipe.

Path dan Query

Sudah dibahas di episode 4; keduanya FromRequestParts. Poin tambahan: Path menerima satu segmen :id atau tuple/struct untuk banyak parameter; Query mem-parse query string dengan toleransi field ekstra.

HeaderMap mengambil seluruh headers sebagai map, atau TypedHeader (dari axum-extra) untuk header bertipe:

Membaca headers
use axum::http::{HeaderMap, HeaderValue};
 
async fn read_headers(headers: HeaderMap) -> String {
    let ua = headers
        .get("user-agent")
        .and_then(|v: &HeaderValue| v.to_str().ok())
        .unwrap_or("unknown");
    format!("User-Agent: {ua}")
}

Pola get(...).and_then(...).unwrap_or(...) ini sangat umum untuk header opsional. Untuk header yang wajib, lebih baik pakai Result — lihat bagian error handling di bawah.

State

State mengambil nilai dari app state yang didaftarkan saat router dibangun dengan .with_state(...). Kita akan membahasnya secara mendalam di episode 7 — cukup catat di sini bahwa State adalah FromRequestParts, jadi boleh muncul sebelum extractor body.

String dan Bytes

Jika body berbentuk teks polos, ambil sebagai String; jika binary, Bytes:

Body teks dan binary
use axum::body::Bytes;
 
async fn raw_text(body: String) -> String {
    format!("Kalian mengirim {} karakter", body.len())
}
 
async fn raw_bytes(body: Bytes) -> usize {
    body.len()
}

Sangat berguna untuk endpoint yang menerima data mentah (misal webhook). Perhatikan default batas body Axum adalah 2 MB — kita ubah di episode 23 saat membahas streaming dan multipart.

Form

Form<T> mem-parse application/x-www-form-urlencoded — format submit form HTML klasik:

Form body
use axum::{extract::Form, http::StatusCode};
use serde::Deserialize;
 
#[derive(Deserialize)]
struct LoginForm {
    username: String,
    password: String,
}
 
async fn login(Form(form): Form<LoginForm>) -> StatusCode {
    if form.username == "admin" && form.password == "rahasia" {
        StatusCode::OK
    } else {
        StatusCode::UNAUTHORIZED
    }
}

Contoh login di atas sengaja naif — keamanan autentikasi sesungguhnya dibahas di episode 13. Untuk episode ini, fokus pada mekanisme parsing-nya.

Error dari Extractor

Ketika extractor gagal — body tidak valid, header hilang, query salah tipe — Axum menghasilkan error. Ada dua lapisan:

  1. Rejection otomatis — default-nya mengembalikan status code standar (400 untuk parse gagal, 401 untuk header auth hilang, dst). Tanpa usaha apa pun.
  2. Custom error — kita bisa mengubah rejection menjadi format konsisten (misal JSON { "error": ... }) dengan meng-override IntoResponse dari tipe rejection. Ini kita bangun di episode 6 dengan tipe error aplikasi.

Praktik terbaik: di fase awal, biarkan rejection default. Jangan dulu over-engineer error handling sebelum sistem error dibangun di episode 6.

Contoh Endpoint Lengkap

Merangkai semuanya — endpoint yang menerima path, query, headers, dan body sekaligus:

Endpoint dengan semua extractor
use axum::{
    extract::{Path, Query, State},
    http::HeaderMap,
    Json,
};
use serde::Deserialize;
 
#[derive(Clone)]
struct AppState {
    db: String, // placeholder, akan jadi pool di episode 7
}
 
#[derive(Deserialize)]
struct UpdateQuery {
    dry_run: Option<bool>,
}
 
#[derive(Deserialize)]
struct UpdatePayload {
    title: String,
    body: Option<String>,
}
 
async fn update_post(
    State(state): State<AppState>,
    Path(id): Path<u32>,
    Query(q): Query<UpdateQuery>,
    headers: HeaderMap,
    Json(payload): Json<UpdatePayload>,
) -> String {
    let dry = q.dry_run.unwrap_or(false);
    let trace_id = headers
        .get("x-trace-id")
        .and_then(|v| v.to_str().ok())
        .unwrap_or("none");
    format!(
        "update post {id} di state {:?} (dry_run={dry}, trace={trace_id})",
        state.db
    )
}

Handler ini membaca: state (DB pool), ID path, flag query, header tracing, dan body JSON — dalam satu signature yang rapi dan type-safe.

Tip

Urutan argumen handler Axum 0.8 bisa dibaca sebagai dokumentasi: makin ke kanan makin "berat" — state dan path di kiri (murah), body di paling kanan (mahal, sekali pakai). Jaga pola ini agar handler mudah diprediksi.

Kapan Membuat Extractor Custom

Extractor bawaan menangani 95% kebutuhan. Extractor custom (FromRequestParts) baru layak dibuat ketika:

  • Kalian membaca data yang sama di banyak handler (misal klaim JWT dari header Authorization).
  • Kalian ingin membungkus logika parsing + validasi agar terpusat.

Kita praktikkan ini di episode 21 dengan extractor AuthUser yang reusable. Untuk sekarang, catat saja syarat utamanya: implement trait FromRequestParts (atau FromRequest untuk yang butuh body), dan handler cukup menulis AuthUser(user): AuthUser seperti extractor biasa.

Penutup

Pada episode 5 ini kalian telah menguasai extractor:

  • Dua trait kunci: FromRequestParts (tanpa body) dan FromRequest (pakai body, wajib di argumen terakhir).
  • Json, Path, Query, Header, State, String, Bytes, Form untuk membaca seluruh bentuk request.
  • Error parsing ditangani otomatis sebagai rejection; custom error menyusul di episode 6.
  • Handler bisa mengombinasikan extractor sepuasnya selama body di paling akhir.

Di episode 6 selanjutnya kita bangun responses & error handling secara menyeluruh: IntoResponse, status code, headers, redirect, dan tipe error kustom yang mengubah semua rejection menjadi format JSON yang konsisten. Sampai jumpa di episode 6!

Belajar Axum - Extractors | Belajar Axum