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

# 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](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:

```plaintext
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:

```plaintext
cargo new namaprojek
```

Misalnya:

```plaintext
cargo new api-tweet
```

Jadi kita akan coba membuat API yang so so Twitter gitu deh. Hasilnya akan menjadi seperti ini:

```plaintext
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:

```plaintext
📁 src/
│── 📄 main.rs
📁 target/
📄 .gitignore
📄 Cargo.lock
📄 Cargo.toml
📄 README.md
```

Sekarang ubah main.rs lalu masukkan kode berikut:

```rust
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:

```plaintext
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:

```plaintext
📁 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.

```rust
cargo add serde-json
cargo add serde --features derive
```

Hasilnya akan menjadi seperti ini

```rust
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:

```rust
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:

```rust
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:

```rust
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:

```rust
pub mod helper;
pub mod controller;
```

## Init File

Sekarang kembali ke `main.rs` lalu ubah isinya agar menjadi seperti ini:

```rust
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:

```rust
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:

```json
{"message":"Welcome to the API","status":200,"values":{}}
```

Cukup mudah kan membuat API di Rust? 😉
