Skip to main content

Command Palette

Search for a command to run...

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

Updated
โ€ข7 min read
Belajar Bikin API Pake Rust Actix - Part 1 (Introduction)
M

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 async digunakan karena framework actix bertipe asynchronous ya harus dimasukin async

  • impl Responder artinya 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 mengembalikan std::io::Result atau tipe data Result

  • Di Rust Result adalah 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 alamat localhost dengan port 8080.

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 controller

  • Folder helper โ†’ tempat library / helper yang dibutuhkan pada API

  • lib.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 fungsi default_empty_object().

  • Kita punya impl ResponseJSON artinya yang akan mengimplementasikan isi fungsi dari struct ResponseJSON. Perlu diingat karena Rust tidak mengenal konsep class sehingga apabila kita ingin implementaskan fungsinya kita bisa pake syntax impl dari sebuah struct.

  • Terdapat dua fungsi new dan success, keduanya memiliki dua parameter yang beda. Fungsi new bisa menerima status, sedangkan fungsi success sudah disiapkan status defaultnya 200 (Ok).

  • Pada fungsi new terdapat trait allow(dead_code) artinya walaupun nanti fungsi new tidak 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 parameter values tidak 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? ๐Ÿ˜‰