Proyek: Membuat Web API dengan Axum di Rust
Di proyek ini kamu akan membuat web server sungguhan: sebuah API to-do kecil. Program lain (sebuah website, aplikasi HP, atau sekadar curl di terminal) mengirim request HTTP ke server ini, dan server menjawab dengan JSON. Inilah jenis program yang menjalankan “back end” di sebagian besar aplikasi yang kamu pakai.
Kita akan memakai axum, web framework populer yang ramah pemula, buatan tim Tokio. Axum dibangun di atas async Rust dan serde, jadi proyek ini menyatukan banyak hal yang sudah kamu pelajari: struct, Result, smart pointer, dan state yang dipakai bersama antar-thread. Kalau kamu sudah mengerjakan Daftar Tugas CLI, idenya akan terasa familier. Bedanya, kali ini daftar to-do-nya tinggal di balik sebuah alamat web.
Yang akan kamu buat
Section titled “Yang akan kamu buat”Setelah selesai, server-mu akan menjawab request berikut:
| Method dan path | Fungsinya | Status kalau berhasil |
|---|---|---|
GET /todos |
Menampilkan semua to-do | 200 OK |
GET /todos/{id} |
Mengambil satu to-do | 200 OK, atau 404 Not Found |
POST /todos |
Membuat to-do dari JSON | 201 Created |
DELETE /todos/{id} |
Menghapus satu to-do | 204 No Content, atau 404 Not Found |
Satu to-do dalam bentuk JSON terlihat seperti ini:
{"id":1,"title":"Beli susu","done":false}Satu catatan penting: web server menunggu request selamanya dan butuh port jaringan, jadi tidak bisa dijalankan di Rust Playground. Kode server di halaman ini dimaksudkan untuk dijalankan di komputermu sendiri dengan cargo run. Setiap langkah di bawah sudah dikompilasi dan dijalankan, dan response yang ditampilkan adalah hasil asli. Kalau ada bagian program yang bisa jalan di Playground, kamu akan menemukan tombol “Try it Yourself”.
Menyiapkan proyek
Section titled “Menyiapkan proyek”Buat proyek baru lalu tambahkan empat crate:
cargo new todo-apicd todo-apicargo add serde --features derivecargo add serde_jsonSekarang Cargo.toml kamu berisi kira-kira seperti ini (nomor versinya mungkin lebih baru):
[dependencies]axum = "0.8"serde = { version = "1", features = ["derive"] }serde_json = "1"tokio = { version = "1", features = ["full"] }Fungsi masing-masing:
- axum mengurus HTTP: membaca request dan mengirim response.
- tokio adalah async runtime yang menjalankan semuanya. Lihat Async/Await.
- serde dan serde_json mengubah struct-mu menjadi JSON dan sebaliknya. Lihat JSON dengan Serde.
Kamu juga butuh curl untuk mengirim request. Program ini sudah ada di macOS, Linux, dan Windows 10 ke atas. (Di Windows, ketik curl.exe di PowerShell, karena di sana curl saja artinya lain.)
Langkah 1: Halo, dunia!
Section titled “Langkah 1: Halo, dunia!”Ganti isi src/main.rs dengan ini:
use axum::{routing::get, Router};
#[tokio::main]async fn main() { let app = Router::new().route("/", get(hello));
let listener = tokio::net::TcpListener::bind("127.0.0.1:3000").await.unwrap(); println!("Server berjalan di http://127.0.0.1:3000"); axum::serve(listener, app).await.unwrap();}
async fn hello() -> &'static str { "Halo, dunia!"}Ada tiga bagian yang bekerja sama di sini:
- Handler adalah
async fnyang menjawab sebuah request.hellomengembalikan teks, dan axum mengirimnya balik sebagai response. - Router menghubungkan path ke handler.
.route("/", get(hello))artinya “kalau ada yang mengirim requestGETke/, panggilhello”. - Listener membuka port 3000 di komputermu sendiri (
127.0.0.1), danaxum::servemulai menjawab request. Server terus berjalan sampai kamu menghentikannya.
Jalankan dengan cargo run. Program mencetak satu baris lalu menunggu:
Server berjalan di http://127.0.0.1:3000Biarkan tetap berjalan, buka terminal kedua, lalu kirim request:
curl http://127.0.0.1:3000/Halo, dunia!Server tidak menambahkan baris baru setelah teksnya, jadi prompt terminal-mu mungkin muncul tepat setelah Halo, dunia!. Itu normal. Di halaman ini, setiap response ditampilkan di barisnya sendiri. Kamu juga bisa membuka http://127.0.0.1:3000/ di browser. Tekan Ctrl + C di terminal pertama untuk menghentikan server. Kamu perlu menghentikan dan menjalankan ulang server setiap kali mengubah kode.
Langkah 2: Mengembalikan JSON dari struct
Section titled “Langkah 2: Mengembalikan JSON dari struct”API biasanya menjawab dengan JSON. Di axum, kamu cukup membungkus nilai apa pun yang mengimplementasikan Serialize dari serde dengan Json(...), lalu axum mengubahnya menjadi JSON dan memasang header content-type yang benar:
use axum::{routing::get, Json, Router};use serde::Serialize;
#[derive(Serialize)]struct Todo { id: u32, title: String, done: bool,}
#[tokio::main]async fn main() { let app = Router::new() .route("/", get(hello)) .route("/example", get(example_todo));
let listener = tokio::net::TcpListener::bind("127.0.0.1:3000").await.unwrap(); println!("Server berjalan di http://127.0.0.1:3000"); axum::serve(listener, app).await.unwrap();}
async fn hello() -> &'static str { "Halo, dunia!"}
async fn example_todo() -> Json<Todo> { let todo = Todo { id: 1, title: String::from("Belajar axum"), done: false, }; Json(todo)}Flag -i menyuruh curl ikut menampilkan baris status dan header:
curl -i http://127.0.0.1:3000/exampleHTTP/1.1 200 OKcontent-type: application/jsoncontent-length: 44
{"id":1,"title":"Belajar axum","done":false}(curl juga mencetak header date, yang tidak kita tampilkan di halaman ini.) Kunci-kunci JSON-nya persis sama dengan nama field di struct. Kamu bisa mengecek bentuk JSON itu di Playground tanpa server, karena ini serde biasa. Di sini juga ada struct NewTodo yang akan kita pakai di langkah 4 untuk data yang masuk:
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, Serialize)]struct Todo { id: u32, title: String, done: bool,}
#[derive(Debug, Deserialize)]struct NewTodo { title: String,}
fn main() { let todo = Todo { id: 1, title: String::from("Belajar axum"), done: false }; println!("{}", serde_json::to_string(&todo).unwrap());
let list = vec![todo.clone(), Todo { id: 2, title: String::from("Beli susu"), done: true }]; println!("{}", serde_json::to_string(&list).unwrap());
let input: NewTodo = serde_json::from_str(r#"{"title":"Ajak anjing jalan-jalan"}"#).unwrap(); println!("{:?}", input);
let bad = serde_json::from_str::<NewTodo>(r#"{"name":"Ajak anjing jalan-jalan"}"#); println!("{}", bad.unwrap_err());}Output:
{"id":1,"title":"Belajar axum","done":false}[{"id":1,"title":"Belajar axum","done":false},{"id":2,"title":"Beli susu","done":true}]NewTodo { title: "Ajak anjing jalan-jalan" }missing field `title` at line 1 column 34Baris terakhir adalah pesan error dari serde (dalam bahasa Inggris): field title tidak ditemukan di baris 1, kolom 34. Sebuah Vec<Todo> menjadi array JSON. Persis itulah yang nanti dikembalikan oleh GET /todos.
Langkah 3: Path parameter
Section titled “Langkah 3: Path parameter”Di path seperti /todos/2, angka 2 berubah dari satu request ke request lain. Di axum 0.8, kamu menandai bagian route itu dengan kurung kurawal, {id}, lalu membacanya di handler dengan extractor Path:
use axum::{extract::Path, routing::get, Router};
#[tokio::main]async fn main() { let app = Router::new() .route("/hello/{name}", get(hello_name)) .route("/add/{a}/{b}", get(add));
let listener = tokio::net::TcpListener::bind("127.0.0.1:3000").await.unwrap(); println!("Server berjalan di http://127.0.0.1:3000"); axum::serve(listener, app).await.unwrap();}
async fn hello_name(Path(name): Path<String>) -> String { format!("Halo, {name}!")}
async fn add(Path((a, b)): Path<(i32, i32)>) -> String { format!("{a} + {b} = {}", a + b)}Extractor adalah parameter handler yang mengambil satu bagian dari request. Path(name): Path<String> adalah sebuah pattern (pola; lihat Pattern Matching): ia membuka bungkus Path dan memberimu String di dalamnya. Dengan dua parameter, kamu mendapat sebuah tuple.
curl http://127.0.0.1:3000/hello/Anacurl http://127.0.0.1:3000/add/2/3Halo, Ana!2 + 3 = 5Ada kejutan menyenangkan di sini. Kamu sudah bilang a dan b bertipe i32, jadi axum memeriksanya untukmu. Coba kirim sesuatu yang bukan angka:
curl -i http://127.0.0.1:3000/add/2/xHTTP/1.1 400 Bad Requestcontent-type: text/plain; charset=utf-8content-length: 68
Invalid URL: Cannot parse value at index 1 with value `x` to a `i32`Pesan dari axum ini artinya nilai x di posisi 1 tidak bisa diubah menjadi i32. Handler-mu bahkan tidak dijalankan sama sekali. Tipe-tipe di signature fungsimu itulah validasinya.
Langkah 4: Menerima JSON dengan POST
Section titled “Langkah 4: Menerima JSON dengan POST”Untuk membuat to-do, client mengirim JSON di body request. Extractor Json membaca body itu dan melakukan deserialize ke struct-mu. Client hanya mengirim judul, jadi kita pakai struct NewTodo tersendiri untuk input-nya:
use axum::{routing::post, Json, Router};use serde::{Deserialize, Serialize};
#[derive(Serialize)]struct Todo { id: u32, title: String, done: bool,}
#[derive(Deserialize)]struct NewTodo { title: String,}
#[tokio::main]async fn main() { let app = Router::new().route("/echo", post(echo_todo));
let listener = tokio::net::TcpListener::bind("127.0.0.1:3000").await.unwrap(); println!("Server berjalan di http://127.0.0.1:3000"); axum::serve(listener, app).await.unwrap();}
async fn echo_todo(Json(input): Json<NewTodo>) -> Json<Todo> { let todo = Todo { id: 1, title: input.title, done: false, }; Json(todo)}Pakai post(...) sebagai ganti get(...) di route-nya. Di curl, -X POST mengatur method, -H menambah header, dan -d mengirim body:
curl -X POST http://127.0.0.1:3000/echo -H "Content-Type: application/json" -d '{"title":"Beli susu"}'{"id":1,"title":"Beli susu","done":false}Sekali lagi, axum memeriksa input sebelum kodemu berjalan. Kalau JSON-nya tidak punya title, kamu mendapat 422 Unprocessable Entity:
curl -i -X POST http://127.0.0.1:3000/echo -H "Content-Type: application/json" -d '{"name":"Beli susu"}'HTTP/1.1 422 Unprocessable Entitycontent-type: text/plain; charset=utf-8content-length: 99
Failed to deserialize the JSON body into the target type: missing field `title` at line 1 column 20Dan kalau kamu lupa header Content-Type, kamu mendapat 415 Unsupported Media Type dengan pesan Expected request with `Content-Type: application/json` . Header itulah yang paling sering terlupa saat menguji dengan curl.
Langkah 5: Shared state untuk API to-do sungguhan
Section titled “Langkah 5: Shared state untuk API to-do sungguhan”Sejauh ini, setiap handler mengarang datanya sendiri. API sungguhan perlu mengingat to-do di antara request. Request bisa datang bersamaan dan ditangani di thread yang berbeda, jadi daftarnya harus aman untuk dipakai bersama. Itulah tugas pasangan yang sudah kamu kenal di halaman Konkurensi:
Mutex<Vec<Todo>>memastikan hanya satu request yang mengubah daftar pada satu waktu.Arc<...>membuat banyak handler bisa memiliki pointer ke daftar yang sama (lihat Smart Pointer).
Kita beri tipe itu nama pendek dengan type Db = Arc<Mutex<Vec<Todo>>>;, memberikannya ke router dengan .with_state(db), dan membacanya di handler dengan extractor State. Axum meng-clone Arc itu (murah: hanya menyalin pointer) untuk setiap request.
Ini program lengkapnya. Ganti isi src/main.rs dengan kode berikut:
use std::sync::{Arc, Mutex};
use axum::{ extract::{Path, State}, http::StatusCode, routing::get, Json, Router,};use serde::{Deserialize, Serialize};
#[derive(Clone, Serialize)]struct Todo { id: u32, title: String, done: bool,}
#[derive(Deserialize)]struct NewTodo { title: String,}
// "Database"-nya: daftar to-do yang dipakai bersama oleh semua request.type Db = Arc<Mutex<Vec<Todo>>>;
#[tokio::main]async fn main() { let db: Db = Arc::new(Mutex::new(Vec::new()));
let app = Router::new() .route("/todos", get(list_todos).post(create_todo)) .route("/todos/{id}", get(get_todo).delete(delete_todo)) .with_state(db);
let listener = tokio::net::TcpListener::bind("127.0.0.1:3000").await.unwrap(); println!("Server berjalan di http://127.0.0.1:3000"); axum::serve(listener, app).await.unwrap();}
// GET /todosasync fn list_todos(State(db): State<Db>) -> Json<Vec<Todo>> { let todos = db.lock().unwrap(); Json(todos.clone())}
// GET /todos/{id}async fn get_todo(State(db): State<Db>, Path(id): Path<u32>) -> Result<Json<Todo>, StatusCode> { let todos = db.lock().unwrap(); match todos.iter().find(|todo| todo.id == id) { Some(todo) => Ok(Json(todo.clone())), None => Err(StatusCode::NOT_FOUND), }}
// POST /todosasync fn create_todo(State(db): State<Db>, Json(input): Json<NewTodo>) -> (StatusCode, Json<Todo>) { let mut todos = db.lock().unwrap(); let id = todos.iter().map(|todo| todo.id).max().unwrap_or(0) + 1; let todo = Todo { id, title: input.title, done: false, }; todos.push(todo.clone()); (StatusCode::CREATED, Json(todo))}
// DELETE /todos/{id}async fn delete_todo(State(db): State<Db>, Path(id): Path<u32>) -> StatusCode { let mut todos = db.lock().unwrap(); let before = todos.len(); todos.retain(|todo| todo.id != id); if todos.len() < before { StatusCode::NO_CONTENT } else { StatusCode::NOT_FOUND }}Mari kita bahas ide-ide barunya:
- Satu path, beberapa method.
get(list_todos).post(create_todo)mengirimGET /todosdanPOST /todoske handler yang berbeda. - Status code adalah nilai kembalian.
StatusCodeberasal dari modulhttpmilik axum. Handler bisa mengembalikanResult:Ok(Json(todo))menjadi 200 dengan JSON, danErr(StatusCode::NOT_FOUND)menjadi 404 yang kosong. Tuple(StatusCode::CREATED, Json(todo))mengatur status dan body sekaligus. MengembalikanStatusCode::NO_CONTENT(204) saja artinya “beres, tidak ada yang perlu dikirim balik”. lock()dan guard-nya.db.lock().unwrap()menunggu sampai tidak ada request lain yang memakai daftar itu, lalu memberimu sebuah guard yang bisa kamu baca dan ubah sepertiVecbiasa. Kunci dilepas saat guard keluar dari scope (cakupan) di akhir fungsi.- Id baru. Id berikutnya adalah id terbesar di daftar ditambah satu, atau 1 kalau daftarnya kosong.
- Menghapus.
retainmenyimpan semua to-do yang id-nya tidak cocok. Kalau panjangnya tidak berubah, berarti tidak ada yang terhapus, jadi kita kembalikan 404.
Kenapa std::sync::Mutex dan bukan versi async? Tidak masalah (dan malah lebih cepat) selama kamu tidak memegang kunci melewati sebuah .await. Tidak ada handler kita yang melakukannya: semuanya mengunci, mengolah daftar, lalu selesai.
Logika daftar-dan-kunci ini tidak butuh web server, jadi bisa kamu coba di Playground. Fungsi-fungsi berikut melakukan pekerjaan yang sama dengan handler-nya, tanpa axum:
use std::sync::{Arc, Mutex};
#[derive(Clone, Debug)]struct Todo { id: u32, title: String, done: bool,}
type Db = Arc<Mutex<Vec<Todo>>>;
fn create(db: &Db, title: &str) -> Todo { let mut todos = db.lock().unwrap(); let id = todos.iter().map(|todo| todo.id).max().unwrap_or(0) + 1; let todo = Todo { id, title: title.to_string(), done: false }; todos.push(todo.clone()); todo}
fn get(db: &Db, id: u32) -> Option<Todo> { let todos = db.lock().unwrap(); todos.iter().find(|todo| todo.id == id).cloned()}
fn delete(db: &Db, id: u32) -> bool { let mut todos = db.lock().unwrap(); let before = todos.len(); todos.retain(|todo| todo.id != id); todos.len() < before}
fn main() { let db: Db = Arc::new(Mutex::new(Vec::new())); let second_handle = Arc::clone(&db); // yang dilakukan axum untuk setiap request
println!("{:?}", create(&db, "Beli susu")); println!("{:?}", create(&second_handle, "Belajar axum")); println!("{:?}", get(&db, 2)); println!("{:?}", get(&db, 99)); println!("1 terhapus? {}", delete(&db, 1)); println!("1 terhapus lagi? {}", delete(&db, 1)); println!("sisa: {}", db.lock().unwrap().len());}Output:
Todo { id: 1, title: "Beli susu", done: false }Todo { id: 2, title: "Belajar axum", done: false }Some(Todo { id: 2, title: "Belajar axum", done: false })None1 terhapus? true1 terhapus lagi? falsesisa: 1To-do yang dibuat lewat second_handle ikut muncul saat kamu membaca lewat db, karena kedua Arc menunjuk ke daftar yang sama.
Langkah 6: Uji dengan curl
Section titled “Langkah 6: Uji dengan curl”Jalankan server dengan cargo run, lalu di terminal kedua, coba semua endpoint. Awalnya daftarnya kosong:
curl http://127.0.0.1:3000/todos[]Buat sebuah to-do. Perhatikan status 201 Created-nya:
curl -i -X POST http://127.0.0.1:3000/todos -H "Content-Type: application/json" -d '{"title":"Beli susu"}'HTTP/1.1 201 Createdcontent-type: application/jsoncontent-length: 41
{"id":1,"title":"Beli susu","done":false}Buat satu lagi, lalu tampilkan semuanya:
curl -X POST http://127.0.0.1:3000/todos -H "Content-Type: application/json" -d '{"title":"Belajar axum"}'curl http://127.0.0.1:3000/todos{"id":2,"title":"Belajar axum","done":false}[{"id":1,"title":"Beli susu","done":false},{"id":2,"title":"Belajar axum","done":false}]Ambil satu berdasarkan id, lalu minta yang tidak ada:
curl http://127.0.0.1:3000/todos/2curl -i http://127.0.0.1:3000/todos/99{"id":2,"title":"Belajar axum","done":false}HTTP/1.1 404 Not Foundcontent-length: 0Hapus to-do 1, lalu coba hapus sekali lagi:
curl -i -X DELETE http://127.0.0.1:3000/todos/1curl -i -X DELETE http://127.0.0.1:3000/todos/1HTTP/1.1 204 No Content
HTTP/1.1 404 Not Foundcontent-length: 0curl http://127.0.0.1:3000/todos[{"id":2,"title":"Belajar axum","done":false}]Axum juga menangani beberapa kesalahan untukmu. Tipe yang salah di JSON menghasilkan 422 yang menjelaskan masalahnya (di sini: title seharusnya string, bukan integer), dan method yang tidak kamu siapkan untuk suatu path menghasilkan 405 Method Not Allowed, dengan header yang mencantumkan method yang bisa dipakai:
curl -i -X POST http://127.0.0.1:3000/todos -H "Content-Type: application/json" -d '{"title": 42}'curl -i -X PUT http://127.0.0.1:3000/todos/2HTTP/1.1 422 Unprocessable Entitycontent-type: text/plain; charset=utf-8content-length: 130
Failed to deserialize the JSON body into the target type: title: invalid type: integer `42`, expected a string at line 1 column 12HTTP/1.1 405 Method Not Allowedallow: GET,HEAD,DELETEcontent-length: 0Hentikan server dengan Ctrl + C lalu jalankan lagi: daftarnya kosong. Datanya hanya tinggal di memori. Menyimpannya adalah ide pertama di bawah.
Kesalahan umum
Section titled “Kesalahan umum”Memakai sintaks route lama :id. Banyak tutorial di internet ditulis untuk axum 0.7, yang memakai /todos/:id. Di axum 0.8 kode ini tetap bisa dikompilasi, tapi programnya panic begitu dijalankan:
thread 'main' (12345) panicked at src/main.rs:32:10:Path segments must not start with `:`. For capture groups, use `{capture}`. If you meant to literally match a segment starting with a colon, call `without_v07_checks` on the router.Pesannya bilang segmen path tidak boleh diawali : dan menyarankan {capture}. Solusinya: tulis /todos/{id}.
Menaruh Json sebelum extractor lain. Body request hanya bisa dibaca sekali, jadi extractor yang membacanya (Json) harus menjadi parameter terakhir. Kalau kamu menulis create_todo(Json(input): Json<NewTodo>, State(db): State<Db>), pesan error-nya tidak langsung bilang begitu. Baris-baris pentingnya adalah:
error[E0277]: the trait bound `fn(Json<NewTodo>, State<...>) -> ... {create_todo}: Handler<_, ...>` is not satisfied |31 | .route("/todos", get(list_todos).post(create_todo)) | ---- ^^^^^^^^^^^ unsatisfied trait bound | = note: Consider using `#[axum::debug_handler]` to improve the error messageKalau kamu melihat “Handler is not satisfied” (handler-nya tidak memenuhi trait Handler), periksa urutan parameter, dan pastikan setiap tipe kembalian adalah sesuatu yang bisa diubah axum menjadi response.
Lupa -H "Content-Type: application/json" di curl. Server membalas 415. Header ini wajib untuk extractor Json.
Lupa menjalankan ulang. Server yang sedang berjalan masih program yang lama. Hentikan dengan Ctrl + C lalu cargo run lagi setiap kali ada perubahan.
Ide pengembangan
Section titled “Ide pengembangan”Setelah API dasarnya jalan, ini beberapa langkah berikutnya yang bagus. Coba satu per satu.
- Tandai to-do sebagai selesai. Tambahkan
PATCH /todos/{id}dengan.patch(...)di route kedua. Cari to-do-nya denganiter_mut(), setdone = true, lalu kembalikan. - Validasi. Judul kosong seharusnya tidak boleh. Handler bisa mengembalikan
Result<(StatusCode, Json<Todo>), (StatusCode, String)>, dan mengirim balikErr((StatusCode::BAD_REQUEST, String::from("judul tidak boleh kosong"))). Pakaititle.trim().is_empty()untuk mengeceknya. - Id yang tidak pernah berulang. Dengan cara “id terbesar ditambah satu”, menghapus to-do terbaru membuat id-nya bisa dipakai lagi. Sebagai gantinya, simpan penghitung terpisah di state-mu, misalnya struct berisi
todos: Vec<Todo>dannext_id: u32di dalamMutex. - Simpan permanen ke file JSON. Tambahkan juga derive
DeserializepadaTodo. Saat server mulai, bacatodos.jsondenganstd::fs::read_to_stringdanserde_json::from_str(atau mulai dari kosong kalau file-nya belum ada). Setiap kali ada perubahan, tulis daftarnya kembali denganserde_json::to_string_prettydanstd::fs::write. Halaman Baca-Tulis File dan JSON dengan Serde berisi semua yang kamu butuhkan. Nantinya kamu bisa mengganti file itu dengan database sungguhan seperti SQLite. - Logging dengan tower-http. Jalankan
cargo add tower-http --features tracedancargo add tracing-subscriber, lalu tambahkan layer logging. Sekarang server mencetak satu baris untuk setiap request, berisi method, path, status, dan berapa lama prosesnya:
use tower_http::trace::TraceLayer;
// di awal main:tracing_subscriber::fmt() .with_max_level(tracing_subscriber::filter::LevelFilter::DEBUG) .init();
// saat membuat router:let app = Router::new() .route("/todos", get(list_todos).post(create_todo)) .route("/todos/{id}", get(get_todo).delete(delete_todo)) .layer(TraceLayer::new_for_http()) .with_state(db);- Catatan untuk deployment.
127.0.0.1hanya menerima request dari komputermu sendiri. Untuk menerima request dari mesin lain (misalnya di dalam Docker atau di server), bind ke0.0.0.0. Banyak layanan hosting memberi tahu programmu port mana yang dipakai lewat environment variablePORT, yang bisa kamu baca denganstd::env::var(lihat Environment Variable dan Proses). Build dengancargo build --releaseuntuk server yang jauh lebih cepat. Hasilnya adalah satu file ditarget/release/yang bisa kamu salin ke server Linux, atau masukkan ke image Docker yang kecil. Untuk situs publik, taruh di belakang reverse proxy (seperti Caddy atau nginx) yang mengurus HTTPS.
Ringkasan
Section titled “Ringkasan”- Handler adalah sebuah
async fn. Router memetakan method dan path ke handler.axum::servemenjalankannya di atasTcpListenermiliktokio. - Extractor mengambil data dari request:
Pathuntuk bagian URL (/todos/{id}),Jsonuntuk body (selalu paling akhir), danStateuntuk data yang dipakai bersama. - Kembalikan
Json(value)untuk JSON,StatusCodeuntuk status, tuple untuk mengatur keduanya, atauResultuntuk memilih antara sukses dan status error. Arc<Mutex<Vec<Todo>>>membagi satu daftar dengan aman ke semua request. Kunci, pakai, lalu biarkan guard-nya di-drop. Jangan memegangnya melewati.await.- Uji API-mu dengan
curl:-imenampilkan status,-Xmengatur method,-Hmenambah header, dan-dmengirim body.
Kuis singkat
Section titled “Kuis singkat”Kuis singkat
1. Di axum 0.8, bagaimana cara menulis route dengan id di path-nya?
2. Sebuah handler mengembalikan (StatusCode::CREATED, Json(todo)). Apa yang diterima client?
3. Kenapa daftar yang dipakai bersama dibungkus dengan Arc<Mutex<...>>?
4. Extractor mana yang harus ada di urutan terakhir parameter handler?
5. Kamu mengirim JSON dengan curl -X POST tapi lupa header Content-Type: application/json. Apa balasan axum?
Skor: 0 / 5
Bacaan lebih lanjut
Section titled “Bacaan lebih lanjut”- Dokumentasi axum — docs.rs (bahasa Inggris) — panduan resmi, dengan bagian tentang routing, handler, dan response.
- Extractor axum — docs.rs (bahasa Inggris) — semua extractor bawaan dan aturan urutannya.
- Tutorial Tokio (bahasa Inggris) — pelajari async runtime yang menjalankan axum, termasuk shared state.
- Dokumentasi tower-http — docs.rs (bahasa Inggris) — layer siap pakai untuk logging, CORS, kompresi, dan timeout.
- HTTP response status codes — MDN (bahasa Inggris) — arti 200, 201, 204, 404, 422, dan kode lainnya.