Express.js 5
  • Bab 5 dari 9
  • 25 menit
  • 4 soal kuis

Membangun REST API

Di bab ini (6 bagian)
  1. Tujuan
  2. Konvensi REST dan Status Code
  3. Membuat Operasi CRUD Lengkap
  4. Validasi Input dari Klien
  5. Menyusun Struktur Folder Proyek
  6. Rangkuman

Tujuan bab ini

Dalam bab ini, kamu akan mempelajari cara membangun sebuah REST API yang solid dan sesuai standar industri. Tujuan utama kita mencakup:

  1. Menerapkan konvensi REST dan status code yang tepat.
  2. Membuat operasi CRUD (Create, Read, Update, Delete) yang lengkap.
  3. Menyusun struktur folder proyek yang rapi dan mudah dirawat.

Konvensi REST dan Status Code

REST (Representational State Transfer) adalah gaya arsitektur standar untuk merancang API. Saat membangun REST API, kita berinteraksi dengan sebuah "resource" atau sumber daya. Praktik terbaik menyarankan penggunaan kata benda jamak untuk menamai resource tersebut, misalnya /api/users bukan /api/user atau /api/getUser.

Aksi yang dilakukan pada resource tersebut ditentukan oleh metode HTTP yang digunakan. Ini membuat URL kita tetap bersih dan mudah diprediksi.

Berikut adalah pemetaan metode HTTP dan fungsinya:

  • GET: Digunakan untuk membaca data. Contoh: GET /api/users untuk mengambil daftar semua pengguna, atau GET /api/users/:id untuk melihat detail satu pengguna secara spesifik.
  • POST: Digunakan untuk membuat data baru. Contoh: POST /api/users akan membuat pengguna baru.
  • PUT / PATCH: Keduanya digunakan untuk memperbarui data. Biasanya PUT untuk mengganti seluruh data resource, sedangkan PATCH untuk mengubah sebagian data saja. Contoh: PATCH /api/users/:id.
  • DELETE: Digunakan untuk menghapus data. Contoh: DELETE /api/users/:id.

Selain metode, API yang baik juga harus mengembalikan HTTP status code yang tepat agar klien (seperti aplikasi frontend atau mobile) mengerti apa yang terjadi dengan permintaan mereka.

  • 200 OK: Permintaan berhasil. Biasanya untuk GET, PUT, atau PATCH.
  • 201 Created: Resource baru berhasil dibuat. Ini adalah respons standar untuk POST yang sukses.
  • 204 No Content: Permintaan berhasil, tetapi tidak ada data yang perlu dikembalikan. Sangat cocok untuk DELETE yang sukses.
  • 400 Bad Request: Permintaan klien tidak valid, misalnya format data salah.
  • 401 Unauthorized: Klien belum terautentikasi atau gagal login.
  • 403 Forbidden: Klien sudah login, tetapi tidak memiliki izin (hak akses) untuk melakukan tindakan tersebut.
  • 404 Not Found: Resource yang diminta tidak ada.
  • 500 Server Error: Terjadi kesalahan internal di sisi server kita.

Kesalahan umum di sini adalah mengembalikan status 200 OK untuk segala jenis respons, bahkan ketika terjadi error, dan hanya menyelipkan pesan error di dalam body JSON. Hal ini menyalahi konvensi REST dan membuat klien kesulitan mendeteksi error secara otomatis.

Membuat Operasi CRUD Lengkap

Sekarang mari kita terapkan konvensi di atas ke dalam kode. Kita akan membuat operasi CRUD dasar menggunakan array sederhana di memori untuk menyimpan data. Tentu saja pada aplikasi nyata, kamu akan menggunakan database.

import express from 'express';

const app = express();

// Middleware untuk membaca JSON dari body request
app.use(express.json());

// Data simulasi di memori
const users = [];
let nextId = 1;

// Helper function untuk mencari user berdasarkan ID
const find = (id) => users.find(u => u.id === Number(id));

// Read: Mengambil daftar user
app.get('/api/users', (req, res) => {
  res.json(users);
});

// Read: Mengambil detail satu user
app.get('/api/users/:id', (req, res) => {
  const user = find(req.params.id);

  // Jika user tidak ditemukan, kembalikan 404
  if (!user) return res.sendStatus(404);

  res.json(user);
});

// Create: Membuat user baru
app.post('/api/users', (req, res) => {
  const user = { id: nextId++, ...req.body };
  users.push(user);

  // Kembalikan status 201 (Created) beserta data user
  res.status(201).json(user);
});

// Update: Memperbarui user (sebagian data)
app.patch('/api/users/:id', (req, res) => {
  const user = find(req.params.id);
  if (!user) return res.sendStatus(404);

  // Gabungkan data lama dengan data baru dari req.body
  Object.assign(user, req.body);
  res.json(user);
});

// Delete: Menghapus user
app.delete('/api/users/:id', (req, res) => {
  const user = find(req.params.id);
  if (!user) return res.sendStatus(404);

  const index = users.indexOf(user);
  users.splice(index, 1);

  // Kembalikan 204 (No Content) karena berhasil dihapus
  res.sendStatus(204);
});

export default app;

Dalam contoh di atas, perhatikan bagaimana kita secara konsisten menggunakan pengecekan if (!user) lalu membalas dengan res.sendStatus(404) jika resource tidak ada. Untuk POST, kita secara eksplisit menggunakan res.status(201).json(user). Pada DELETE, fungsi res.sendStatus(204) digunakan karena tidak ada data spesifik yang perlu ditampilkan ulang setelah objek terhapus.

