tutorial, express.js,

Error Handling dan Validasi Input di Express.js

Kiki/🎮🍉⌨️🍩💻 Kiki/🎮🍉⌨️🍩💻 Aug 26, 2026 · 10 mins read
Error Handling dan Validasi Input di Express.js
Share this

API yang baik bukan hanya yang bisa menangani kasus sukses — tapi juga yang memberikan respons error yang informatif, konsisten, dan aman. Di artikel ini kita bahas cara membangun sistem error handling terpusat dan validasi input yang profesional di Express.js.


Masalah dengan Error Handling yang Tidak Konsisten

Tanpa sistem error handling yang terpusat, setiap route menulis error response sendiri-sendiri:

// Tidak konsisten — setiap route format errornya berbeda
app.get('/a', (req, res) => {
  res.status(404).send('Tidak ditemukan');
});

app.get('/b', (req, res) => {
  res.status(404).json({ message: 'Not found' });
});

app.get('/c', (req, res) => {
  res.status(404).json({ error: true, detail: 'Resource missing' });
});

Klien API kamu harus menangani tiga format berbeda. Ini menyulitkan integrasi.


Custom Error Class

Buat class Error kustom yang membawa HTTP status code:

// utils/AppError.js
class AppError extends Error {
  constructor(message, statusCode = 500) {
    super(message);
    this.statusCode = statusCode;
    this.isOperational = true; // error yang kita lempar sengaja
    Error.captureStackTrace(this, this.constructor);
  }
}

module.exports = AppError;

Error Handler Terpusat

// middleware/errorHandler.js
function errorHandler(err, req, res, next) {
  const isDev = process.env.NODE_ENV !== 'production';

  // Default status dan message
  let status = err.statusCode || 500;
  let message = err.message || 'Terjadi kesalahan pada server';

  // Prisma error - record not found
  if (err.code === 'P2025') {
    status = 404;
    message = 'Data tidak ditemukan';
  }

  // Prisma error - unique constraint
  if (err.code === 'P2002') {
    status = 409;
    message = `Field '${err.meta?.target}' sudah digunakan`;
  }

  // JWT error
  if (err.name === 'JsonWebTokenError') {
    status = 403;
    message = 'Token tidak valid';
  }

  if (err.name === 'TokenExpiredError') {
    status = 401;
    message = 'Token sudah kadaluarsa, silakan login kembali';
  }

  // SyntaxError pada JSON body yang tidak valid
  if (err instanceof SyntaxError && err.status === 400) {
    status = 400;
    message = 'Format JSON tidak valid';
  }

  const response = { error: message };
  if (isDev) response.stack = err.stack;

  res.status(status).json(response);
}

module.exports = errorHandler;

Daftarkan di index.jspaling akhir setelah semua route:

const errorHandler = require('./middleware/errorHandler');
// ... semua route ...
app.use(errorHandler);

Menggunakan AppError di Route

const AppError = require('../utils/AppError');

app.get('/pengguna/:id', async (req, res, next) => {
  try {
    const id = parseInt(req.params.id);
    if (isNaN(id)) throw new AppError('ID harus berupa angka', 400);

    const pengguna = await db.findById(id);
    if (!pengguna) throw new AppError('Pengguna tidak ditemukan', 404);

    res.json(pengguna);
  } catch (err) {
    next(err); // teruskan ke error handler terpusat
  }
});

Validasi Input dengan express-validator

Library express-validator menyediakan validasi yang deklaratif dan mudah dibaca.

npm install express-validator

Contoh Validasi Registrasi

const { body, validationResult } = require('express-validator');

const aturanValidasiDaftar = [
  body('nama')
    .trim()
    .notEmpty().withMessage('Nama wajib diisi')
    .isLength({ min: 2, max: 100 }).withMessage('Nama harus 2-100 karakter'),

  body('email')
    .trim()
    .notEmpty().withMessage('Email wajib diisi')
    .isEmail().withMessage('Format email tidak valid')
    .normalizeEmail(),

  body('password')
    .notEmpty().withMessage('Password wajib diisi')
    .isLength({ min: 8 }).withMessage('Password minimal 8 karakter')
    .matches(/[A-Z]/).withMessage('Password harus mengandung huruf kapital')
    .matches(/[0-9]/).withMessage('Password harus mengandung angka'),

  body('konfirmasiPassword')
    .custom((value, { req }) => {
      if (value !== req.body.password) {
        throw new Error('Konfirmasi password tidak cocok');
      }
      return true;
    })
];

// Middleware untuk cek hasil validasi
function cekValidasi(req, res, next) {
  const errors = validationResult(req);
  if (!errors.isEmpty()) {
    return res.status(422).json({
      error: 'Input tidak valid',
      detail: errors.array().map(e => ({ field: e.path, pesan: e.msg }))
    });
  }
  next();
}

// Pakai di route
router.post('/register', aturanValidasiDaftar, cekValidasi, async (req, res) => {
  // input sudah tervalidasi dan di-sanitize
  const { nama, email, password } = req.body;
  // ... proses registrasi
});

Validasi Artikel

const aturanArtikel = [
  body('judul').trim().notEmpty().withMessage('Judul wajib diisi')
    .isLength({ max: 200 }).withMessage('Judul maksimal 200 karakter'),

  body('konten').trim().notEmpty().withMessage('Konten wajib diisi')
    .isLength({ min: 50 }).withMessage('Konten minimal 50 karakter'),

  body('kategori').optional()
    .isIn(['tutorial', 'opini', 'berita', 'review'])
    .withMessage('Kategori tidak valid'),

  body('tags').optional().isArray().withMessage('Tags harus berupa array')
];

router.post('/artikel', authMiddleware, aturanArtikel, cekValidasi, async (req, res) => {
  const { judul, konten, kategori } = req.body;
  // ...
});

404 Handler untuk Route Tidak Dikenal

// Letakkan setelah semua route, sebelum error handler
app.use((req, res, next) => {
  next(new AppError(`Endpoint ${req.method} ${req.url} tidak ditemukan`, 404));
});

// Error handler terakhir
app.use(errorHandler);

Format Response Error yang Konsisten

Dengan pendekatan ini, semua error response memiliki format yang sama:

{
  "error": "Pengguna tidak ditemukan"
}

Untuk error validasi:

{
  "error": "Input tidak valid",
  "detail": [
    { "field": "email", "pesan": "Format email tidak valid" },
    { "field": "password", "pesan": "Password minimal 8 karakter" }
  ]
}

Klien API kamu hanya perlu menangani satu format — jauh lebih mudah diintegrasikan.


Kesimpulan

Error handling dan validasi yang baik adalah tanda API yang matang. Dengan AppError, error handler terpusat, dan express-validator, semua error ditangani secara konsisten di satu tempat — route handler hanya perlu fokus pada logika bisnis, bukan mengurus format error.

Di artikel berikutnya, kita bahas upload file di Express.js menggunakan Multer.

Kiki/🎮🍉⌨️🍩💻
Written by Kiki/🎮🍉⌨️🍩💻
hello I'm friend K.