Koneksi Database — Node.js

API yang sebenarnya menyimpan data di database, bukan di memori. Menggunakan SQL dari Express: import Database from "better-sqlite3"; // atau knex, prisma…

Database adalah tempat data disimpan di luar program, jadi datanya tetap ada walaupun server mati. Bayangin papan tulis dan buku catatan di laci. Array catatan kita itu papan tulis: gampang ditulis, tapi dihapus tiap kali kelas selesai. Database itu buku di laci: besok pagi isinya masih sama. API sungguhan menyimpan datanya di database.

🧪 Tebakan cepat: kamu POST catatan Olahraga, lalu menghentikan server dengan Ctrl+C dan menjalankan node app.js lagi. Berapa catatan yang dijawab GET /catatan kalau datanya di array? Kalau di database? Tebak dulu, jawabannya ada di bagian "Array hilang saat server mati".

Array hilang saat server mati

Array hidup di memori proses Node. Waktu proses berhenti, memorinya dibuang, dan node app.js berikutnya mulai lagi dari isi array yang tertulis di kode.

Langkah Data di array Data di database
server jalan 2 catatan 2 catatan
POST Olahraga 3 catatan 3 catatan
Ctrl+C, lalu node app.js 2 catatan, Olahraga hilang 3 catatan, Olahraga masih ada

Array: 2, database: 3. Itu jawaban tebakannya.

SQLite bawaan Node 24: node:sqlite

SQLite adalah database yang disimpan sebagai satu file biasa, tanpa server database terpisah. Node 24 sudah membawanya, jadi gak perlu npm install:

const { DatabaseSync } = require("node:sqlite");

const db = new DatabaseSync("catatan.db");
db.exec(`
  CREATE TABLE IF NOT EXISTS catatan (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    judul TEXT NOT NULL,
    isi TEXT NOT NULL
  )
`);

Di Node 22, node:sqlite masih mencetak ExperimentalWarning: SQLite is an experimental feature and might change at any time. Package better-sqlite3 punya cara pakai yang sama (prepare, get, all, run) untuk versi Node yang lebih lama.

Route yang bicara SQL

app.get("/catatan", (req, res) => {
  const rows = db.prepare("SELECT * FROM catatan").all();
  res.json(rows);
});

app.get("/catatan/:id", (req, res) => {
  const row = db.prepare("SELECT * FROM catatan WHERE id = ?").get(req.params.id);
  if (!row) return res.status(404).json({ error: "Catatan tidak ditemukan" });
  res.json(row);
});

app.post("/catatan", (req, res) => {
  const { judul, isi } = req.body;
  const info = db.prepare("INSERT INTO catatan (judul, isi) VALUES (?, ?)").run(judul, isi);
  const baru = db.prepare("SELECT * FROM catatan WHERE id = ?").get(info.lastInsertRowid);
  res.status(201).json(baru);
});

app.delete("/catatan/:id", (req, res) => {
  const info = db.prepare("DELETE FROM catatan WHERE id = ?").run(req.params.id);
  if (info.changes === 0) return res.status(404).json({ error: "Catatan tidak ditemukan" });
  res.status(204).end();
});

Placeholder ?: nilainya dikirim terpisah

? adalah tempat kosong di SQL. Nilainya dikirim ke database terpisah dari teks SQL-nya, jadi apa pun isinya, dia dianggap data. Bandingkan dengan menempelkan nilai langsung ke teks SQL:

// BAHAYA: nilai dari URL ditempel ke teks SQL
const info = db.prepare(`DELETE FROM catatan WHERE id = ${req.params.id}`).run();
Request SQL yang sampai ke database Hasil
DELETE /catatan/2 DELETE FROM catatan WHERE id = 2 catatan 2 terhapus
DELETE /catatan/0%20OR%201=1 DELETE FROM catatan WHERE id = 0 OR 1=1 semua catatan terhapus, 204
sama, dengan ? DELETE FROM catatan WHERE id = ? + nilai "0 OR 1=1" gak ada yang cocok, 404

Ini namanya SQL injection: input pengguna berubah jadi bagian dari perintah SQL. 1=1 selalu benar, jadi WHERE cocok untuk semua baris. Input yang jujur pun bisa merusak: judul Jum'at membuat VALUES ('Jum'at', ...), dan SQLite menolaknya dengan near "at": syntax error, lalu Express menjawab 500. Dengan ?, Jum'at tersimpan apa adanya.

MySQL dan PostgreSQL: driver dan pool

Untuk database server seperti MySQL, Node butuh driver dari npm. Dengan mysql2:

const mysql = require("mysql2/promise");

const pool = mysql.createPool({ host: "localhost", user: "root", database: "sekolah" });

const [rows] = await pool.execute("SELECT * FROM siswa WHERE id = ?", [id]);

PostgreSQL dengan package pg memakai $1, $2 sebagai pengganti ?.

ORM dan query builder

Banyak proyek gak menulis SQL sendiri. ORM (Object Relational Mapper) seperti Prisma dan Sequelize membuat tabel terasa seperti object JavaScript: prisma.catatan.findMany(). Query builder seperti Knex menyusun SQL lewat function: knex("catatan").where({ id }). Ketiganya tetap memakai placeholder di belakang layar.

Di latihan: database tiruan dari array

Latihannya memakai array products sebagai pengganti tabel. getAllProducts() sudah ada. Isi insertProduct(name, price): buat { id: nextId++, name, price }, push ke products, lalu return produknya, seperti INSERT yang diikuti SELECT baris barunya. Kode awal mencetak:

Semua produk: 2
INSERT: {"id":3,"name":"Mouse","price":150000}
Setelah insert: 3

⚠️ Jebakan yang sering ketemu

TL;DR: Array hilang tiap server restart, database tetap. Node 24 punya SQLite bawaan: const { DatabaseSync } = require("node:sqlite"), new DatabaseSync("catatan.db"), CREATE TABLE IF NOT EXISTS, lalu db.prepare(sql).all(), .get(), .run() (yang mengembalikan changes dan lastInsertRowid). Nilai dari request selalu lewat placeholder ?, jangan ditempel ke teks SQL: id = 0 OR 1=1 yang ditempel menghapus semua baris (SQL injection). MySQL: mysql.createPool({...}) lalu const [rows] = await pool.execute("... WHERE id = ?", [id]). ORM populer: Prisma dan Sequelize, query builder: Knex.

Yang akan kamu pelajari