Lewati ke konten
Puguh Sulistyo Pambudi Freelance Web Developer | Guru SMK
  • Beranda
  • Profil Saya
  • Belajar
    • Vibecoding
    • Kecerdasan Buatan
    • Koding
    • Python
    • WordPress
    • Internet of Things
Cari

Tekan Enter untuk melihat semua hasil · Esc untuk menutup

Beranda / Koding / Belajar Hono Framework #5: Update dan Hapus Data Siswa (PUT & DELETE)
Koding

Belajar Hono Framework #5: Update dan Hapus Data Siswa (PUT & DELETE)

Puguh Sulistyo Pambudi
Puguh Sulistyo Pambudi Mei 19, 2026 · 7 menit baca · 0
Belajar Hono Framework #5: Update dan Hapus Data Siswa (PUT & DELETE)
Daftar Isi
  1. 1 Apa Bedanya PUT dan PATCH?
  2. 2 Membuat Endpoint PUT (Update Data Siswa)
  3. 3 Testing Endpoint PUT
  4. 4 Membuat Endpoint DELETE (Hapus Data Siswa)
  5. 5 Testing Endpoint DELETE
  6. 6 Recap: Semua Endpoint yang Sudah Dibuat
  7. 7 File Lengkap src/routes/siswa.ts
  8. 8 Langkah Selanjutnya

Kita sudah bisa baca data (GET) dan tambah data baru (POST). Tapi namanya CRUD belum komplit kalau belum bisa update dan hapus! Di part ini, kita akan membuat dua endpoint terakhir — PUT untuk mengubah data dan DELETE untuk menghapusnya. Setelah ini, API siswa kita sudah benar-benar full CRUD!

Apa Bedanya PUT dan PATCH?

Sebelum nulis kode, ada satu konsep yang sering bikin bingung pemula: apa bedanya PUT dan PATCH?

  • PUT = Update seluruh data. Kamu harus kirim semua field, bahkan yang tidak berubah sekalipun.
  • PATCH = Update sebagian data. Kamu cukup kirim field yang mau diubah saja.

Untuk kasus kita, kita akan pakai PUT dulu karena lebih simpel dan cocok untuk pemula. Nanti di proyek nyata, kamu bisa eksplorasi PATCH sendiri.

Membuat Endpoint PUT (Update Data Siswa)

Tambahkan kode berikut di src/routes/siswa.ts:

// PUT /api/siswa/:id — Update data siswa
siswa.put('/:id', async (c) => {
  const id = Number(c.req.param('id'))

  // Validasi ID
  if (isNaN(id) || id <= 0) {
    return c.json({ success: false, message: 'ID tidak valid.' }, 400)
  }

  // Ambil data dari body
  const body = await c.req.json()
  const { nama, nis, kelas, jurusan, email } = body

  // Validasi: semua field wajib ada
  if (!nama || !nis || !kelas || !jurusan || !email) {
    return c.json({
      success: false,
      message: 'Semua field wajib diisi: nama, nis, kelas, jurusan, email'
    }, 400)
  }

  // Validasi format email
  if (!isEmailValid(email)) {
    return c.json({ success: false, message: 'Format email tidak valid.' }, 400)
  }

  try {
    const result = await sql`
      UPDATE siswa
      SET 
        nama    = ${nama},
        nis     = ${nis},
        kelas   = ${kelas},
        jurusan = ${jurusan},
        email   = ${email}
      WHERE id = ${id}
      RETURNING id, nama, nis, kelas, jurusan, email, created_at
    `

    // Kalau tidak ada baris yang terupdate, berarti ID tidak ditemukan
    if (result.length === 0) {
      return c.json({
        success: false,
        message: `Siswa dengan ID ${id} tidak ditemukan.`
      }, 404)
    }

    return c.json({
      success: true,
      message: 'Data siswa berhasil diperbarui!',
      data: result[0]
    })

  } catch (error: any) {
    if (error.code === '23505') {
      const field = error.constraint_name?.includes('nis') ? 'NIS' : 'Email'
      return c.json({
        success: false,
        message: `${field} sudah digunakan oleh siswa lain.`
      }, 409)
    }
    console.error('Error saat update siswa:', error)
    return c.json({ success: false, message: 'Kesalahan server.' }, 500)
  }
})

Trik penting di sini adalah RETURNING di query UPDATE — persis seperti di INSERT. Ini cara kita tahu apakah ada data yang benar-benar terupdate. Kalau result.length === 0, berarti tidak ada baris dengan ID tersebut di database.

Testing Endpoint PUT

Tes dengan curl:

curl -X PUT http://localhost:3000/api/siswa/1 \
  -H "Content-Type: application/json" \
  -d '{
    "nama": "Budi Santoso Wijaya",
    "nis": "2024001",
    "kelas": "XII RPL 2",
    "jurusan": "Rekayasa Perangkat Lunak",
    "email": "[email protected]"
  }'

