kodingup.camp

Beranda/Node.js Backend: Dasar, Express & REST API

Merancang REST API

Modul 7 dari 105 menit bacaDesigning REST APIs

Isi modul

    Kenapa ini penting

    API adalah kontrak. Begitu frontend, aplikasi mobile, atau tim lain mulai memakainya, mengubah bentuk URL atau format JSON berarti merusak kode mereka. Desain yang asal-asalan (/getAllTasks, /deleteTaskById?id=5, error kadang string kadang objek) membuat semua orang yang memakainya harus menebak-nebak. Desain yang konsisten membuat API bisa ditebak: kalau kamu tahu cara mengambil tugas, kamu otomatis tahu cara mengambil pengguna.

    REST (Representational State Transfer) adalah gaya desain API yang paling umum. Ia bukan library dan bukan standar resmi yang kaku, melainkan sekumpulan kebiasaan yang memanfaatkan HTTP sebagaimana mestinya: URL untuk menunjuk "benda", method untuk menyatakan "aksi", dan status code untuk menyatakan "hasil".

    Konsep inti

    Resource dan URL

    Pikirkan API seperti lemari arsip. Setiap laci adalah koleksi (/tugas, /pengguna), dan setiap map di dalamnya adalah satu item (/tugas/42). URL menunjuk benda, bukan aksi.

    Hindari Lebih baik
    GET /getTugas GET /tugas
    POST /createTugas POST /tugas
    POST /tugas/hapus/5 DELETE /tugas/5
    GET /tugasByUser?id=3 GET /pengguna/3/tugas

    Aturan praktis: kata benda jamak, huruf kecil, pisahkan kata dengan tanda hubung (/kategori-produk), dan relasi bersarang maksimal satu tingkat (/pengguna/3/tugas, bukan /pengguna/3/proyek/9/tugas/42/komentar).

    Method dan maknanya

    Method Aksi Contoh Status sukses Idempoten?
    GET Ambil GET /tugas, GET /tugas/5 200 Ya
    POST Buat baru POST /tugas 201 + header Location Tidak
    PUT Ganti seluruhnya PUT /tugas/5 200 Ya
    PATCH Ubah sebagian PATCH /tugas/5 dengan { "selesai": true } 200 Biasanya
    DELETE Hapus DELETE /tugas/5 204 Ya

    Idempoten artinya mengulang request yang sama berkali-kali menghasilkan keadaan akhir yang sama. Menghapus tugas 5 dua kali tetap berakhir "tugas 5 tidak ada" (request kedua boleh menjawab 404, tapi datanya tidak berubah lagi). Mengirim POST dua kali membuat dua tugas. Ini penting untuk retry otomatis: GET, PUT, DELETE aman diulang, POST tidak.

    GET tidak boleh mengubah data. Kalau GET /tugas/5/hapus menghapus data, crawler mesin pencari atau fitur prefetch browser bisa menghapus datamu tanpa sengaja.

    Status code yang benar

    Selain yang sudah dibahas, dua kode berikut sering dipakai di API:

    Kelompok besar: 2xx sukses, 3xx redirect, 4xx kesalahan client (client harus memperbaiki request), 5xx kesalahan server (client tidak bisa berbuat apa-apa selain mencoba lagi nanti).

    Pagination, filter, dan sort

    Jangan pernah mengembalikan seluruh tabel. Bayangkan 100 ribu tugas dikirim sekaligus ke HP pengguna. Gunakan query string:

    Sertakan metadata agar client bisa membuat tombol halaman:

    { "data": [ ... ], "meta": { "page": 1, "limit": 10, "total": 37, "totalPages": 4 } }
    

    Pagination model page/limit (offset) mudah dipahami. Untuk data yang sangat besar atau feed yang terus bertambah, banyak API memakai cursor pagination (?after=<id terakhir>), yang dibahas saat kita memakai database.

    Versioning

    Suatu saat kamu perlu mengubah format secara tidak kompatibel. Supaya client lama tidak rusak, beri versi sejak awal. Cara paling umum dan paling mudah dipahami adalah di URL: /api/v1/tugas. Saat ada perubahan besar, buat /api/v2/... dan biarkan v1 hidup selama masa transisi. Alternatif lain lewat header (Accept: application/vnd.app.v2+json), tapi lebih sulit diuji dengan browser biasa.

    Format response dan error yang konsisten

    Pilih satu "amplop" dan pakai di semua endpoint. Contoh yang kita pakai:

    // sukses
    { "data": { "id": "…", "judul": "Belajar Node" } }
    
    // error
    { "error": { "code": "VALIDATION_ERROR", "message": "Data yang dikirim tidak valid", "details": { } } }
    

    Jangan pernah mengirim stack trace atau pesan error database ke client. Itu membocorkan struktur internal ke penyerang. Catat di log server, kirim pesan umum ke client.

    Nama field dan tanggal

    Pilih satu gaya penamaan (camelCase umum di dunia JavaScript) dan pakai konsisten. Kirim tanggal dalam format ISO 8601 UTC (2026-10-23T18:11:43.157Z), biarkan frontend yang mengubahnya ke zona waktu pengguna (WIB, WITA, WIT).

    Contoh kode

    Rancangan lengkap API Tugas yang akan kita bangun di modul CRUD:

    # Dasar URL: /api/v1
    GET    /api/v1/health                 # → 200 {"status":"ok"}
    GET    /api/v1/tugas                  # → 200 {"data":[...],"meta":{...}}
           ?page=1&limit=10               #   pagination (limit maks 50)
           &selesai=true|false            #   filter status
           &q=kata                        #   cari di judul
           &sort=dibuatPada|-dibuatPada|judul|-judul
    GET    /api/v1/tugas/:id              # → 200 {"data":{...}} | 404
    POST   /api/v1/tugas                  # → 201 {"data":{...}} + Location | 400 | 422
    PATCH  /api/v1/tugas/:id              # → 200 {"data":{...}} | 404 | 422
    DELETE /api/v1/tugas/:id              # → 204 (tanpa body) | 404
    

    Helper kecil untuk menjaga amplop tetap konsisten:

    // src/http.js, helper amplop response
    export const ok = (res, data, meta) => res.json(meta ? { data, meta } : { data });
    export const created = (res, data, lokasi) => res.status(201).location(lokasi).json({ data });
    export const gagal = (res, status, code, message, details) =>
      res.status(status).json({ error: { code, message, details } });
    
    // Pemakaian:
    // ok(res, tugas)                              → 200 {"data":{...}}
    // ok(res, daftar, { page, limit, total })     → 200 {"data":[...],"meta":{...}}
    // created(res, baru, `/api/v1/tugas/${id}`)   → 201 + header Location
    // gagal(res, 404, "NOT_FOUND", "Tugas tidak ditemukan")
    

    Fungsi pagination murni yang bisa diuji tanpa server:

    // paginate.js
    export function paginate(items, page, limit) {
      const total = items.length;
      return {
        data: items.slice((page - 1) * limit, page * limit),
        meta: { page, limit, total, totalPages: Math.ceil(total / limit) },
      };
    }
    
    const angka = Array.from({ length: 23 }, (_, i) => i + 1);
    console.log(paginate(angka, 3, 10));
    // → { data: [ 21, 22, 23 ], meta: { page: 3, limit: 10, total: 23, totalPages: 3 } }
    

    Kesalahan umum

    1. Kata kerja di URL (/createTugas, /tugas/delete/5). Biarkan method HTTP yang menyatakan aksi.
    2. GET yang mengubah data. Bisa terpicu oleh crawler, prefetch, atau sekadar refresh. Ubah data hanya lewat POST/PUT/PATCH/DELETE.
    3. Tidak ada batas limit. Client (atau penyerang) bisa meminta ?limit=1000000 dan membuat server kehabisan memori. Selalu beri maksimum.
    4. Format error berubah-ubah: kadang string, kadang { msg }, kadang { error: { ... } }. Frontend jadi penuh if aneh. Tetapkan satu bentuk.
    5. Membocorkan detail internal seperti stack trace, query SQL, atau path file di response error. Catat di log, kirim pesan umum.

    Latihan

    1. Mudah, Rancang URL dan method REST untuk fitur komentar pada tugas: daftar komentar sebuah tugas, tambah komentar, hapus komentar.
    2. Sedang, Untuk setiap situasi berikut tentukan status code: (a) login dengan password salah, (b) user biasa mencoba menghapus tugas milik orang lain, (c) mendaftar dengan email yang sudah dipakai, (d) server gagal terhubung ke database.
    3. Menantang, Tulis fungsi parseSort(teks, fieldDiizinkan) yang mengubah "-dibuatPada" menjadi { field: "dibuatPada", arah: -1 } dan melempar error kalau field tidak diizinkan. Tambahkan dukungan beberapa field dipisah koma: "selesai,-dibuatPada".
    Lihat solusi

    1.

    GET    /api/v1/tugas/:id/komentar          # daftar komentar tugas
    POST   /api/v1/tugas/:id/komentar          # tambah komentar → 201
    DELETE /api/v1/komentar/:komentarId        # hapus → 204 (komentar punya id sendiri, tak perlu bersarang)
    

    2. (a) 401 Unauthorized, (b) 403 Forbidden, (c) 409 Conflict, (d) 500 Internal Server Error (atau 503 Service Unavailable kalau sifatnya sementara).

    3.

    export function parseSort(teks, fieldDiizinkan) {
      return teks.split(",").map((bagian) => {
        const arah = bagian.startsWith("-") ? -1 : 1;
        const field = bagian.replace(/^-/, "");
        if (!fieldDiizinkan.includes(field)) {
          throw new Error(`Tidak bisa mengurutkan berdasarkan '${field}'`);
        }
        return { field, arah };
      });
    }
    
    console.log(parseSort("selesai,-dibuatPada", ["selesai", "dibuatPada", "judul"]));
    // → [ { field: 'selesai', arah: 1 }, { field: 'dibuatPada', arah: -1 } ]
    

    Ringkasan

    Video pendamping

    Kalau penjelasan di atas belum masuk, tonton ini. Video dari kanal YouTube pihak lain.

    What Is REST API? Examples And How To Use It: Crash Course System Design #3ByteByteGo · Bahasa InggrisBuka di YouTube

    Kuis pemahaman

    Pilih satu jawaban per soal. Penjelasan muncul setelah kamu menjawab.