Mengapa desain basis data dan desain API harus dilakukan bersama?

Dalam dunia pengembangan aplikasi, keselarasan antara desain basis data dan desain API sangat penting. Artikel ini menjelajahi mengapa kedua elemen ini harus dikembangkan secara bersamaan, menggunakan perumpamaan sederhana dan contoh praktis untuk menggambarkan kesalingterkaitan mereka.

Kamu sedang membuat sandwich selai kacang dan jeli. Kamu memiliki dua slice roti (aplikasi front-end dan server back-end), dan selai kacang dan jeli (database dan API).

Jika kamu tidak menyebarkannya dalam urutan yang benar atau menggunakan jumlah yang salah, sandwich bisa menjadi bencana. Hal yang sama berlaku untuk desain database dan API. Mereka memerlukan harmoni yang sempurna jika kamu ingin aplikasimu berjalan dengan baik.

Mari kita bahas bagaimana desain database dan desain API lebih terhubung daripada yang kamu pikirkan. Saat kamu mengembangkan API, kamu pada dasarnya membangun jembatan untuk mengakses data yang disimpan dalam database. Dan seperti dalam setiap hubungan yang baik, komunikasi adalah kunci.

Mari kita lihat bagaimana kamu bisa menghubungkan desain database dan desain API menggunakan contoh konkret dengan beberapa kode nyata. Kita akan membahas beberapa konsep langkah demi langkah untuk benar-benar memahaminya. Persiapkan diri! Kita akan menyelam ke dalam PB&J pengembangan.

Mengapa Desain Database dan API Harus Bersama?

Skema & Endpoint: Memetakan Databasemu ke Rute API

Yang pertama kita butuhkan adalah skema untuk database. Misalkan kita membuat API untuk platform blogging sederhana. Berikut mungkin seperti apa skema database untuk dua entitas utama: Pengguna dan Postingan.

Desain Database (Skema):