Validasi Input dari Klien

Aturan emas dalam pengembangan server web adalah jangan pernah percaya pada input dari klien. Semua data yang masuk dari luar harus divalidasi dengan ketat sebelum diproses atau disimpan ke database.

Kita bisa menggunakan pustaka pihak ketiga seperti Zod, Joi, atau express-validator untuk mendefinisikan skema data yang valid. Zod cukup populer karena integrasinya yang baik dengan TypeScript. Kita bisa membuat sebuah middleware reusable bernama validate() untuk memvalidasi body request pada route mana pun.

Berikut adalah contoh penerapan validasi dengan Zod:

// Mengimpor Zod
import { z } from 'zod';

// Mendefinisikan skema
const UserSchema = z.object({
  name: z.string().min(2),
  email: z.email(),
  age: z.number().int().positive().optional(),
});

// Middleware validasi reusable
export const validate = (schema) => (req, res, next) => {
  const result = schema.safeParse(req.body);

  // Jika gagal validasi
  if (!result.success) {
    return res.status(422).json({ errors: result.error.issues });
  }

  // Jika berhasil, timpa body dengan data yang sudah "bersih"
  req.body = result.data;
  next();
};

// Cara menggunakan middleware pada route POST
app.post('/api/users', validate(UserSchema), (req, res) => {
    // Hanya akan dieksekusi jika validasi lulus
    // req.body dijamin sesuai skema
});

Pada kode validasi di atas, jika req.body tidak lolos uji (misalnya nama terlalu pendek, atau email tidak valid formatnya), middleware akan langsung menghentikan proses request dan mengembalikan status code 422 (Unprocessable Entity) beserta daftar error result.error.issues. Jika validasi berhasil, fungsi next() dipanggil agar eksekusi berlanjut ke handler utama.

Menyusun Struktur Folder Proyek

Seiring dengan bertambah besarnya kode aplikasimu, meletakkan semua route dan logika bisnis di dalam satu file app.js akan menjadi sangat tidak efisien dan sulit dipelihara. Express tidak memaksakan struktur spesifik, tetapi ada konvensi umum yang banyak digunakan oleh komunitas.

Struktur yang baik memisahkan kode berdasarkan tanggung jawab (separation of concerns). Berikut gambaran struktur folder standar yang disarankan:

  • src/app.js: File ini bertanggung jawab untuk konfigurasi awal Express.
  • src/server.js: Menjalankan fungsi app.listen() untuk memulai server.
  • src/routes/: Menyimpan file-file yang memetakan URL dan metode HTTP ke controller yang tepat.
  • src/controllers/: Tempat logika request dan response berada. Controller membaca input (req), memanggil fungsi dari service, dan mengirim hasil (res).
  • src/services/: Tempat bersemayamnya logika bisnis inti. Sebisa mungkin, kode di layer ini tidak bergantung secara langsung pada Express (tidak ada req atau res), sehingga mudah dites ulang atau dipindahkan.
  • src/models/: Berisi kode untuk berinteraksi dengan database atau konfigurasi ORM.
  • src/middleware/: Tempat menyimpan middleware kustom seperti autentikasi (auth), validasi input, atau penanganan error (error handler).
  • src/config/: Menyimpan konfigurasi aplikasi atau database.
  • src/tests/: Tempat menyimpan skrip pengujian atau unit test.

Pemisahan fungsi secara spesifik ke dalam routes, controllers, dan services memberikan fleksibilitas tinggi. Misalnya, bila nanti kamu ingin menambahkan akses API melalui pesan antrean (message queue) di samping HTTP, logika bisnismu yang ada di folder services tetap utuh dan bisa langsung digunakan kembali tanpa perlu menulis ulang.

Rangkuman

  • Gunakan nama resource dalam bentuk jamak dan terapkan aksi menggunakan metode HTTP yang relevan seperti GET, POST, PUT/PATCH, dan DELETE.
  • Pastikan respons selalu mengembalikan status code yang akurat untuk setiap aksi CRUD, seperti 201 untuk Create dan 204 untuk aksi Delete.
  • Semua input yang diterima dari klien tidak boleh langsung dipercaya dan wajib melewati tahapan validasi sebelum diproses lebih lanjut.
  • Organisasikan kodemu ke dalam layer yang terpisah dengan membagi tanggung jawab fungsi ke dalam folder spesifik seperti routes, controllers, dan services.

Selesaikan bab ini

  1. 1. Baca materi
  2. 2. Kuis (4 soal)

Cek pemahaman

  1. 1Metode HTTP dan status code mana yang direkomendasikan untuk membuat data resource baru pada REST API?

  2. 2Berdasarkan potongan kode handler Express berikut, status code apa yang paling tepat untuk mengisi bagian yang kosong (titik-titik) ketika resource sukses dihapus?

    app.delete('/api/users/:id', (req, res) => {
      const user = find(req.params.id);
      if (!user) return res.sendStatus(404);
    
      const index = users.indexOf(user);
      users.splice(index, 1);
    
      res.sendStatus(...);
    });
  3. 3Berdasarkan konvensi struktur folder aplikasi, file controller di dalam folder `src/controllers/` sebaiknya berisikan logika bisnis mandiri yang terbebas dari campur tangan fungsi spesifik Express.

  4. 4Jika kamu menggunakan skema Zod dan menemukan bahwa input dari request body tidak sesuai (misal email salah format), status code apa yang idealnya kamu kembalikan melalui blok middleware validasi?