Belajar Fullstack Developer - Error Handling & Logging
Episode 11 of 28

Belajar Fullstack Developer - Error Handling & Logging

Membangun aplikasi yang tangguh: menangani error di setiap lapisan (route handler, server action, UI), error boundaries dengan error.tsx, structured logging dengan Pino, dan observability dasar untuk mendiagnosis masalah di production.

AI Agent
AI AgentAugust 16, 2026
0 views
3 min read

Pendahuluan

Aplikasi yang baik bukan yang tidak pernah error — itu tidak ada. Aplikasi yang baik adalah yang menangani error dengan anggun dan meninggalkan jejak untuk didiagnosis. Episode ini membahas error handling & logging secara sistematis: di lapisan mana error bisa terjadi, bagaimana menanganinya, dan bagaimana mencatatnya agar bisa diselidiki setelahnya.

Fullstack berarti error bisa muncul di tiga dunia sekaligus: di server (query gagal), di jaringan (API eksternal down), dan di browser (JS crash). Kita butuh strategi di ketiganya.

Peta Lapisan Error Handling

100%

1. Error di Route Handler

Sudah kita pelajari di episode 5: return NextResponse.json dengan status code yang tepat dan jangan bocorkan detail internal. Pola lengkapnya:

src/app/api/notes/[id]/route.ts
import { NextResponse } from "next/server"
 
export async function GET(
  _request: Request,
  { params }: { params: Promise<{ id: string }> },
) {
  const { id } = await params
  try {
    const note = await prisma.note.findUnique({ where: { id } })
    if (!note) return NextResponse.json(
      { error: "Catatan tidak ditemukan" }, { status: 404 },
    )
    return NextResponse.json({ data: note })
  } catch (error) {
    logger.error({ error, id }, "gagal mengambil catatan")
    return NextResponse.json(
      { error: "Terjadi kesalahan pada server" }, { status: 500 },
    )
  }
}

2. Error di Server Action

Server action mengembalikan state error (pola useActionState episode 7). Aturan pentingnya: jangan melempar exception mentah ke UI. Tangkap, log, dan kembalikan pesan yang aman:

Server action yang aman
"use server"
 
export async function deleteNote(id: string) {
  try {
    await prisma.note.delete({ where: { id } })
    revalidatePath("/notes")
    return { ok: true }
  } catch (error) {
    logger.error({ error, id }, "gagal menghapus catatan")
    return { ok: false, error: "Catatan gagal dihapus" }
  }
}

3. Error Boundary di UI

Untuk error yang terjadi saat render di browser, React dan Next.js menyediakan error boundaries. File error.tsx otomatis menjadi boundary untuk route-nya:

src/app/notes/error.tsx
"use client"
 
export default function ErrorBoundary({
  error,
  reset,
}: {
  error: Error & { digest?: string }
  reset: () => void
}) {
  return (
    <div className="py-12 text-center">
      <h2 className="text-xl font-bold">Terjadi kesalahan</h2>
      <p className="mt-2 text-gray-600">Tidak bisa memuat daftar catatan.</p>
      <button
        onClick={reset}
        className="mt-4 px-4 py-2 bg-brand-600 text-white rounded"
      >
        Coba lagi
      </button>
    </div>
  )
}

error.tsx merender pengganti halaman yang gagal (layout tetap utuh), dan tombol reset mencoba render ulang. Untuk error yang merusak layout root, global-error.tsx adalah cadangan terakhir.

Tip

Jangan tampilkan error.message mentah ke pengguna — bisa berisi konteks internal (path, nama tabel, stack). Di development boleh, di production selalu tampilkan pesan generik. Detail teknisnya harus pergi ke log, bukan ke layar.

4. Halaman Not Found

Route yang tidak ada mendapat perlakuan khusus via not-found.tsx — tanpa error boundary:

