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:
| Method | Endpoint | Fungsi | Status Code |
|---|---|---|---|
GET | /api/siswa | Ambil semua siswa | 200 |
GET | /api/siswa?nama=x | Cari siswa by nama | 200 |
GET | /api/siswa/:id | Detail satu siswa | 200 / 404 |
POST | /api/siswa | Tambah siswa baru | 201 / 400 / 409 |
PUT | /api/siswa/:id | Update data siswa | 200 / 400 / 404 / 409 |
DELETE | /api/siswa/:id | Hapus siswa | 200 / 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! 💪
Tinggalkan Balasan