Studi Kasus #4: Aplikasi Wisata Indonesia – Membuat API untuk Kategori (Categories)
Halo, calon full-stack developer!
Di Studi Kasus #3, kita sudah membuat server Express dan koneksi ke database. Kita juga sempat membuat contoh API untuk tabel categories. Sekarang, kita akan membahasnya lebih mendalam: membuat endpoint CRUD (Create, Read, Update, Delete) untuk kategori. Kategori ini akan digunakan untuk mengelompokkan tempat wisata, penginapan, dan transportasi.
Dengan API ini, kita bisa:
- Melihat semua kategori
- Melihat detail kategori berdasarkan ID
- Menambah kategori baru
- Mengubah data kategori
- Menghapus kategori
Kita akan menguji semua endpoint menggunakan Postman untuk memastikan semuanya berjalan dengan baik. Yuk, kita mulai! 🚀
📁 Struktur Folder dan File yang Digunakan
Sebelum mulai, pastikan struktur folder backend kamu seperti ini:
backend/
├── config/
│ └── db.js
├── controllers/
│ └── categoryController.js
├── routes/
│ └── categoryRoutes.js
├── .env
├── app.js
└── package.json
File-file ini sudah kita buat di tutorial sebelumnya. Sekarang kita akan memeriksa dan memastikan kode di dalamnya sudah benar.
Skema Tabel Categories (Review)
Ingat kembali tabel categories yang kita buat di Studi Kasus #1:
CREATE TABLE categories (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(100) NOT NULL,
slug VARCHAR(100) NOT NULL UNIQUE,
description TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
id– primary key, auto increment.name– nama kategori (misal: "Wisata Alam").slug– versi URL-friendly (misal: "wisata-alam"), harus unik.description– deskripsi opsional.created_at– otomatis diisi saat record dibuat.
Kode Controller (categoryController.js)
Buka file controllers/categoryController.js. Kita sudah menulis beberapa fungsi di tutorial sebelumnya. Mari kita lihat kembali dan pastikan tidak ada kesalahan.
const db = require('../config/db');
// GET semua kategori
const getAllCategories = async (req, res) => {
try {
const [rows] = await db.query('SELECT * FROM categories ORDER BY id DESC');
res.json(rows);
} catch (err) {
console.error(err);
res.status(500).json({ error: 'Gagal mengambil data kategori' });
}
};
// GET satu kategori berdasarkan ID
const getCategoryById = async (req, res) => {
const { id } = req.params;
try {
const [rows] = await db.query('SELECT * FROM categories WHERE id = ?', [id]);
if (rows.length === 0) {
return res.status(404).json({ error: 'Kategori tidak ditemukan' });
}
res.json(rows[0]);
} catch (err) {
console.error(err);
res.status(500).json({ error: 'Gagal mengambil data kategori' });
}
};
// POST tambah kategori baru
const createCategory = async (req, res) => {
const { name, slug, description } = req.body;
if (!name || !slug) {
return res.status(400).json({ error: 'Nama dan slug harus diisi' });
}
try {
const [result] = await db.query(
'INSERT INTO categories (name, slug, description) VALUES (?, ?, ?)',
[name, slug, description]
);
res.status(201).json({ id: result.insertId, name, slug, description });
} catch (err) {
console.error(err);
// Cek error duplikat slug
if (err.code === 'ER_DUP_ENTRY') {
return res.status(400).json({ error: 'Slug sudah digunakan, gunakan slug lain' });
}
res.status(500).json({ error: 'Gagal menambah kategori' });
}
};
// PUT update kategori
const updateCategory = async (req, res) => {
const { id } = req.params;
const { name, slug, description } = req.body;
try {
const [result] = await db.query(
'UPDATE categories SET name = ?, slug = ?, description = ? WHERE id = ?',
[name, slug, description, id]
);
if (result.affectedRows === 0) {
return res.status(404).json({ error: 'Kategori tidak ditemukan' });
}
res.json({ message: 'Kategori berhasil diupdate' });
} catch (err) {
console.error(err);
if (err.code === 'ER_DUP_ENTRY') {
return res.status(400).json({ error: 'Slug sudah digunakan, gunakan slug lain' });
}
res.status(500).json({ error: 'Gagal mengupdate kategori' });
}
};
// DELETE hapus kategori
const deleteCategory = async (req, res) => {
const { id } = req.params;
try {
const [result] = await db.query('DELETE FROM categories WHERE id = ?', [id]);
if (result.affectedRows === 0) {
return res.status(404).json({ error: 'Kategori tidak ditemukan' });
}
res.json({ message: 'Kategori berhasil dihapus' });
} catch (err) {
console.error(err);
// Jika ada foreign key constraint, kita bisa tangani
if (err.code === 'ER_ROW_IS_REFERENCED_2') {
return res.status(400).json({ error: 'Kategori tidak bisa dihapus karena masih digunakan' });
}
res.status(500).json({ error: 'Gagal menghapus kategori' });
}
};
module.exports = {
getAllCategories,
getCategoryById,
createCategory,
updateCategory,
deleteCategory
};
Penjelasan tambahan:
- Kita menambahkan penanganan error khusus untuk duplikasi slug (
ER_DUP_ENTRY) dan foreign key constraint (ER_ROW_IS_REFERENCED_2). Ini akan memberikan pesan error yang lebih jelas. - Pada
createCategory, kita mengembalikan data yang baru dibuat beserta ID-nya. - Pada
updateCategory, kita hanya mengirim pesan sukses karena data yang diupdate sudah dikirim dari client.
Kode Routes (categoryRoutes.js)
File routes/categoryRoutes.js bertugas menghubungkan endpoint dengan fungsi controller.
const express = require('express');
const router = express.Router();
const categoryController = require('../controllers/categoryController');
// GET /api/categories
router.get('/', categoryController.getAllCategories);
// GET /api/categories/:id
router.get('/:id', categoryController.getCategoryById);
// POST /api/categories
router.post('/', categoryController.createCategory);
// PUT /api/categories/:id
router.put('/:id', categoryController.updateCategory);
// DELETE /api/categories/:id
router.delete('/:id', categoryController.deleteCategory);
module.exports = router;
Di app.js, kita sudah mendaftarkan route ini dengan prefix /api/categories:
const categoryRoutes = require('./routes/categoryRoutes');
app.use('/api/categories', categoryRoutes);
Menguji API dengan Postman
Sekarang saatnya menguji apakah API kita bekerja dengan baik. Pastikan server sudah berjalan dengan perintah node app.js di folder backend. Buka Postman dan ikuti langkah-langkah berikut.
1. GET Semua Kategori
- Method: GET
- URL:
http://localhost:3000/api/categories - Klik Send.
Respons yang diharapkan: array JSON (mungkin kosong atau berisi data jika sudah ada). Contoh:
[
{
"id": 1,
"name": "Wisata Alam",
"slug": "wisata-alam",
"description": "Tempat wisata alam seperti pantai, gunung, dll.",
"created_at": "2026-03-02T12:00:00.000Z"
}
]
2. GET Kategori Berdasarkan ID
- Method: GET
- URL:
http://localhost:3000/api/categories/1(ganti 1 dengan ID yang ada) - Send.
Respons: satu objek kategori. Jika ID tidak ditemukan, akan mendapat status 404 dengan pesan error.
3. POST Tambah Kategori Baru
- Method: POST
- URL:
http://localhost:3000/api/categories - Headers:
Content-Type: application/json - Body (raw JSON):
{ "name": "Wisata Budaya", "slug": "wisata-budaya", "description": "Tempat wisata yang menampilkan budaya dan adat istiadat." } - Send.
Respons sukses (status 201):
{
"id": 2,
"name": "Wisata Budaya",
"slug": "wisata-budaya",
"description": "Tempat wisata yang menampilkan budaya dan adat istiadat."
}
Coba juga kirim data dengan slug yang sama untuk melihat error duplikasi.
4. PUT Update Kategori
- Method: PUT
- URL:
http://localhost:3000/api/categories/2(ID yang baru ditambahkan) - Headers:
Content-Type: application/json - Body:
{ "name": "Wisata Budaya dan Tradisi", "slug": "wisata-budaya", "description": "Mengenal budaya dan tradisi lokal." } - Send.
Respons sukses:
{
"message": "Kategori berhasil diupdate"
}
5. DELETE Hapus Kategori
- Method: DELETE
- URL:
http://localhost:3000/api/categories/2 - Send.
Respons sukses:
{
"message": "Kategori berhasil dihapus"
}
Coba hapus kategori yang masih digunakan oleh tabel lain (misal destinations) untuk melihat error constraint.
Screenshot Hasil Pengujian (Contoh)
Berikut contoh tampilan Postman saat menguji endpoint GET all:
(Sisipkan gambar tangkapan layar di sini jika diperlukan)
Penjelasan Error Handling
- 400 Bad Request: Digunakan ketika input tidak valid (misal nama atau slug kosong).
- 404 Not Found: Ketika ID yang diminta tidak ditemukan di database.
- 500 Internal Server Error: Kesalahan server yang tidak terduga (misal koneksi database putus).
- ER_DUP_ENTRY: Dikembalikan sebagai 400 dengan pesan slug sudah digunakan.
- ER_ROW_IS_REFERENCED_2: Terjadi jika kategori masih dipakai di tabel lain, kita tangani dengan pesan yang sesuai.
Langkah Selanjutnya
Dengan selesainya API kategori, kita sekarang bisa melanjutkan ke Studi Kasus #5: Membuat API untuk Tempat Wisata (Destinations). Kita akan membuat CRUD untuk destinasi yang terhubung dengan kategori, serta mulai menggunakan multer untuk upload gambar cover.
Pastikan semua endpoint kategori berfungsi dengan baik sebelum lanjut. Jika ada error, periksa kembali kode dan koneksi database.
Sampai jumpa di tutorial berikutnya!
Ditulis dengan ❤️ untuk para pengembang yang ingin membangun aplikasi wisata.