Lewati ke konten

PRAKTIKUM RSI

Pengantar Backend & Implementasi REST API dengan CRUD Dasar

Routing, CRUD dasar (GET/POST/PUT/DELETE), response format.

REST API

Apa itu Backend?

Backend adalah bagian server yang bertanggung jawab atas logika bisnis, validasi, pengolahan data, dan komunikasi dengan Database.

Client tidak langsung mengakses database. Semua permintaan melewati backend yang menjadi perantara antara frontend dan data.

Bayangkan aplikasi adalah sebuah restoran. Frontend adalah meja tempat pelanggan melihat menu dan memesan. Backend adalah Koki di Dapur.

Pelanggan (Client) tidak diizinkan masuk ke Gudang Makanan (Database) untuk mengambil bahan sendiri. Mereka harus meminta ke Koki. Koki inilah yang bertugas mengecek pesanan, mengambil bahan di gudang, memasaknya (logika bisnis), dan menyajikannya kembali ke pelanggan.

Kenapa Backend Wajib?

Bayangkan frontend langsung terhubung ke database tanpa backend di tengah. Tiga masalah fatal muncul:

Kebocoran Keamanan

Kredensial database dan query rahasia ikut terunduh di kode frontend publik, siapa pun bisa mengintip isi database atau menghapus tabel.

Tanpa Validasi

Data bisa dimanipulasi sebelum disimpan, misalnya mengubah harga barang dari Rp100.000 menjadi Rp-10.000 tanpa pengecekan ulang.

Kacau saat Ramai

Database tidak didesain melayani ribuan akses bersamaan. Backend menjadi traffic controller yang mengamankan dan mengatur antrean.

Karena itu backend wajib berdiri di antara frontend dan data.

Apa itu REST API dan HTTP Methods?

REST adalah arsitektur yang mengatur bagaimana frontend dan backend saling berkomunikasi. Di dalam arsitektur REST ini, HTTP Methods digunakan sebagai standar protokol untuk menentukan aksi apa yang ingin dilakukan terhadap data.

POST (Create)

Membuat atau menambahkan data baru ke server, misalnya saat mendaftarkan akun baru.

GET (Read)

Mengambil atau melihat data dari server, misalnya menampilkan daftar produk.

PUT / PATCH (Update)

Mengubah data yang sudah ada. PUT mengganti seluruh bagian, PATCH hanya sebagian.

DELETE (Delete)

Menghapus data yang ada di server.

Bacaan Lanjutan

Endpoint & URL

Jika HTTP Method bertindak sebagai kata kerja (apa yang ingin dilakukan: mengambil, membuat, menghapus), maka Endpoint & URL bertindak sebagai alamat / objek tujuan dari data tersebut.

Alamat Spesifik Objek/Resource

URL menunjuk data mana yang dituju, dengan nama resource jamak:/api/v1/usersuntuk kumpulan pengguna, atau/api/v1/users/5untuk satu pengguna dengan ID 5.

Keamanan Versi (Versioning)

Awalan /api/v1/ditambahkan agar saat backend diperbarui besar-besaran, aplikasi client lama tidak langsung rusak atau error.

HTTP Method + Endpoint

Walaupun URL /api/v1/users/5dipakai oleh GETmaupun DELETE, hasilnya sangat berbeda. GET mengambil data user, DELETE menghapusnya secara permanen.

Cara Kirim Data dari Client

Setelah menentukan alamat URL dan metode HTTP, bagaimana cara client membungkus data yang ingin dikirim ke server? Data dikirim melalui tiga bagian utama pada Request:

Path Parameter

Bagian yang melekat langsung pada URL untuk menunjuk identitas data spesifik, misalnya/users/5, angka 5 adalah ID unik milik user.

Query Parameter

Bagian tambahan di akhir URL menggunakan tanda tanya (?), untuk filter, pencarian, atau pembagian halaman (pagination), misalnya/products?category=food&page=2.

Request Body

Ruang khusus untuk mengirim data yang lebih kompleks atau berukuran besar dalam format JSON, paling sering dipakai saat mendaftarkan akun baru menggunakan metode POST.

Masing-masing bagian memiliki fungsi spesifik dan digunakan secara bergantian tergantung skenario aksi yang ingin dilakukan oleh client.

Bacaan Lanjutan

Server Menjawab Dengan Format JSON

Setelah backend selesai memproses request, saatnya backend memberikan Response. Agar bisa dipahami platform apa pun, data dibungkus dengan format universal yaitu JSON (JavaScript Object Notation).