Kita ubah nama, kelas, dan email Budi. Kalau berhasil, respons akan menampilkan data terbaru. Coba juga update siswa dengan ID yang tidak ada (misalnya ID 999) untuk memastikan error 404 berjalan dengan benar!

Membuat Endpoint DELETE (Hapus Data Siswa)

Endpoint DELETE lebih simpel dari PUT karena kita tidak perlu mengirim body data — cukup ID saja:

// DELETE /api/siswa/:id — Hapus siswa
siswa.delete('/:id', async (c) => {
  const id = Number(c.req.param('id'))

  // Validasi ID
  if (isNaN(id) || id <= 0) {
    return c.json({ success: false, message: 'ID tidak valid.' }, 400)
  }

  // Ambil data dulu sebelum dihapus (untuk respons konfirmasi)
  const existing = await sql`
    SELECT id, nama, nis FROM siswa WHERE id = ${id}
  `

  if (existing.length === 0) {
    return c.json({
      success: false,
      message: `Siswa dengan ID ${id} tidak ditemukan.`
    }, 404)
  }

  // Hapus dari database
  await sql`DELETE FROM siswa WHERE id = ${id}`

  return c.json({
    success: true,
    message: `Data siswa "${existing[0].nama}" (NIS: ${existing[0].nis}) berhasil dihapus.`
  })
})

Kenapa kita query dulu sebelum delete? Supaya respons konfirmasinya lebih informatif — menyebutkan nama dan NIS siswa yang dihapus, bukan sekadar “berhasil dihapus” tanpa konteks.

Testing Endpoint DELETE

# Hapus siswa ID 4
curl -X DELETE http://localhost:3000/api/siswa/4

# Coba hapus lagi (harusnya dapat 404)
curl -X DELETE http://localhost:3000/api/siswa/4

Request pertama berhasil, request kedua akan mengembalikan 404 karena data sudah tidak ada. Logis dan benar!

Recap: Semua Endpoint yang Sudah Dibuat

Selamat! API CRUD data siswa kita sekarang sudah lengkap! Ini semua endpoint yang sudah berjalan:

MethodEndpointFungsiStatus Code
GET/api/siswaAmbil semua siswa200
GET/api/siswa?nama=xCari siswa by nama200
GET/api/siswa/:idDetail satu siswa200 / 404
POST/api/siswaTambah siswa baru201 / 400 / 409
PUT/api/siswa/:idUpdate data siswa200 / 400 / 404 / 409
DELETE/api/siswa/:idHapus siswa200 / 404

File Lengkap src/routes/siswa.ts

Ini versi lengkap dan bersih dari seluruh file routing kita:

import { Hono } from 'hono'
import sql from '../db'

const siswa = new Hono()

const isEmailValid = (email: string): boolean => {
  return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)
}

// GET / — Ambil semua siswa (dengan filter opsional)
siswa.get('/', async (c) => {
  const cariNama = c.req.query('nama')
  const cariKelas = c.req.query('kelas')
  let data
  if (cariNama) {
    data = await sql`SELECT * FROM siswa WHERE LOWER(nama) LIKE ${'%' + cariNama.toLowerCase() + '%'} ORDER BY nama`
  } else if (cariKelas) {
    data = await sql`SELECT * FROM siswa WHERE LOWER(kelas) LIKE ${'%' + cariKelas.toLowerCase() + '%'} ORDER BY nama`
  } else {
    data = await sql`SELECT * FROM siswa ORDER BY created_at DESC`
  }
  return c.json({ success: true, total: data.length, data })
})

// GET /:id — Detail satu siswa
siswa.get('/:id', async (c) => {
  const id = Number(c.req.param('id'))
  if (isNaN(id) || id <= 0) return c.json({ success: false, message: 'ID tidak valid.' }, 400)
  const result = await sql`SELECT * FROM siswa WHERE id = ${id}`
  if (result.length === 0) return c.json({ success: false, message: `Siswa ID ${id} tidak ditemukan.` }, 404)
  return c.json({ success: true, data: result[0] })
})

// POST / — Tambah siswa baru
siswa.post('/', async (c) => {
  const { nama, nis, kelas, jurusan, email } = await c.req.json()
  if (!nama || !nis || !kelas || !jurusan || !email) return c.json({ success: false, message: 'Semua field wajib diisi.' }, 400)
  if (!isEmailValid(email)) return c.json({ success: false, message: 'Format email tidak valid.' }, 400)
  try {
    const data = await sql`INSERT INTO siswa (nama, nis, kelas, jurusan, email) VALUES (${nama}, ${nis}, ${kelas}, ${jurusan}, ${email}) RETURNING *`
    return c.json({ success: true, message: 'Siswa berhasil ditambahkan!', data: data[0] }, 201)
  } catch (e: any) {
    if (e.code === '23505') return c.json({ success: false, message: 'NIS atau email sudah terdaftar.' }, 409)
    return c.json({ success: false, message: 'Kesalahan server.' }, 500)
  }
})

