Di balik sebuah aplikasi yang tampak sederhana-seperti formulir pendaftaran, fitur login, atau halaman pemesanan-selalu ada arus data yang terus mengalir masuk ke server. Data ini tidak selalu rapi, tidak selalu lengkap, dan tidak selalu dapat dipercaya. Di sinilah validasi input menjadi garis pertahanan pertama: mencegah bug, melindungi data, dan menjaga API tetap kokoh meski digempur berbagai jenis permintaan.
Dalam ekosistem Node.js, kombinasi Express sebagai web framework dan Joi sebagai library validasi menawarkan cara yang elegan dan terstruktur untuk memastikan setiap data yang masuk telah “disaring” dengan benar. Bukan hanya sekadar mengecek apakah sebuah field kosong atau tidak, tetapi juga memastikan format, tipe data, panjang, hingga pola tertentu benar-benar sesuai dengan yang diharapkan.
Artikel ini akan mengulas bagaimana memaksimalkan validasi input pada API berbasis Express menggunakan Joi: mulai dari konsep dasar, pola penggunaan yang rapi dan terorganisir, hingga strategi penanganan error yang lebih informatif bagi klien. Dengan pendekatan yang tepat, validasi bukan lagi sekadar “tambahan”, melainkan bagian integral dari desain API yang aman, andal, dan mudah dirawat.
Merancang Skema Validasi Joi yang Modular dan Mudah Dipelihara
Alih-alih menulis satu skema raksasa untuk setiap endpoint, jauh lebih enak kalau kamu pecah jadi potongan-potongan kecil yang bisa dipakai ulang. Misalnya, kamu bisa punya base schema untuk user (email, nama, password), lalu di-extend untuk kebutuhan spesifik seperti registrasi, login, atau update profil. Dengan begitu, kalau suatu saat aturan validasi email berubah, kamu cukup ubah di satu tempat. Pola ini biasanya didukung dengan struktur folder yang rapi, misalnya:
- /validators untuk mengumpulkan semua skema Joi
- file per domain seperti
user.validator.js,auth.validator.js - helper schema kecil seperti
idSchemaataupaginationSchema
“`js
// validators/baseSchemas.js
const Joi = require(‘joi’);
const idSchema = Joi.string().uuid().required();
const emailSchema = Joi.string().email().required();
module.exports = { idSchema, emailSchema };
“`
Supaya makin mudah dipelihara, kamu bisa memisahkan skema berdasarkan bagian request (body, params, query) dan menyiapkan satu middleware generik yang tinggal dipanggil di tiap route. Pendekatan ini bikin validasi terasa “plug and play”, jadi kalau ada endpoint baru, kamu cuma fokus bikin skemanya tanpa ngoprek middleware lagi. Contoh pembagian tanggung jawabnya bisa dilihat di tabel singkat berikut:
| Berkas | Peran |
|---|---|
user.schema.js |
Definisi skema Joi untuk user |
validate.middleware.js |
Middleware umum untuk body/params/query |
user.routes.js |
Memasang skema ke tiap endpoint |
“`js
// middlewares/validate.js
module.exports = (schema, property = ‘body’) => (req, res, next) => {
const { error, value } = schema.validate(req[property], {
abortEarly: false,
stripUnknown: true,
});
if (error) {
return res.status(400).json({
message: ‘Input tidak valid’,
details: error.details.map(d => d.message),
});
}
req[property] = value;
next();
};
“`
Mengintegrasikan Middleware Validasi Joi di Express untuk Setiap Layer Request
Di Express, trik utamanya adalah membuat middleware generik yang bisa dipakai ulang di setiap layer request: params, query, dan body. Caranya, kita bikin satu fungsi pembungkus yang menerima skema Joi, lalu mengembalikan middleware Express. Dengan pendekatan ini, alur request jadi lebih rapi: data masuk → dicek skema → kalau lolos, baru menyentuh controller. Biasanya middleware ini dipasang tepat sebelum handler route, sehingga controller hanya menerima data yang sudah “disucikan”. Nilai plusnya, kita bisa memecah skema berdasarkan kebutuhan, misalnya skema untuk endpoint publik, admin, atau internal service.
“`js
const Joi = require(‘joi’);
const validate =
(schema, target = ‘body’) =>
(req, res, next) => {
const { error, value } = schema.validate(req[target], {
abortEarly: false,
stripUnknown: true,
});
if (error) {
return res.status(400).json({
status: ‘fail’,
errors: error.details.map((d) => d.message),
});
}
req[target] = value;
next();
};
// Contoh pemakaian:
// app.post(‘/users’, validate(userSchema, ‘body’), userController.create);
// app.get(‘/users/:id’, validate(idSchema, ‘params’), userController.detail);
“`
Supaya makin fleksibel, kamu bisa menggabungkan beberapa skema sekaligus dalam satu route, misalnya memvalidasi params dan query dalam satu waktu. Biasanya ini dikombinasikan dengan struktur folder yang jelas, misalnya memisahkan schemas, middlewares, dan controllers. Dengan begitu, perubahan aturan validasi cukup dilakukan di skema tanpa mengotak-atik logika utama. Selain itu, menggunakan middleware juga memudahkan debug: tinggal cek layer validasi untuk tahu kenapa sebuah request mental. Untuk merapikan dokumentasi internal, kamu bahkan bisa merangkum pemakaian middleware ini dalam tabel kecil seperti di bawah:
| Layer | Target Joi | Contoh Pemakaian |
|---|---|---|
| Route Params | req.params |
validate(idSchema, 'params') |
| Query String | req.query |
validate(filterSchema, 'query') |
| Request Body | req.body |
validate(bodySchema, 'body') |
- Reusable: satu middleware bisa dipakai di banyak endpoint.
- Terstruktur: controller fokus ke logika bisnis, bukan validasi.
- Mudah dirawat: perubahan aturan cukup di skema Joi.
Menangani Error Validasi Secara Konsisten dan Aman dalam Respons API
Supaya respons API tetap rapi dan mudah diprediksi, biasakan mengubah semua error validasi dari Joi menjadi satu format standar sebelum dikirim ke client. Alih-alih langsung melempar pesan mentah dari Joi, bungkus dulu ke dalam struktur objek yang konsisten, misalnya selalu punya properti code, message, dan details. Dengan begitu, frontend bisa dengan mudah menampilkan error tanpa harus menebak-nebak bentuk respons. Beberapa pola yang sering dipakai antara lain:
- Selalu gunakan HTTP status 400 untuk error validasi.
- Jangan bocorkan detail internal seperti stack trace ke client.
- Gabungkan semua kesalahan field ke dalam satu array yang mudah di-loop.
- Bedakan error validasi dengan error server (500) secara jelas.
| Status | Code | Contoh Message |
|---|---|---|
| 400 | VALIDATION_ERROR | Input tidak sesuai format |
| 422 | UNPROCESSABLE_ENTITY | Data valid tapi tidak bisa diproses |
Dari sisi keamanan, kuncinya adalah hanya mengirim informasi yang memang dibutuhkan oleh client, bukan semua isi error dari server. Di Express, kamu bisa membuat middleware khusus yang memetakan error Joi menjadi respons yang aman, lalu memastikan semua rute pakai pola itu. Dengan cara ini, kamu bisa:
- Menyensor informasi sensitif (misalnya nama field internal yang tidak perlu diketahui user).
- Menghindari enum atau pola regex bocor langsung ke respons, cukup kirim pesan ramah pengguna.
- Menjaga konsistensi format error antar endpoint, meski skema Joi-nya berbeda-beda.
“`js
app.use((err, req, res, next) => {
if (err.isJoi) {
const details = err.details.map(d => ({
field: d.context.key,
message: d.message.replace(/[“]/g, ”)
}));
return res.status(400).json({
code: ‘VALIDATION_ERROR’,
message: ‘Input tidak valid, periksa kembali data yang dikirim.’,
details
});
}
// fallback untuk error lain
res.status(500).json({
code: ‘INTERNAL_SERVER_ERROR’,
message: ‘Terjadi kesalahan pada server.’
});
});
“`
Menerapkan Praktik Terbaik Validasi Lanjutan Termasuk Custom Rules dan Sanitasi Data
Setelah pola dasar skema berjalan mulus, saatnya naik level dengan memanfaatkan kemampuan lanjutan seperti custom rules dan sanitasi data. Di Joi, kamu bisa menulis aturan sendiri yang benar-benar nyambung dengan kebutuhan bisnis, misalnya memvalidasi format kode referral internal, struktur nomor anggota, atau pola username yang wajib unik di sistem tertentu. Dengan approach seperti ini, logika bisnis yang biasanya tersebar di berbagai lapisan aplikasi bisa kamu “tarik turun” ke lapisan validasi, sehingga lebih terpusat dan gampang dirawat. Beberapa hal yang sering jadi kandidat custom rule antara lain:
- Validasi domain email tertentu (misalnya hanya boleh pakai domain perusahaan).
- Aturan numerik khusus seperti range dinamis yang tergantung role user.
- Validasi cross-field, contohnya tanggal mulai tidak boleh lebih besar dari tanggal selesai.
- Pattern ID internal dengan prefix dan checksum tertentu.
| Jenis Aturan | Contoh Penggunaan | Tujuan |
|---|---|---|
| Custom Rule | Validasi kode referral | Jaga kualitas data |
| Sanitasi | Trim spasi & lowerCase | Samakan format input |
| Normalisasi | Mapping nilai default | Kurangi edge case |
Yang sering terlupakan justru bagian sanitasi data. Padahal, sebelum data lolos ke layer service, sebaiknya sudah “dibersihkan” dulu: spasi berlebih di-trim, nilai kosong distandarkan, dan karakter berbahaya diblok. Ini bukan cuma soal keamanan, tapi juga soal konsistensi; API jadi lebih mudah diprediksi karena input yang berantakan sudah dinormalkan sejak awal. Beberapa langkah yang layak dijadikan kebiasaan antara lain:
- Normalisasi string:
trim, ubah ke lower/upper case sesuai kebutuhan. - Filter karakter tak diinginkan pada field yang sensitif (misalnya hanya huruf & angka).
- Pemisahan field mentah dan field bersih saat butuh logging atau audit trail.
- Integrasi dengan middleware keamanan untuk mengurangi risiko XSS dan injection di layer Express.
The Way Forward
Pada akhirnya, validasi bukan sekadar “pemeriksa kesalahan”, tetapi garda terdepan yang menentukan seberapa tangguh sebuah API bisa bertahan menghadapi data yang datang dari segala arah. Dengan memanfaatkan Joi dan mengintegrasikannya secara rapi ke dalam Express, kita bukan hanya menolak input yang tidak sesuai, tetapi juga mendokumentasikan niat bisnis, mengurangi bug tersembunyi, dan memudahkan proses debugging di kemudian hari.
Setelah ini, cara pandang terhadap validasi mungkin tidak lagi sama: bukan lagi langkah tambahan yang mengganggu alur pengembangan, melainkan fondasi penting yang layak dirancang dengan sengaja sejak awal. Silakan bereksperimen dengan skema yang lebih kompleks, menerapkan middleware yang dapat digunakan ulang, atau mengombinasikan Joi dengan lapisan keamanan lain di aplikasi Anda.
Pada akhirnya, API yang kokoh lahir dari serangkaian keputusan kecil yang konsisten. Memaksimalkan validasi dengan Joi dan Express adalah salah satu keputusan itu-diam, sederhana, tetapi menentukan.