{
"success": true,
"message": "Data pengguna berhasil diambil",
"data": {
"id": 5,
"name": "Prayuda Afifan",
"role": "customer",
"email": "prayuda@example.com"
}
}

Membaca Jawaban Server: HTTP Status Code

Bersamaan dengan data JSON, server selalu menyelipkan HTTP Status Code. Fungsinya untuk memberi tahu client secara instan apakah permintaan berhasil, ditolak, atau gagal total, tanpa harus membaca seluruh isi JSON terlebih dahulu.

Berikut contoh yang paling sering muncul:

2xx (Berhasil)

Permintaan diproses tanpa masalah. Contoh:200 OKsukses umum (misal berhasil mengambil data produk),201 Createdsaat akun berhasil didaftarkan di database.

4xx (Kesalahan Client)

"Aku nggak bisa proses ini karena client yang salah kirim." Contoh:400 input salah format,401 belum login akses data rahasia,404 endpoint/data tidak ada.

5xx (Kesalahan Server)

"Permintaanmu benar, tapi sistem kami (backend) yang sedang rusak." Contoh:500 Internal Server Errorada bug/crash di backend atau database sedang mati.

Bacaan Lanjutan

Hands-on 1

Setup Project & Instalasi Express

Sebelum menulis kode backend, kita perlu membuat folder khusus dan menyiapkan proyek Node.js terlebih dahulu.

Kode Hands-on di GitHub
Terminal: Setup project
mkdir my-api && cd my-api # Buat folder my-api dan pergi ke sana mkdir src # Folder khusus kode sumber npm init -y npm install express npm install -D typescript tsx @types/express @types/node npx tsc --init

Setelah setup selesai, struktur folder proyek kita:

my-api/
├─ package.json
├─ tsconfig.json
└─ src/
   └─ index.ts
Apa itu package.json?

File package.jsonbertindak sebagai "identitas" proyek yang mencatat seluruh pustaka (dependencies) yang kita pakai. Ia dibuat otomatis oleh perintahnpm init -y, dan npm install expressmenyimpan Express ke dalamnya.

Bacaan Lanjutan

Menjalankan TypeScript dengan tsx

Dengan tsx, kita bisa menjalankan file.tslangsung tanpa langkah kompilasi terpisah.Dependencies terisi otomatis oleh npm install; yang perlu ditambahkan manual adalah bagian scripts(dan "type"/"main"). Agar mudah, kita daftarkan dua script,startdandev, di dalampackage.json:

package.jsonjson
{
  "name": "my-api",
  "version": "1.0.0",
  "description": "",
  "main": "src/index.ts",
  "scripts": {
    "start": "tsx src/index.ts",
    "dev": "tsx watch src/index.ts"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "type": "commonjs",
  "dependencies": {
    "express": "^5.2.1"
  },
  "devDependencies": {
    "@types/express": "^5.0.6",
    "@types/node": "^26.5.1",
    "tsx": "^4.23.13",
    "typescript": "^7.0.2"
  }
}

Buat juga tsconfig.jsonagar TypeScript mengenali lingkungan Node (seperti processdan modul bawaan) serta import bergaya ./file.ts, sehingga editor/LSP tidak menandai error:

tsconfig.jsonjson
{
  "compilerOptions": {
    "target": "ES2022",
    "lib": ["ES2022"],
    "module": "ESNext",
    "moduleResolution": "Bundler",
    "types": ["node"],
    "strict": true,
    "noEmit": true,
    "allowImportingTsExtensions": true,
    "resolveJsonModule": true,
    "esModuleInterop": true,
    "forceConsistentCasingInFileNames": true,
    "skipLibCheck": true
  },
  "include": ["src"]
}
  • "main": "src/index.ts"mengarahkan main entry point proyek ke file src/index.ts.
  • "start"menjalankan server sekali:tsx src/index.ts.
  • "dev"memakai tsx watchagar server otomatis memuat ulang (hot-reload) setiap file disimpan.

Membuat Endpoint Pertama (GET /health)

Buat file baru bernama  src/index.ts  di dalam folder src proyekmu. File ini adalah entry point di mana kita menyusun aturan server. Di tahap ini kita membuat Health Check Endpoint, yaitu endpoint sederhana untuk memastikan server aktif dan merespons.

src/index.tsts
import express, { type Request, type Response } from 'express';

const app = express();
const PORT: number = 3000;

// Health Check Endpoint
app.get('/health', (req: Request, res: Response) => {
    res.status(200).json({
        status: "success",
        message: "Server is running",
        timestamp: new Date()
    });
});

// Menyalakan server
app.listen(PORT, () => {
    console.log(`Server berjalan di http://localhost:${PORT}`);
});

Menyalakan Server & Pengujian di Browser

Setelah kode di file  src/index.ts  selesai ditulis, saatnya menghidupkan mesin server lokal dan memverifikasi apakah endpoint kita berjalan sesuai harapan.

  1. Jalankan Server di Terminal
    Terminal: Jalankan server
    npx tsx src/index.ts

    Jika berhasil, terminal akan menampilkan pesan:Server berjalan di http://localhost:3000

  2. Uji Endpoint di Browser
    http://localhost:3000/health
  3. Hasil yang Diharapkan (JSON Response).
    response.jsonjson
    {
      "status": "success",
      "message": "Server is running",
      "timestamp": "2026-09-09T16:46:09.000Z"
    }

Arsitektur Layered

Mengapa Butuh Layer?

Kita telah menulis endpoint/health langsung di satu file. Cara tersebut sangat oke untuk skenario sederhana. Namun, bagaimana jika aplikasi mulai menangani fitur kompleks seperti autentikasi, transaksi, validasi input, query database, dan banyak lagi?

Jika semua logika tersebut ditumpuk di dalam satu file atau satu fungsi handler, kode akan menjadi "Spaghetti Code" yang gemuk dan sulit diperbaiki.

Lalu, bagaimana cara menjaga kode tetap bersih dan mudah dipelajari saat fitur mulai bertambah banyak?

Solusi: Layered Architecture

Kita membagi kode ke dalam beberapa lapisan. Setiap layer hanya fokus pada satu tugas spesifik (Separation of Concerns):

  • Router: menangkap request HTTP dan meneruskannya ke fungsi controller yang tepat berdasarkan kombinasi HTTP Method dan URL.
  • Controller: menerima input dari client, memeriksa kelengkapan data, dan menyusun response (status code & JSON).
  • DTO (Data Transfer Object): objek pembawa data yang menyusun/menyaring field sebelum dikirim antar layer maupun ke client, supaya struktur data konsisten dan tidak "bocor" (hanya field yang perlu yang dikirim).
  • Service: tempat khusus untuk logika bisnis dan aturan aplikasi.
  • Repository / Data Access: khusus menangani perintah SQL/query untuk menyimpan atau mengambil data dari Database.

Hands-on 2

Repository

Karena ini contoh dasar, kita gunakan mock data yaitu interface & data didefinisikan langsung di file ini.

Kode Hands-on di GitHub

Hands-on 2 melanjutkan project yang sama (my-api). Kita menambahkan folder untuk arsitektur berlapis:

my-api/
├─ package.json
├─ tsconfig.json
└─ src/
   ├─ index.ts                # diubah: daftarkan router
   ├─ routes/
   │  └─ stallRouter.ts       # baru
   ├─ controllers/
   │  └─ stallController.ts   # baru
   ├─ services/
   │  └─ stallService.ts      # baru
   ├─ repositories/
   │  └─ stallRepository.ts   # baru
   └─ dtos/
      └─ stallDto.ts          # baru
src/repositories/stallRepository.tsts
// Interface simulasi schema tabel Stalls
export interface Stall {
    id: number;
    name: string;
    location: string;
    rating: number;
}

// Mock Data simulasi isi tabel Stalls
const stallsData: Stall[] = [
    { id: 1, name: "Kwetiau Goreng", location: "Kantin Alif FKIP", rating: 4.8 },
    { id: 2, name: "Bakso Pasca", location: "Kantin Bakso Pasca", rating: 4.5 },
    { id: 3, name: "Paket Chikcen Steak Hot Plate", location: "Kantin FK", rating: 4.8 }
];

export class StallRepository {
    async findAll(): Promise<Stall[]> {
        return stallsData;
    }

    async findById(id: number): Promise<Stall | undefined> {
        return stallsData.find(stall => stall.id === id);
    }
}

DTO (Data Transfer Object)

DTO berfungsi sebagai "kontrak data" untuk menentukan struktur pasti data yang akan diterima atau dikembalikan oleh server.

src/dtos/stallDto.tsts
// Interface kontrak data response untuk client
export interface StallResponseDto {
  id: number;
  name: string;
  location: string;
  rating: number;
  isPopular: boolean; // Property tambahan dari kalkulasi logika bisnis
}
  • Memisahkan struktur internal database/repository dengan struktur data yang disajikan ke publik/frontend.
  • Penambahan field khusus tampilan (sepertiisPopular) bisa dilakukan tanpa mengacak-acak struktur tabel asli di repository.

Service

Setelah kontrak DTO siap, kita buat Service Layer yang memanggil Repository untuk mengambil data mentah, lalu menerapkan logika bisnisisPopular), dan mengembalikan data dalam format StallResponseDto.