// PUT /:id — Update siswa
siswa.put('/:id', async (c) => {
  const id = Number(c.req.param('id'))
  if (isNaN(id) || id <= 0) return c.json({ success: false, message: 'ID tidak valid.' }, 400)
  const { nama, nis, kelas, jurusan, email } = await c.req.json()
  if (!nama || !nis || !kelas || !jurusan || !email) return c.json({ success: false, message: 'Semua field wajib diisi.' }, 400)
  if (!isEmailValid(email)) return c.json({ success: false, message: 'Format email tidak valid.' }, 400)
  try {
    const result = await sql`UPDATE siswa SET nama=${nama}, nis=${nis}, kelas=${kelas}, jurusan=${jurusan}, email=${email} WHERE id=${id} RETURNING *`
    if (result.length === 0) return c.json({ success: false, message: `Siswa ID ${id} tidak ditemukan.` }, 404)
    return c.json({ success: true, message: 'Data siswa diperbarui!', data: result[0] })
  } catch (e: any) {
    if (e.code === '23505') return c.json({ success: false, message: 'NIS atau email sudah digunakan siswa lain.' }, 409)
    return c.json({ success: false, message: 'Kesalahan server.' }, 500)
  }
})

// DELETE /:id — Hapus siswa
siswa.delete('/:id', async (c) => {
  const id = Number(c.req.param('id'))
  if (isNaN(id) || id <= 0) return c.json({ success: false, message: 'ID tidak valid.' }, 400)
  const existing = await sql`SELECT id, nama, nis FROM siswa WHERE id = ${id}`
  if (existing.length === 0) return c.json({ success: false, message: `Siswa ID ${id} tidak ditemukan.` }, 404)
  await sql`DELETE FROM siswa WHERE id = ${id}`
  return c.json({ success: true, message: `Siswa "${existing[0].nama}" (NIS: ${existing[0].nis}) berhasil dihapus.` })
})

export default siswa

Langkah Selanjutnya

Di Part #6 (terakhir!), kita akan upgrade validasi kita menggunakan library Zod yang jauh lebih powerful, dan membuat global error handler supaya aplikasi kita tidak pernah menampilkan error yang jelek ke pengguna!

Praktekkan semua endpoint PUT dan DELETE-nya ya — pastikan semua skenario berhasil dan error-nya! 💪

#Hono #postgresql
Puguh Sulistyo Pambudi

Ditulis oleh

Puguh Sulistyo Pambudi
Bagikan
← Sebelumnya Belajar Hono Framework #4: Tambah Data Siswa Baru dengan POST Request
Selanjutnya → Belajar Hono Framework #6: Validasi Input dengan Zod dan Global Error Handler

Tinggalkan Balasan Batalkan balasan

Alamat email Anda tidak akan dipublikasikan. Ruas yang wajib ditandai *

Artikel Populer

  1. Anatomi Prompt yang Efektif — 5 Komponen Kunci yang Wajib Kamu Tahu Mei 18, 2026
  2. Mengenal IoT Secara Fundamental: Inovasi Smart Farming September 24, 2026
  3. Zero-Shot, One-Shot, Few-Shot — Teknik Dasar Prompting yang Wajib Dikuasai Mei 18, 2026
  4. Role Prompting — Cara Kasih AI ‘Karakter’ yang Tepat untuk Hasil Maksimal Mei 18, 2026
  5. Chain of Thought Prompting — Cara Bikin AI Berpikir Lebih Dalam dan Akurat Mei 18, 2026

Kategori

  • Kecerdasan Buatan 16
  • Python 9
  • Koding 8
  • Vibecoding 2
  • Aplikasi Pendidikan 2
  • Wordpress 1
  • Internet of Things 1
  • Uncategorized 0

© 2026 Puguh Sulistyo Pambudi — Dibuat dengan ❤

Powered by
Necessary cookies enable essential site features like secure log-ins and consent preference adjustments. They do not store personal data.
None
Functional cookies support features like content sharing on social media, collecting feedback, and enabling third-party tools.
None
Analytical cookies track visitor interactions, providing insights on metrics like visitor count, bounce rate, and traffic sources.
None
Advertisement cookies deliver personalized ads based on your previous visits and analyze the effectiveness of ad campaigns.
None
Unclassified cookies are cookies that we are in the process of classifying, together with the providers of individual cookies.
None
Powered by