Sebelum kita mulai koding alangkah baiknya kita baca basmallah dalam hati.
Yuk kita intip langkah - langkah apa saja yang perlu disiapkan dalam membuat restful api ini.
- Pertama kalian harus install rust di dalam komputer masing - masing buka linknya di sini https://www.rust-lang.org/tools/install
- Kedua setelah selesai penginstalan kita mulai pembuatan projeknya ya, ketikkan perintah di dalam terminal. " cargo new nama projek"
- Ketiga siapkan database mysql dengan nama blog_db dan buat tabel baru di dalam database tersebut misalnya posts. Isi tabel tersebut ada id, title dan content
- Keempat , langkah ini barulah kita buatkan script atau kodenya. Di file main.rs isi kodenya seperti ini ya:
mod handler;
mod model;
mod schema;
use actix_web::middleware::Logger;
use actix_web::{web, App, HttpServer};
use sqlx::mysql::{MySqlPool, MySqlPoolOptions};
use dotenv::dotenv;
pub struct AppState {
db: MySqlPool,
}
#[actix_web::main]
async fn main() -> std::io::Result<()>{
if std::env::var_os("RUST_LOG").is_none() {
std::env::set_var("RUST_LOG", "actix_web=info")
}
dotenv().ok();
env_logger::init();
let database_url = std::env::var("DATABASE_URL").expect("DATABASE_URL must be set");
let pool = match MySqlPoolOptions::new()
.max_connections(10)
.connect(&database_url)
.await
{
Ok(pool) => {
println!("✅Connection to the database is successful!");
pool
},
Err(err) => {
println!("🔥 Failed to connect to the database: {:?}", err);
std::process::exit(1);
}
};
println!("🚀 Server started successfully");
HttpServer::new(move || {
App::new()
.app_data(web::Data::new(AppState { db : pool.clone()}))
.wrap(Logger::default())
.service(handler::get_post)
.service(handler::create_post)
.service(handler::get_single_post)
.service(handler::edit_post)
.service(handler::delete_post)
})
.bind(("127.0.0.1", 5050))?
.run()
.await
}
selain file main.rs ada beberapa file yang perlu dipersiapkan diantaranya: handler.rs, model.rs dan schema.rs.
Di dalam file handler.rs memiliki tugas untuk menjalankan segala service yang dibutuhkan dalam mengolah data untuk kodenya bisa dilihat di bawah ini.
use actix_web::{delete, get, patch, post, web, HttpResponse, Responder};
use serde_json::json;
use crate::{
model::{Post, PostResponse}, schema::{CreatePostSchema, FilterOptions}, AppState
};
fn filter_db_record(post: &Post) -> PostResponse {
PostResponse {
id: post.id.to_owned(),
title: post.title.to_owned(),
content: post.content.to_owned(),
}
}
#[get("/api/posts")]
pub async fn get_post(opt: web::Query<FilterOptions>,config: web::Data<AppState>)-> impl Responder{
let limit = opt.limit.unwrap_or(10);
let offset = (opt.page.unwrap_or(1) - 1) * limit;
let posts = sqlx::query_as!(Post, r#"SELECT id, title, content FROM posts ORDER BY id DESC LIMIT ? OFFSET ?"#, limit as i32, offset as i32)
.fetch_all(&config.db)
.await
.unwrap();
let post_resp = posts
.into_iter()
.map(|post| filter_db_record(&post))
.collect::<Vec<PostResponse>>();
let response = serde_json::json!({
"status" : "success",
"posts" : post_resp
});
HttpResponse::Ok().json(response)
}
#[post("/api/posts")]
pub async fn create_post(body: web::Json<CreatePostSchema>, config: web::Data<AppState>) -> impl Responder{
sqlx::query!(r#"INSERT INTO posts(title, content) VALUES(?, ?)"#, body.title, body.content)
.execute(&config.db)
.await;
let query_result = sqlx::query_as!(Post ,r#"SELECT id, title, content FROM posts WHERE title =?"#, body.title)
.fetch_one(&config.db)
.await;
match query_result {
Ok(value) => {
let post_response = serde_json::json!({"status": "success", "data": serde_json::json!({"post": filter_db_record(&value)})});
return HttpResponse::Ok().json(post_response);
},
Err(err) => {
return HttpResponse::InternalServerError().json(serde_json::json!({
"status": "error",
"message": format!("{:?}", err)
}));
}
}
}
#[get("/api/posts/{id}")]
pub async fn get_single_post(path: web::Path<i32>, config: web::Data<AppState>) -> impl Responder{
let post_id = path.into_inner().to_string();
let query_result = sqlx::query_as!(Post, r#"SELECT id, title, content FROM posts WHERE id=?"#, post_id)
.fetch_one(&config.db)
.await;
match query_result {
Ok(post) => {
let post_response = serde_json::json!({"status": "success", "data": serde_json::json!({"post": filter_db_record(&post)})});
return HttpResponse::Ok().json(post_response)
},
Err(err) => {
return HttpResponse::InternalServerError()
.json(serde_json::json!({"status": "error", "message": format!("{:?}", err)}));
}
}
}
#[patch("/api/posts/{id}")]
pub async fn edit_post(path: web::Path<i32>, body: web::Json<CreatePostSchema>, config: web::Data<AppState>)-> impl Responder{
let post_id = path.into_inner().to_string();
let update_result = sqlx::query(r#"UPDATE posts SET title = ?, content = ? WHERE id= ?"#)
.bind(body.title.to_owned())
.bind(body.content.to_owned())
.bind(post_id.to_owned())
.execute(&config.db)
.await;
match update_result{
Ok(value) => {
if value.rows_affected() == 0 {
let message = format!("Post with ID: {} not found", post_id);
return HttpResponse::NotFound().json(json!({"status": "fail", "message": message}));
}
},
Err(err) => {
let message = format!("Internal server error: {}", err);
return HttpResponse::InternalServerError().json(json!({"status": "error", "message": message}));
}
}
let _result = sqlx::query_as!(Post, r#"SELECT id, title, content FROM posts WHERE id=?"#, post_id.to_owned())
.fetch_one(&config.db)
.await;
match _result {
Ok(row) => {
let note_response = serde_json::json!({"status": "success","data": serde_json::json!({
"note": filter_db_record(&row)
})});
HttpResponse::Ok().json(note_response)
}
Err(e) => HttpResponse::InternalServerError()
.json(serde_json::json!({"status": "error","message": format!("{:?}", e)})),
}
}
#[delete("/api/posts/{id}")]
pub async fn delete_post(path: web::Path<i32>, config: web::Data<AppState>) -> impl Responder{
let post_id = path.into_inner().to_string();
let query_result = sqlx::query!(r#"DELETE FROM posts WHERE id = ?"#, post_id )
.execute(&config.db).await;
match query_result {
Ok(result) => {
if result.rows_affected() == 0 {
let message = format!("Post with ID: {} not found", post_id);
return HttpResponse::NotFound().json(json!({"status": "fail", "message": message}));
} else {
return HttpResponse::Ok().json(json!({"status": "success", "message": "Delete record successfully"}));
}
},
Err(err) => {
return HttpResponse::InternalServerError().json(json!({"status": "error", "message": format!("Internal server error {:?}", err)}));
}
}
}
Selanjutnya di dalam file model berisi field yang dibutuhkan di dalam tabel posts:
use serde::{Deserialize, Serialize};
#[derive(Debug, sqlx::FromRow, Serialize, Deserialize)]
#[allow(non_snake_case)]
pub struct Post {
pub id: i32,
pub title: Option<String>,
pub content: Option<String>,
}
#[derive(Debug, Serialize, Deserialize)]
#[allow(non_snake_case)]
pub struct PostResponse {
pub id: i32,
pub title: Option<String>,
pub content: Option<String>,
}
Kemudian file schema.rs memiliki beberapa parameter atau inputan query yang digunakan di setiap endpointnya
use serde::{Deserialize, Serialize};
#[derive(Deserialize, Debug)]
pub struct FilterOptions {
pub page: Option<usize>,
pub limit: Option<usize>,
}
#[derive(Deserialize, Debug)]
pub struct ParamOptions {
pub id: String,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct CreatePostSchema{
pub title: Option<String>,
pub content: Option<String>,
}
ada 2 file yang gak boleh kelewat ya .env dan cargo.toml . masing - masing dari file tersebut memiliki tugas yang berbeda - beda. Jika file .env bertugas untuk konfigurasi koneksi database mysql sedangkan cargo.toml berisi modul - modul program yang yang dibutuhkan dalam pembuatan projek tersebut.
Settingan dari koneksi database mysql di dalam file .env sangat mudah sekali yaitu:
MYSQL_USER= root
MYSQL_DATABASE=blog_db
MYSQL_ROOT_PASSWORD=password
MYSQL_PASSWORD=password
DATABASE_URL=mysql://${MYSQL_USER}:${MYSQL_ROOT_PASSWORD}@localhost:3306/${MYSQL_DATABASE}
Tambahkan modul atau library di file cargo.toml ya
[dependencies]
sqlx = { version = "0.8", features = ["mysql", "runtime-async-std"]}
actix-web = "4.9.0"
mysql = "25.0.1"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
chrono = { version = "0.4", features = ["serde"] }
derive_more = "0.99"
env_logger = "0.11"
isahc = "1.7"
log="0.4"
dotenv = "0.15.0"
Jika semua kode sudah dikerjakan dengan benar di dalam projek anda maka anda bisa melakukan uji coba api lewat curl saja.
1. Method Post : Create new a record
curl -H 'Content-Type: application/json' \
-d '{ "title": "lorem ipsum", "content" : "lorem ipsum dolar sit ae"}' \
-X POST \
http://127.0.0.1:5050/api/posts
2. Method Get : singgle record
curl -H 'Content-Type: application/json' \
-X GET http://127.0.0.1:5050/api/posts/6
3. Method Get : All records
curl -H 'Content-Type: application/json' \
-X GET http://127.0.0.1:5050/api/posts
4. Method Patch : update record by id
curl -H 'Content-Type: application/json' \
-d '{ "title": "Hello coders", "content" : "Hello coders, how are you?"}' \
-X PATCH \
http://127.0.0.1:5050/api/posts/6
5. Method Delete : delete record
curl -H 'Content-Type: application/json' \
-X DELETE http://127.0.0.1:5050/api/posts/6
Begitulah tahapan demi tahapan dalam pembuatan simpel restful api menggunakan bahasa pemrograman rust alias framework actix web. Semoga pembelajaran ini bisa bermanfaat di kemudian hari. Selamat mencobanya ya....heeppiii coding brooo.