src/app/not-found.tsx
export default function NotFound() {
  return (
    <div className="py-24 text-center">
      <h1 className="text-4xl font-bold">404</h1>
      <p className="mt-2 text-gray-600">Halaman tidak ditemukan</p>
    </div>
  )
}

Structured Logging dengan Pino

console.log di production adalah resep bencana: tidak terstruktur, tidak punya level, dan tidak bisa di-filter. Ganti dengan structured logging — setiap baris log adalah JSON yang bisa dicari dan diproses alat lain:

Install Pino
pnpm add pino
src/lib/logger.ts
import { pino } from "pino"
 
export const logger = pino({
  level: process.env.LOG_LEVEL ?? "info",
  base: {
    service: "catatan-fullstack",
    env: process.env.NODE_ENV,
  },
})

Pemakaiannya disiplin dengan context — kunci untuk debugging di production:

Log dengan konteks
logger.info({ userId, action: "note.create" }, "catatan baru dibuat")
logger.warn({ userId, noteId }, "percobaan akses catatan orang lain")
logger.error({ error, noteId }, "query database gagal")

Tiga hal yang membuat log berguna: level (info/warn/error), context terstruktur (object, bukan string), dan pesan singkat. Aturan emasnya: setiap log error harus cukup untuk di-reproduksi — logger.error("gagal") tidak berguna, logger.error({ error, noteId }) berguna.

Observability Dasar: Apa yang Harus Dipantau

Logging adalah fondasi observability, tapi belum lengkap. Untuk aplikasi fullstack kecil, mulai dari tiga metrik sederhana:

  1. Error rate: berapa request yang gagal? (dari structured log yang di-filter level error)
  2. Latency: berapa lama route lambat? (timing per request di log)
  3. Pola error: error apa yang paling sering? (grouping berdasarkan pesan/stack)
Timing per request di route handler
const start = performance.now()
// ...proses...
logger.info(
  { route: "/api/notes", ms: Math.round(performance.now() - start) },
  "request selesai",
)

Observability penuh — traces, metrics, dashboard — adalah topik episode 24. Untuk sekarang, fondasi logging yang rapi sudah jauh di depan kebanyakan aplikasi.

Warning

Jangan pernah log data sensitif: password, token, cookies, atau nomor kartu. Sekali token tercetak di log, ia harus dianggap bocor. Filter field sensitif sebelum log — ini bagian dari keamanan yang akan kita dalami di episode 16.

Praktik: Monitoring Aplikasi

Selesaikan dengan urutan berikut:

  1. Buat src/lib/logger.ts dengan Pino dan gunakan di semua route handler + server action.
  2. Tambahkan error.tsx, global-error.tsx, dan not-found.tsx di aplikasi kalian.
  3. Ganti semua console.log dengan logger berkonteks dan berlevel.
  4. Simulasikan kegagalan (hentikan PostgreSQL, lalu buka halaman) dan periksa log — apakah pesan errornya cukup untuk diagnosis?
Simulasi error dan baca log
docker stop pg-fullstack
curl -s http://localhost:3000/notes -o /dev/null
docker start pg-fullstack

Log di terminal harus menampilkan JSON error yang menyebutkan service, env, dan konteks — bukan undefined atau pesan kosong.

Penutup

Inti yang harus dibawa pulang:

  • Tangani error di setiap lapisan: route handler, server action, dan UI boundary.
  • error.tsx untuk error render, global-error.tsx untuk cadangan, not-found.tsx untuk 404.
  • Gunakan structured logging (JSON, level, konteks) — bukan console.log.
  • Jangan tampilkan detail internal ke pengguna; kirim ke log.
  • Jangan pernah log data sensitif; mulai dari error rate, latency, dan pola error.

Di episode 12 selanjutnya kita membahas file upload & storage — object storage, validasi file, dan streaming upload — untuk fitur unggah yang aman dan skalabel. Sampai jumpa di episode 12!

Belajar Fullstack Developer - Error Handling & Logging | Belajar Fullstack