-- Tabel Pengguna
CREATE TABLE users (
  id INT PRIMARY KEY AUTO_INCREMENT,
  username VARCHAR(100) NOT NULL,
  email VARCHAR(255) NOT NULL UNIQUE,
  password_hash VARCHAR(255) NOT NULL,
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- Tabel Postingan
CREATE TABLE posts (
  id INT PRIMARY KEY AUTO_INCREMENT,
  user_id INT NOT NULL,
  title VARCHAR(255) NOT NULL,
  content TEXT NOT NULL,
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  FOREIGN KEY (user_id) REFERENCES users(id)
);

Dalam desain ini:

  • Tabel users menyimpan informasi tentang pengguna.
  • Tabel posts menyimpan postingan blog dan memiliki kunci asing (user_id) yang merujuk ke tabel users (menunjukkan hubungan antara pengguna dan postingannya).

Desain API (Endpoint):

Sekarang, saat mendesain API untuk berinteraksi dengan data ini, kita perlu memaparkan endpoint untuk berinteraksi dengan baik pengguna dan postingan.

  • GET /users/{id} – Mendapatkan pengguna tertentu.
  • POST /users – Membuat pengguna baru.
  • GET /users/{id}/posts – Mendapatkan semua postingan oleh pengguna.
  • POST /posts – Membuat postingan baru.
// Langkah 1: Impor modul yang diperlukan
const express = require('express');  
const app = express();  
const mysql = require('mysql');  // Impor modul MySQL untuk menyambung ke database
const bodyParser = require('body-parser');  

// Langkah 2: Setel koneksi ke database MySQL
const db = mysql.createConnection({
  host: 'localhost',
  user: 'root',  // Nama pengguna MySQL
  password: 'password',  // Kata sandi MySQL
  database: 'blog_db'  // Nama database yang kita gunakan (dalam hal ini, 'blog_db')
});

// Langkah 3: Perangkat tengah untuk mengurai permintaan JSON
app.use(bodyParser.json());  // Ini memungkinkan kita mengakses `req.body` sebagai objek JSON

// Langkah 4: Endpoint untuk mendapatkan detail pengguna berdasarkan ID mereka
// Ketika permintaan dibuat ke '/users/:id', rute ini akan mengambil pengguna dari database berdasarkan ID mereka
app.get('/users/:id', (req, res) => {
  const userId = req.params.id;  // Ambil ID pengguna dari parameter URL

  // Query database untuk mendapatkan pengguna dengan ID yang ditentukan
  db.query('SELECT * FROM users WHERE id = ?', [userId], (err, result) => {
    if (err) {
      // Jika terjadi kesalahan database, kirim respons kesalahan 500
      return res.status(500).send('Kesalahan database');
    }
    if (result.length === 0) {
      // Jika tidak ditemukan pengguna dengan ID yang diberikan, kirim respons 404
      return res.status(404).send('Pengguna tidak ditemukan');
    }

    // Kirim data pengguna sebagai respons JSON
    res.json(result[0]);
  });
});

// Endpoint
// Langkah 5: Endpoint untuk mendapatkan semua postingan oleh pengguna tertentu
// Rute ini mengembalikan semua postingan yang ditulis oleh pengguna, menggunakan ID pengguna mereka
app.get('/users/:id/posts', (req, res) => {
  const userId = req.params.id;  // Ekstrak ID pengguna dari URL

  // Query database untuk mendapatkan semua postingan di mana user_id sesuai dengan ID pengguna yang ditentukan
  db.query('SELECT * FROM posts WHERE user_id = ?', [userId], (err, result) => {
    if (err) {
      // Tangani kesalahan database dengan mengirim respons kesalahan 500
      return res.status(500).send('Kesalahan database');
    }

    // Kirim daftar postingan sebagai respons JSON
    res.json(result);
  });
});

// Langkah 6: Endpoint untuk membuat postingan baru
// Rute ini menerima data dari body permintaan untuk membuat postingan baru
app.post('/posts', (req, res) => {
  const { user_id, title, content } = req.body;  // Ekstrak data postingan dari body permintaan

  // Query untuk menyisipkan postingan baru ke tabel 'posts'
  db.query(
    'INSERT INTO posts (user_id, title, content) VALUES (?, ?, ?)', 
    [user_id, title, content],  // Nilai yang akan disisipkan ke tabel
    (err, result) => {
      if (err) {
        // Jika terjadi kesalahan database, kirim respons kesalahan 500
        return res.status(500).send('Kesalahan database');
      }

      // Kirim respons yang mengkonfirmasi pembuatan postingan dengan ID postingan baru
      res.status(201).send(`Postingan dibuat dengan ID: ${result.insertId}`);
    }
  );
});

// Langkah 7: Mulai server dan dengarkan pada port 3000
app.listen(3000, () => {
  // Log ke konsol ketika server berhasil dimulai
  console.log('API berjalan di http://localhost:3000');
});

Apa yang Kita Lakukan Di Sini:

1. /users/:id – Mendapatkan Pengguna oleh ID

  • Endpoint ini mengambil detail pengguna tertentu dari database menggunakan id mereka.
  • Kueri SQL SELECT * FROM users WHERE id = ? mengambil informasi pengguna dari tabel users.
// Mendapatkan pengguna oleh ID
app.get('/users/:id', (req, res) => {
  const userId = req.params.id;  // Ekstrak ID pengguna dari parameter URL

  // Query database untuk mendapatkan pengguna dengan ID yang ditentukan
  db.query('SELECT * FROM users WHERE id = ?', [userId], (err, result) => {
    if (err) {
      // Tangani kesalahan database dengan mengirim respons kesalahan 500
      return res.status(500).send('Kesalahan database');
    }
    if (result.length === 0) {
      // Jika tidak ditemukan pengguna dengan ID yang diberikan, kirim respons 404
      return res.status(404).send('Pengguna tidak ditemukan');
    }

    // Kirim data pengguna sebagai respons JSON
    res.json(result[0]);
  });
});

Query Database:

  • SQL: SELECT * FROM users WHERE id = ?
  • Ini mengambil semua detail (*) dari pengguna dengan id yang diberikan.
  • Tanda ? adalah placeholder untuk nilai id sebenarnya yang berasal dari req.params.id (parameter URL).

2. /users/:id/posts – Mendapatkan Semua Postingan oleh Pengguna

  • Endpoint ini mengambil semua postingan yang dimiliki oleh pengguna tertentu, menggunakan kunci asing user_id di tabel posts.
  • Kueri SQL SELECT * FROM posts WHERE user_id = ? mengambil postingan yang terkait dengan user_id.
// Mendapatkan postingan oleh pengguna
app.get('/users/:id/posts', (req, res) => {
  const userId = req.params.id;  // Ekstrak ID pengguna dari URL

  // Query database untuk mendapatkan semua postingan oleh pengguna yang ditentukan
  db.query('SELECT * FROM posts WHERE user_id = ?', [userId], (err, result) => {
    if (err) {
      // Tangani kesalahan database dengan mengirim respons kesalahan 500
      return res.status(500).send('Kesalahan database');
    }

    // Kirim daftar postingan sebagai respons JSON
    res.json(result);
  });
});

Query Database:

  • SQL: SELECT * FROM posts WHERE user_id = ?
  • Ini mengambil semua postingan dari tabel posts di mana user_id sesuai dengan id dari tabel users.
  • user_id dalam tabel posts adalah kunci asing yang menghubungkan postingan ke pengguna tertentu.

3. POST /posts – Membuat Postingan Baru

  • Endpoint ini membuat postingan blog baru. Memerlukan user_id, title, dan content dari body permintaan, lalu menyisipkan data tersebut ke tabel posts di database.
  • Kueri SQL INSERT INTO posts (user_id, title, content) VALUES (?, ?, ?) digunakan untuk menyisipkan postingan ke database.
// Membuat postingan baru
app.post('/posts', (req, res) => {
  const { user_id, title, content } = req.body;  // Ekstrak data postingan dari body permintaan

  // Query untuk menyisipkan postingan baru ke tabel 'posts'
  db.query(
    'INSERT INTO posts (user_id, title, content) VALUES (?, ?, ?)', 
    [user_id, title, content],  // Nilai yang akan disisipkan ke tabel
    (err, result) => {
      if (err) {
        // Jika terjadi kesalahan database, kirim respons kesalahan 500
        return res.status(500).send('Kesalahan database');
      }

      // Kirim respons yang mengkonfirmasi pembuatan postingan dengan ID postingan baru
      res.status(201).send(`Postingan dibuat dengan ID: ${result.insertId}`);
    }
  );
});

Query Database:

  • SQL: INSERT INTO posts (user_id, title, content) VALUES (?, ?, ?)
  • Ini menyisipkan data postingan baru ke tabel posts, menghubungkannya dengan pengguna melalui user_id.
  • Placeholder ? diganti dengan nilai sebenarnya (user_id, title, content) dari req.body.

Recap: Interaksi API dan Database

  • /users/:id: Mengambil informasi pengguna berdasarkan ID. Menggunakan kueri SELECT untuk mengambil data dari tabel users berdasarkan ID pengguna.
  • /users/:id/posts: Mengambil semua postingan oleh pengguna tertentu. Menggunakan kunci asing user_id untuk menemukan postingan yang terkait dengan pengguna di tabel posts.
  • POST /posts: Membuat postingan baru dan menghubungkannya dengan pengguna menggunakan user_id. Menjalankan kueri INSERT untuk menyimpan postingan baru di tabel posts.

Endpoint API Express.js berinteraksi langsung dengan database MySQL dengan menanyakan dan memanipulasi data menggunakan kueri SQL. Hubungan antara API dan database sangat terpadu, memungkinkan API untuk menjalankan operasi CRUD dengan lancar.

Dengan mengikuti struktur ini, kita memetakan tabel database langsung ke endpoint API. Semua tentang menyamakan cara database menyimpan data dengan cara API menyajikan data.

Hubungan: API Harus Mencerminkan Database

Di database, kita memiliki hubungan satu-ke-banyak: satu pengguna bisa memiliki banyak postingan. user_id di tabel posts mewakili kunci asing yang menghubungkan postingan ke pengguna tertentu.

Untuk mencerminkan hubungan ini dalam API, kita perlu menggabungkan tabel posts dan users untuk mengambil data yang relevan, seperti nama pengguna yang membuat setiap postingan.

Contoh API dengan Hubungan (Menggunakan JOIN)

Ambil Postingan dengan Informasi Pengguna (Menggunakan JOIN)

// Mendapatkan semua postingan untuk pengguna, termasuk nama pengguna mereka
app.get('/users/:id/posts', (req, res) => {
  const userId = req.params.id;
  db.query(
    'SELECT posts.*, users.username FROM posts JOIN users ON posts.user_id = users.id WHERE posts.user_id = ?',
    [userId],
    (err, result) => {
      if (err) return res.status(500).send('Kesalahan database');
      res.json(result); // Mengembalikan postingan beserta nama pengguna dari tabel users
    }
  );
});

Penjelasan:

  • Rute: /users/:id/posts
  • Interaksi Database: Kita menggunakan SQL JOIN untuk menggabungkan tabel posts dan users. Kita memilih semua kolom dari tabel posts (posts.*), dan juga memilih kolom username dari tabel users.
  • Respons: API mengembalikan postingan yang terkait dengan pengguna bersama dengan username pengguna yang membuat setiap postingan.

Tipe Data: Pilihlah Bijak!

Saat merancang API, kamu harus mempertimbangkan bagaimana tipe data di database akan dikembalikan dan diformat. Sebagai contoh, kolom created_at baik di tabel users dan posts mungkin disimpan sebagai timestamp di database. Namun, mengirimkan timestamp mentah ke klien mungkin tidak ramah pengguna.

Kita perlu memformat timestamp ini menjadi format yang bisa dibaca manusia sebelum mengembalikannya.

Contoh API dengan Penyusunan Ulang Timestamp

Format Lapangan created_at (untuk Kemudahan Membaca)

// Mendapatkan semua postingan untuk pengguna, dengan timestamp created_at yang diformat
app.get('/users/:id/posts', (req, res) => {
  const userId = req.params.id;
  db.query('SELECT * FROM posts WHERE user_id = ?', [userId], (err, result) => {
    if (err) return res.status(500).send('Kesalahan database');
    
    // Format timestamp created_at menjadi format yang mudah dibaca
    result.forEach(post => {
      post.created_at = new Date(post.created_at).toLocaleString(); // Format timestamp
    });
    
    // Kirim postingan dengan timestamp yang diformat
    res.json(result);
  });
});

Penjelasan:

  • Rute: /users/:id/posts
  • Interaksi Database: Kita menanyakan tabel posts untuk mendapatkan semua postingan untuk pengguna.
  • Respons: Sebelum mengembalikan postingan, kita mengulangi hasil dan memformat timestamp created_at menggunakan metode Date().toLocaleString() JavaScript. Ini memastikan timestamp mudah dibaca (misalnya, "23 April 2025, 16:30").

Sama seperti membuat sandwich yang baik, kamu harus menyeimbangkan dengan benar saat mendesain database dan API. Berikut ringkasannya:

  1. Skema = Gambaran Kerja: Skema database mu secara langsung mempengaruhi endpoint API mu.
  2. Hubungan = Koneksi: Pastikan API mu menghormati hubungan antar entitas (misalnya, user_id dan posts).
  3. Tipe Data = Padankan: Respons API harus sesuai dengan desain database mu, seperti memformat timestamp dengan benar.

Membangun API bisa menjadi tantangan, terutama ketika kamu harus memastikan semuanya tersinkronisasi β€” dari database SQL hingga desain API. Kamu tidak ingin API mu mengirim data dalam format yang tidak diharapkan, dan kamu pasti tidak menginginkan nama yang tidak cocok antara tabel database mu dan endpoint API. Inilah tempat EchoAPI masuk.

Bagaimana EchoAPI Membantu Sinkronisasi SQL dan Desain API: Konsistensi adalah Kunci

Mengapa Desain Database dan API Harus Bersama?2

EchoAPI adalah alat yang kuat yang dapat membantu mengisi celah antara database dan API mu, memastikan mereka berbicara dalam bahasa yang sama. Ini mengautomasi banyak aspek dari proses desain, memastikan bahwa skema database mu dan endpoint API tetap sinkron.

EchoAPI membantu menjaga desain API dan database mu sejalan. Dengan membaca langsung dari skema SQL mu, ia dapat secara otomatis membentuk endpoint API mu untuk mencocokkan struktur yang telah kamu tentukan. Ini berarti kurangnya ketidak一致an, sinkronisasi yang lebih baik antara backend dan frontend, dan jauh lebih sedikit waktu yang dihabiskan untuk meragukan nama bidang atau memperbaiki tipe data yang tidak cocok.

Dengan EchoAPI, SQL mu dan API tumbuh bersama secara alami β€” tanpa usaha tambahan yang diperlukan, hanya desain yang bersih dan dapat diprediksi dari awal.

Jika kamu ingin belajar cara menggunakan EchoAPI untuk mengoptimalkan proses desain API mu dan menjamin sinkronisasi antara database dan API mu, ikuti terus. Kita akan menyelami bagaimana menerapkan alat ini dan membimbingmu melalui contoh dunia nyata!