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 #6: Validasi Input dengan Zod dan Global Error Handler
Koding

Belajar Hono Framework #6: Validasi Input dengan Zod dan Global Error Handler

Puguh Sulistyo Pambudi
Puguh Sulistyo Pambudi Mei 19, 2026 · 6 menit baca · 2
Belajar Hono Framework #6: Validasi Input dengan Zod dan Global Error Handler
Daftar Isi
  1. 1 Masalah dengan Validasi Manual
  2. 2 Apa Itu Zod?
  3. 3 Mendefinisikan Schema Siswa dengan Zod
  4. 4 Membuat Helper Validasi
  5. 5 Upgrade Endpoint POST dan PUT dengan Zod
  6. 6 Membuat Global Error Handler
  7. 7 Melihat Pesan Error Zod yang Lebih Kaya
  8. 8 Struktur Akhir Proyek
  9. 9 Selamat! Kamu Sudah Menyelesaikan Seri Ini! 🎉
  10. 10 Mau Lanjut? Tantangan Berikutnya!

Selamat, API CRUD kita sudah jalan penuh! Tapi kalau kamu perhatiin, validasi kita masih “amatiran” — banyak pengecekan manual yang berulang-ulang. Di part terakhir ini, kita akan upgrade total dengan dua senjata ampuh: Zod untuk validasi schema yang profesional, dan global error handler supaya API kita tidak pernah “bocor” error jelek ke pengguna. Ini yang memisahkan API biasa dengan API yang production-ready!

Masalah dengan Validasi Manual

Sebelum kita upgrade, mari kita lihat dulu masalah dengan pendekatan kita sekarang:

// Validasi manual — ribet dan berulang!
if (!nama || !nis || !kelas || !jurusan || !email) { ... }
if (!isEmailValid(email)) { ... }

Ada beberapa masalah di sini:

  • Kita tidak bisa validasi tipe data (apakah nama benar-benar string?)
  • Kita tidak bisa batasi panjang karakter dengan mudah
  • Kita harus copy-paste validasi yang sama di endpoint POST dan PUT
  • Pesan error-nya tidak konsisten

Zod hadir untuk menyelesaikan semua masalah ini!

Apa Itu Zod?

Zod adalah library validasi dan parsing schema untuk TypeScript. Dengan Zod, kamu mendefinisikan “bentuk” data yang valid sekali, lalu Zod akan otomatis memvalidasi dan bahkan memberikan pesan error yang deskriptif.

Install Zod ke proyek kita:

bun add zod

Mendefinisikan Schema Siswa dengan Zod

Buat file baru src/schemas/siswa.schema.ts:

import { z } from 'zod'

export const SiswaSchema = z.object({
  nama: z.string()
    .min(3, 'Nama minimal 3 karakter')
    .max(100, 'Nama maksimal 100 karakter'),

  nis: z.string()
    .min(5, 'NIS minimal 5 karakter')
    .max(20, 'NIS maksimal 20 karakter')
    .regex(/^\d+$/, 'NIS hanya boleh berisi angka'),

  kelas: z.string()
    .min(2, 'Kelas minimal 2 karakter')
    .max(20, 'Kelas maksimal 20 karakter'),

  jurusan: z.string()
    .min(3, 'Jurusan minimal 3 karakter')
    .max(50, 'Jurusan maksimal 50 karakter'),

  email: z.string()
    .email('Format email tidak valid. Contoh: [email protected]')
    .max(100, 'Email maksimal 100 karakter')
})

// Tipe TypeScript yang otomatis digenerate dari schema
export type SiswaInput = z.infer<typeof SiswaSchema>

Lihat betapa ekspresifnya Zod! Setiap aturan validasi ditulis dengan jelas, dan pesan errornya sudah kita tentukan sendiri. Tidak ada lagi if (!nama) yang berulang-ulang!

Membuat Helper Validasi

Buat file src/helpers/validate.ts untuk fungsi validasi yang bisa dipakai ulang:

import { z } from 'zod'

export const validate = <T>(schema: z.ZodSchema<T>, data: unknown): 
  { success: true; data: T } | { success: false; errors: string[] } => {
  
  const result = schema.safeParse(data)

  if (result.success) {
    return { success: true, data: result.data }
  }

  // Kumpulkan semua pesan error menjadi array string
  const errors = result.error.errors.map(err => err.message)
  return { success: false, errors }
}

Fungsi validate ini menggunakan safeParse dari Zod — artinya dia tidak akan throw error kalau validasi gagal, melainkan mengembalikan objek yang bisa kita periksa dengan aman.

Upgrade Endpoint POST dan PUT dengan Zod

Sekarang update src/routes/siswa.ts. Import schema dan helper kita:

import { Hono } from 'hono'
import sql from '../db'
import { SiswaSchema } from '../schemas/siswa.schema'
import { validate } from '../helpers/validate'

const siswa = new Hono()

Kemudian ganti endpoint POST menjadi seperti ini:

siswa.post('/', async (c) => {
  const body = await c.req.json()

  // Validasi dengan Zod — satu baris menggantikan banyak if!
  const validation = validate(SiswaSchema, body)

  if (!validation.success) {
    return c.json({
      success: false,
      message: 'Data tidak valid',
      errors: validation.errors
    }, 400)
  }

  const { nama, nis, kelas, jurusan, email } = validation.data

  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)
    }
    throw e // Lempar ke global error handler!
  }
})

Perhatikan dua hal penting:

Satu validasi untuk semua aturan: Tidak ada lagi 10 baris if. Satu panggilan validate() sudah mengurus semuanya.

throw e: Untuk error yang tidak kita kenal, kita tidak lagi return 500 secara manual. Kita lempar (throw) error-nya supaya ditangkap oleh global error handler yang akan kita buat sebentar lagi.

Lakukan hal yang sama untuk endpoint PUT — tinggal ganti siswa.post menjadi siswa.put dengan tambahan validasi ID di atas.

Membuat Global Error Handler

Ini fitur terbaik! Global error handler menangkap semua error yang tidak tertangani di seluruh aplikasi — satu titik pusat untuk semua error.

Buka src/index.ts dan tambahkan error handler ini:

import { Hono } from 'hono'
import { logger } from 'hono/logger'
import { cors } from 'hono/cors'
import siswaRoutes from './routes/siswa'

const app = new Hono()

app.use(logger())
app.use(cors())

app.route('/api/siswa', siswaRoutes)

app.get('/', (c) => c.json({
  status: 'OK',
  message: 'API Sekolah berjalan! 🎉'
}))

// ↓ Global Error Handler — taruh di paling bawah!
app.onError((err, c) => {
  console.error(`[ERROR] ${c.req.method} ${c.req.url}:`, err.message)

  // Jangan pernah tampilkan detail error ke user di production!
  return c.json({
    success: false,
    message: 'Terjadi kesalahan pada server. Tim kami sedang menanganinya.',
    ...(process.env.NODE_ENV === 'development' && { detail: err.message })
  }, 500)
})

// Handler untuk route yang tidak ditemukan
app.notFound((c) => {
  return c.json({
    success: false,
    message: `Route ${c.req.method} ${c.req.url} tidak ditemukan.`
  }, 404)
})

export default {
  port: Number(process.env.PORT) || 3000,
  fetch: app.fetch
}

Dengan app.onError, semua error yang throw dari mana pun akan ditangkap di sini. Di mode development, kita tampilkan detail error-nya supaya mudah di-debug. Di production, kita sembunyikan detailnya — ini sangat penting untuk keamanan!

Melihat Pesan Error Zod yang Lebih Kaya

Sekarang coba kirim request POST dengan data yang salah:

curl -X POST http://localhost:3000/api/siswa \
  -H "Content-Type: application/json" \
  -d '{ "nama": "A", "nis": "abc", "email": "bukan-email" }'

Responsnya akan jauh lebih informatif dari sebelumnya:

{
  "success": false,
  "message": "Data tidak valid",
  "errors": [
    "Nama minimal 3 karakter",
    "NIS hanya boleh berisi angka",
    "Kelas tidak boleh kosong",
    "Jurusan tidak boleh kosong",
    "Format email tidak valid. Contoh: [email protected]"
  ]
}

Semua error sekaligus ditampilkan dalam satu respons — pengguna tahu apa saja yang perlu diperbaiki tanpa harus submit berkali-kali!

Struktur Akhir Proyek

Ini tampilan akhir struktur proyek kita yang sudah lengkap dan rapi:

api-sekolah/
├── src/
│   ├── index.ts              ← Entry point + global error handler
│   ├── db.ts                 ← Koneksi PostgreSQL
│   ├── schemas/
│   │   └── siswa.schema.ts   ← Schema validasi Zod
│   ├── helpers/
│   │   └── validate.ts       ← Helper fungsi validasi
│   └── routes/
│       └── siswa.ts          ← Semua endpoint CRUD siswa
├── .env
├── .gitignore
└── package.json

Struktur ini bersih, terorganisir, dan mudah untuk dikembangkan ke depannya — misalnya kalau mau tambah fitur guru, mata pelajaran, atau nilai siswa!

Selamat! Kamu Sudah Menyelesaikan Seri Ini! 🎉

Ini semua yang sudah kamu pelajari dari 6 part seri ini:

PartYang Dipelajari
#1Pengenalan Hono, instalasi, routing dasar
#2Setup proyek + koneksi PostgreSQL
#3GET endpoint + query filter
#4POST endpoint + handle duplikat
#5PUT & DELETE endpoint
#6Validasi Zod + Global Error Handler

Kamu sudah punya fondasi yang sangat kuat untuk membangun REST API yang profesional dengan Hono + Bun + PostgreSQL!

Mau Lanjut? Tantangan Berikutnya!

Supaya skill kamu makin tajam, coba tantangan-tantangan ini sendiri:

  1. Tambah fitur pagination — tampilkan siswa 10 per halaman
  2. Tambah endpoint ekspor CSV — unduh data siswa sebagai file Excel
  3. Buat autentikasi JWT — proteksi API supaya hanya yang login yang bisa akses
  4. Deploy ke Cloudflare Workers — biar API kamu bisa diakses dari internet!

Kalau kamu berhasil menyelesaikan seri ini, share proyekmu di kolom komentar! Kita semua senang lihat progress kamu. Dan kalau seri ini bermanfaat, share ke teman-temanmu yang lagi belajar backend ya — mereka pasti akan berterima kasih! 🔥

#Bun #Hono Framework
Puguh Sulistyo Pambudi

Ditulis oleh

Puguh Sulistyo Pambudi
Bagikan
← Sebelumnya Belajar Hono Framework #5: Update dan Hapus Data Siswa (PUT & DELETE)
Selanjutnya → Ini yang Saya Lakukan Sebelum Mulai Vibecoding dengan Claude Opus 4.7

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