Belajar Bikin API Pake Rust Actix - Part 1 (Introduction)

Tech writer and lecturer from Indonesia who is passionate about backend technology.
I ensure that 99% of my content is written in Indonesian because I aim to encourage Indonesian tech enthusiasts to write and share their knowledge.
I often provide guidance to IT students in Indonesia, emphasizing the importance of continuous learning. Don't waste your post-campus life by doing nothing. Read, learn, and study more to become better professionals in the tech industry!
Instalasi
Hello, akhirnya setelah bertapa sekian lama saya pengen nulis kembali cara bikin API pake Rust. Karena lagi pengen ngulik hal yang baru, saya memutuskan untuk pake Rust (Biar ada aja yang nulis). Sumbernya akan berdasarkan https://actix.rs/docs/getting-started.
Pertama, pastiin kamu udah install Rust & Cargo ya, saya disini gak akan ngajarin.
Pastiin ketika kamu ketik cargo di terminal sudah muncul tulisan seperti ini:
hudya@perogeremmer-pc:~/code/rust$ cargo
Rust's package manager
Usage: cargo [+toolchain] [OPTIONS] [COMMAND]
cargo [+toolchain] [OPTIONS] -Zscript <MANIFEST_RS> [ARGS]...
Options:
-V, --version Print version info and exit
--list List installed commands
--explain <CODE> Provide a detailed explanation of a rustc error message
-v, --verbose... Use verbose output (-vv very verbose/build.rs output)
-q, --quiet Do not print cargo log messages
--color <WHEN> Coloring [possible values: auto, always, never]
-C <DIRECTORY> Change to DIRECTORY before doing anything (nightly-only)
--locked Assert that `Cargo.lock` will remain unchanged
--offline Run without accessing the network
--frozen Equivalent to specifying both --locked and --offline
--config <KEY=VALUE|PATH> Override a configuration value
-Z <FLAG> Unstable (nightly-only) flags to Cargo, see 'cargo -Z help' for details
-h, --help Print help
Commands:
build, b Compile the current package
check, c Analyze the current package and report errors, but don't build object files
clean Remove the target directory
doc, d Build this package's and its dependencies' documentation
new Create a new cargo package
init Create a new cargo package in an existing directory
add Add dependencies to a manifest file
remove Remove dependencies from a manifest file
run, r Run a binary or example of the local package
test, t Run the tests
bench Run the benchmarks
update Update dependencies listed in Cargo.lock
search Search registry for crates
publish Package and upload this package to the registry
install Install a Rust binary
uninstall Uninstall a Rust binary
... See all commands with --list
See 'cargo help <command>' for more information on a specific command.
Selanjutnya, pada direktori yang kamu inginkan, buat sebuah projek baru dengan cara mengetikkan:
cargo new namaprojek
Misalnya:
cargo new api-tweet
Jadi kita akan coba membuat API yang so so Twitter gitu deh. Hasilnya akan menjadi seperti ini:
hudya@perogeremmer-pc:~/code/rust$ cargo api-tweet
Creating binary (application) `api-tweet` package
note: see more `Cargo.toml` keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
Sekarang kamu bisa buka VSCode atau editor favorit kamu lalu buka projeknya dan kamu akan melihat struktur seperti ini:
๐ src/
โโโ ๐ main.rs
๐ target/
๐ .gitignore
๐ Cargo.lock
๐ Cargo.toml
๐ README.md
Sekarang ubah main.rs lalu masukkan kode berikut:
use actix_web::{get, App, HttpResponse, HttpServer, Responder};
#[get("/")]
async fn hello() -> impl Responder {
HttpResponse::Ok().body("Hello, World!")
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| {
App::new()
.service(hello)
})
.bind(("127.0.0.1", 8080))?
.run()
.await
}
Penjelasan
Kita memiliki fungsi bernama
hello()yang akan menjadi fungsi pertama kita.Syntax
asyncdigunakan karena framework actix bertipe asynchronous ya harus dimasukin asyncimpl Responderartinya kita menerapkan trait di Rust, trait adalah semacam aturan untuk pengembalian tipe data. Karena Rust cukup strict kita harus jelaskan kembaliannya dalam tipe data apa, disini kita mengembalikannya dalam objek Responder dari actix.Fungsi
main()adalah fungsi untuk inisiasi API kita, disini mengembalikanstd::io::Resultatau tipe data ResultDi Rust
Resultadalah enum dengan 2 kemungkinan: Ok(value) atau Err(error).Di Rust,
<()>merupakan generic parameter, artinya sama seperti void (tidak ada nilai yang dikembalikan.Kita registrasi routes
/atau index pada service lalu kita bind ke alamatlocalhostdengan port8080.
Jalankan dengan cara cargo run dan kamu akan menemukan hasil seperti ini:
hudya@perogeremmer-pc:~/code/rust/api-tweet$ cargo run
Compiling api-tweet v0.1.0 (/home/hudya/code/rust/api-tweet)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 2.67s
Running `target/debug/api-tweet`
Langsung saja kunjungi localhost:8080 dan kamu akan menemukan pesan Hello, World!.
Code Time
Sekarang kita akan ubah strukturnya menjadi seperti ini:
๐ src/
โโโ ๐ controller/
โ โโโ ๐ main_controller.rs
โ โโโ ๐ mod.rs
โ โโโ ๐ user_controller.rs
โโโ ๐ helper/
โ โโโ ๐ mod.rs
โ โโโ ๐ response_json.rs
โโโ ๐ lib.rs
โโโ ๐ main.rs
๐ target/
๐ .gitignore
๐ Cargo.lock
๐ Cargo.toml
Penjelasan
Folder
controllerโ tempat seluruh controllerFolder
helperโ tempat library / helper yang dibutuhkan pada APIlib.rsโ File inisiasi seluruh module / folder
Jadi kita pengen ngebuat supaya lebih terorganisir dimana semua controller dimasukkan ke controller, kebutuhan lainnya kita masukkan ke helper. Segera buat filenya.
Instalasi Library
Kita akan membutuhkan dua library yaitu serde-json dan serde, masukkan perintah berikut pada terminal yang sudah ada di dalam folder projekmu.
cargo add serde-json
cargo add serde --features derive
Hasilnya akan menjadi seperti ini
hudya@perogeremmer-pc:~/code/rust/api-tweet$ cargo add serde-json
warning: translating `serde-json` to `serde_json`
Updating crates.io index
Adding serde_json v1.0.140 to dependencies
Features:
+ std
- alloc
- arbitrary_precision
- float_roundtrip
- indexmap
- preserve_order
- raw_value
- unbounded_depth
hudya@perogeremmer-pc:~/code/rust/api-tweet$ cargo add serde --features derive
Updating crates.io index
Adding serde v1.0.219 to dependencies
Features:
+ derive
+ serde_derive
+ std
- alloc
- rc
- unstable
Library serde adalah library yang digunakan untuk memberitahu di Rust bahwa kita akan merubah tipe data tersebut dengan aturan khusus (trait). Sedangkan serde-json adalah resep agar program Rust mengubah tipe data objek menjadi JSON.
Response JSON
Pertama kita ubah isi file response_json.rs dengan kode berikut:
use serde::Serialize;
use serde_json::Value;
#[derive(Serialize)]
pub struct ResponseJSON {
pub message: String,
pub status: i32,
#[serde(default = "default_empty_object")]
pub values: Value,
}
impl ResponseJSON {
#[allow(dead_code)]
pub fn new(message: String, status: i32, values: Option<Value>) -> Self {
Self {
message,
status,
values: values.unwrap_or_else(default_empty_object),
}
}
pub fn success(message: String, values: Option<Value>) -> Self {
Self {
message,
status: 200,
values: values.unwrap_or_else(default_empty_object),
}
}
}
fn default_empty_object() -> Value {
serde_json::json!({})
}
Penjelasan:
Pada tipe data ResponseJSON kita berikan trait
derive(Serialize)artinya kita minta Rust bersiap untuk merubah tipe data Struct menjadi tipe data yang akan kita minta nantinya.Pada tipe data values, kita masukkan trait
serde(default = "default_empty_object")yang artinya apabila kosong kita akan menggunakan nilai dari fungsidefault_empty_object().Kita punya
impl ResponseJSONartinya yang akan mengimplementasikan isi fungsi daristruct ResponseJSON. Perlu diingat karena Rust tidak mengenal konsep class sehingga apabila kita ingin implementaskan fungsinya kita bisa pake syntaximpldari sebuahstruct.Terdapat dua fungsi
newdansuccess, keduanya memiliki dua parameter yang beda. Fungsinewbisa menerima status, sedangkan fungsisuccesssudah disiapkan status defaultnya 200 (Ok).Pada fungsi
newterdapat traitallow(dead_code)artinya walaupun nanti fungsinewtidak diimplementasikan, kita bisa mengabaikan compiler agar tidak memberikan warning. Harapannya fungsi new ini suatu saat kepake. Walau sebenarnya bisa dihapus saja.Fungsi
default_empty_object()digunakan untuk mengembalikan tipe data JSON kosong apabila nilai parametervaluestidak diisi.
Sekarang ubah file mod.rs pada folder helper dengan kode berikut:
pub mod response_json;
File mod.rs berguna untuk mengexpose file-file di dalam folder supaya bisa dipanggil dari luar folder tersebut, misal dari main.rs.
Main Controller
Sekarang pergi ke main_controller.rs lalu masukkan kode berikut:
use crate::helper::response_json::ResponseJSON;
use actix_web::{HttpResponse, Responder, get};
#[get("/")]
async fn index() -> impl Responder {
HttpResponse::Ok().json(
ResponseJSON::success("Welcome to the API".to_string(), None)
)
}
Disini kita akan mengembalikan pesan Welcome to the API dan dengan value kosong.
Library Init File
Pada file lib.rs kita masukkan kode berikut:
pub mod helper;
pub mod controller;
Init File
Sekarang kembali ke main.rs lalu ubah isinya agar menjadi seperti ini:
use actix_web::{get, App, HttpResponse, HttpServer, Responder};
use api_tweet::controller;
#[get("/")]
async fn hello() -> impl Responder {
HttpResponse::Ok().body("Hello world!")
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| {
App::new()
.service(controller::main_controller::index)
})
.bind(("127.0.0.1", 8080))?
.run()
.await
}
Perhatikan pada fungsi .service, kita tidak lagi menggunakan fungsi hello melainkan menggunakan fungsi index dari main_controller.rs.
Pada baris kedua, use api_tweet::controller kita sesuaikan nama folder dari projek kita yang ditulis pada Cargo.tml yaitu name = "api-tweet". Jadi kita bisa pakai seluruh file di dalam folder controller untuk digunakan pada main.rs.
Kenapa kita perlu
lib.rs?
Karena kita akan import semua modul (folder) di dalam sebuah file sebagai gerbang utama, tujuannya agar lebih terorganisir. Kita bisa aja sih manggil langsung di dalam main.rs untuk inisiasi modulnya, nanti kodenya jadi gini:
use actix_web::{get, App, HttpResponse, HttpServer, Responder};
mod controller;
mod helper;
#[get("/")]
async fn hello() -> impl Responder {
HttpResponse::Ok().body("Hello world!")
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| {
App::new()
.service(controller::main_controller::index)
})
.bind(("127.0.0.1", 8080))?
.run()
.await
}
tapi bayangin kalo foldernya jadi ada banyak, otomatis makin banyak lagi yang di-import. Dengan memindahkan gerbangnya ke lib.rs kita bisa membuat kode jadi sedikit lebih bersih.
Sekarang kembali jalankan dengan cargo run dan akses localhost:8080, kamu akan mendapatkan response:
{"message":"Welcome to the API","status":200,"values":{}}
Cukup mudah kan membuat API di Rust? ๐