src/services/stallService.tsts
import { StallRepository } from '../repositories/stallRepository.ts';
import { type StallResponseDto } from '../dtos/stallDto.ts';

export class StallService {
  constructor(private stallRepository: StallRepository = new StallRepository()) {}

  async getAllStalls(): Promise<StallResponseDto[]> {
    const stalls = await this.stallRepository.findAll();

    // Logika Bisnis: Menambahkan penanda 'isPopular' jika rating >= 4.7
    return stalls.map(stall => ({
      ...stall,
      isPopular: stall.rating >= 4.7
    }));
  }

  async getStallById(id: number): Promise<StallResponseDto> {
    const stall = await this.stallRepository.findById(id);

    // Logika Bisnis: Jika data warung tidak ditemukan, lempar error khusus
    if (!stall) {
      throw new Error("STALL_NOT_FOUND");
    }

    return {
      ...stall,
      isPopular: stall.rating >= 4.7
    };
  }
}

Controller

Controller membungkus logika bisnis di balik HTTP: menerimareq, meneruskan parameter ke Service, lalu menyusunresberformat JSON beserta status code yang sesuai.

src/controllers/stallController.tsts
import { type Request, type Response } from 'express';
import { StallService } from '../services/stallService.ts';

export class StallController {
  constructor(private stallService: StallService = new StallService()) {}

  // Menggunakan arrow function agar binding konteks 'this' tetap terjaga di Router
  getStalls = async (req: Request, res: Response): Promise<Response> => {
    try {
      const stalls = await this.stallService.getAllStalls();
      return res.status(200).json({
        status: "success",
        data: stalls
      });
    } catch (error) {
      return res.status(500).json({
        status: "error",
        message: "Gagal mengambil data warung"
      });
    }
  };

  getStallById = async (req: Request, res: Response): Promise<Response> => {
    try {
      const id = Number(req.params.id);
      const stall = await this.stallService.getStallById(id);
      
      return res.status(200).json({
        status: "success",
        data: stall
      });
    } catch (error: any) {
      if (error.message === "STALL_NOT_FOUND") {
        return res.status(404).json({
          status: "fail",
          message: "Data warung tidak ditemukan"
        });
      }
      return res.status(500).json({
        status: "error",
        message: "Terjadi kesalahan pada server"
      });
    }
  };
}

Router

Layer ini bertugas memetakan pasangan HTTP Method (mis. GET) dan URL Path (mis. /atau /:id) ke fungsi handler yang sesuai di StallController.

src/routes/stallRouter.tsts
import { Router } from 'express';
import { StallController } from '../controllers/stallController.ts';

const stallRouter = Router();
const stallController = new StallController();

// Memetakan endpoint ke metode controller
stallRouter.get('/', stallController.getStalls);
stallRouter.get('/:id', stallController.getStallById);

export { stallRouter };

Integrasi ke Entry Point

Seluruh lapisan (Repository → Service → Controller → Router) kini digabungkan ke file entry point utama server, src/index.ts.

src/index.tsts
import express, { type Request, type Response, type Application } from 'express';
import { stallRouter } from './routes/stallRouter.ts';

const app: Application = express();
const PORT: number = 3000;

app.use(express.json());

app.get('/health', (req: Request, res: Response) => {
    res.status(200).json({ status: "success", message: "Server running" });
});

// Pendaftaran Module Router dengan URL Prefix
app.use('/api/v1/stalls', stallRouter);

app.listen(PORT, () => {
    console.log(`Server berjalan di http://localhost:${PORT}`);
});

Pengujian Endpoint

Jalankan server dengannpm run dev, lalu buka browser dan uji endpoint berikut:

  1. Ambil Semua Warung.

    GET http://localhost:3000/api/v1/stalls

    Hasil: mengembalikan daftar warung lengkap dengan propertiisPopular.

  2. Ambil Detail Warung (ID Ada).

    GET http://localhost:3000/api/v1/stalls/1

    Hasil: mengembalikan objek warung Kwetiau Goreng (Status 200).

  3. Ambil Detail Warung (ID Tidak Ada).

    GET http://localhost:3000/api/v1/stalls/99

    Hasil: mengembalikan{"status":"fail","message":"Data warung tidak ditemukan"}(Status 404).

Diagram
Diagram (perbesar)
100%