Pendahuluan — pintu sudah ada, siapa yang menjaga?

Di Laravel Routing & JSON (#56) kamu sudah punya pintu HTTP yang menjawab JSON. Artikel ini adalah #57 (ini) — langkah kedua stack Laravel di Seri 4.

Ide barunya: data yang masuk lewat pintu bisa kotor (kosong, salah tipe, tidak masuk akal). Kita butuh penjaga yang memeriksa dulu — baru boleh diproses.

Awam: bayangkan loket perpustakaan. Pengunjung membawa formulir pinjam. Petugas (penjaga) cek: nama terisi? tahun masuk akal? Kalau tidak, formulir dikembalikan dengan catatan — bukan langsung masuk ke rak.

Prasyarat: sudah baca Laravel Routing & JSON (#56) — paham route, JSON, dan status 200/404. Domain tetap perpustakaan mini. Pakai Laravel 11+ — sintaks validasi di sini berlaku di versi modern.

Istilah — Request, validasi, Form Request

Istilah Arti awam Contoh di artikel ini
Request Paket data yang masuk lewat pintu (judul, tahun, dll.) {"judul":"...","tahun":2024}
Validasi Cek: data wajib ada, tipe benar, rentang masuk akal Judul tidak kosong; tahun 1900-2100
Form Request Kelas Laravel khusus yang menyimpan aturan penjaga StoreBukuRequest
POST Cara mengetuk pintu: “kirim data baru/ubah” (bukan hanya minta baca) POST /api/buku + JSON isi buku
Status 422 “Data kamu tidak diterima” (bukan “pintu tidak ada”) 422 — isian ditolak

Jangan hafal semua status dulu. Cukup tiga: 200/201 (sukses), 404 (tidak ketemu), 422 (data kotor).

Validasi dulu — tanpa framework

Kenapa belum langsung Form Request? Karena ide “cek dulu, baru proses” bisa dirasakan di PHP biasa. Kalau ide-nya sudah “klik”, cuplikan Laravel nanti terasa seperti bungkus yang sama.

<?php
$input = [
    "judul" => "",
    "tahun" => 2024,
];

$errors = [];

if (! isset($input["judul"]) || trim((string) $input["judul"]) === "") {
    $errors["judul"] = "Judul wajib diisi";
}

if (! isset($input["tahun"]) || ! is_numeric($input["tahun"])) {
    $errors["tahun"] = "Tahun harus angka";
} elseif ((int) $input["tahun"] < 1900 || (int) $input["tahun"] > 2100) {
    $errors["tahun"] = "Tahun di luar rentang";
}

header("Content-Type: application/json; charset=utf-8");

if ($errors !== []) {
    http_response_code(422);
    echo json_encode(["pesan" => "Data tidak valid", "errors" => $errors], JSON_UNESCAPED_UNICODE), PHP_EOL;
    exit;
}

http_response_code(201);
echo json_encode(["ok" => true, "buku" => $input], JSON_UNESCAPED_UNICODE), PHP_EOL;

Output:

{"pesan":"Data tidak valid","errors":{"judul":"Judul wajib diisi"}}

Awam: status 422 artinya “pintu ketemu, tapi isian ditolak”. Bedakan dari 404 (pintu/data tidak ada) di Laravel Routing & JSON (#56).

Kalau data bersih — status 201

Isian lengkap dan masuk akal boleh “diterima”:

<?php
$input = [
    "judul" => "Belajar PHP",
    "tahun" => 2024,
];

$errors = [];

if (! isset($input["judul"]) || trim((string) $input["judul"]) === "") {
    $errors["judul"] = "Judul wajib diisi";
}

if (! isset($input["tahun"]) || ! is_numeric($input["tahun"])) {
    $errors["tahun"] = "Tahun harus angka";
} elseif ((int) $input["tahun"] < 1900 || (int) $input["tahun"] > 2100) {
    $errors["tahun"] = "Tahun di luar rentang";
}

header("Content-Type: application/json; charset=utf-8");

if ($errors !== []) {
    http_response_code(422);
    echo json_encode(["pesan" => "Data tidak valid", "errors" => $errors], JSON_UNESCAPED_UNICODE), PHP_EOL;
    exit;
}

http_response_code(201);
echo json_encode(["ok" => true, "buku" => $input], JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT), PHP_EOL;

Output:

{
    "ok": true,
    "buku": {
        "judul": "Belajar PHP",
        "tahun": 2024
    }
}

Awam: 201 sering dipakai untuk “berhasil membuat sesuatu baru” (misalnya buku baru). Kalau bingung, anggap dulu “sukses” — angka pastinya bisa dirapikan nanti.

Request masuk -> penjaga cek -> JSON jawaban Browser / curl POST + JSON Penjaga validasi aturan JSON + status 201 atau 422
Penjaga bukan “menolak orang”. Ia menolak data yang tidak layak — supaya sistem tetap rapi.

Laravel — Request di route

Di project Laravel, cuplikan tipikal memakai $request->validate(...). File ini bukan dijalankan dengan php file.php — ia hidup di dalam project Laravel:

<?php
// Cuplikan Laravel (bukan file mandiri) — ide sama dengan validasi PHP di atas.
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;

Route::post('/api/buku', function (Request $request) {
    $data = $request->validate([
        'judul' => 'required|string|max:120',
        'tahun' => 'required|integer|min:1900|max:2100',
    ]);

    return response()->json(['ok' => true, 'buku' => $data], 201);
});

Awam:

  • Request $request = paket data masuk (isi formulir / isi JSON)
  • validate([...]) = daftar aturan penjaga
  • Teks aturan dipisah |: required = wajib, string = teks, integer = bilangan bulat, max:120 / min:1900 = batas panjang/nilai
  • Kalau gagal, Laravel biasanya menjawab status 422 + daftar error (tanpa kamu tulis manual)

Form Request — aturan pindah ke kelas

Kalau aturan makin panjang, jangan biarkan semuanya menumpuk di file route. Pindahkan ke kelas Form Request:

<?php
// Cuplikan Laravel — kelas penjaga (bukan file mandiri).
namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

class StoreBukuRequest extends FormRequest
{
    public function rules(): array
    {
        return [
            'judul' => 'required|string|max:120',
            'tahun' => 'required|integer|min:1900|max:2100',
        ];
    }
}
<?php
// Cuplikan Laravel — pakai Form Request di route.
use App\Http\Requests\StoreBukuRequest;
use Illuminate\Support\Facades\Route;

Route::post('/api/buku', function (StoreBukuRequest $request) {
    $data = $request->validated();

    return response()->json(['ok' => true, 'buku' => $data], 201);
});

Awam: validated() hanya mengembalikan field yang sudah lolos penjaga. Data kotor tidak ikut “nyelonong”.

Pola Dasar — Request & validasi

  1. 1
    Tahu data apa yang masuk Misalnya judul dan tahun untuk buku baru.
  2. 2
    Tulis aturan penjaga Wajib? String? Angka? Rentang tahun?
  3. 3
    Tolak dengan status jujur Data kotor -> 422. Jangan pura-pura 200.
  4. 4
    Pakai data yang sudah bersih validated() / array yang lolos cek — bukan data mentah dari request.
  5. 5
    Baru pikir penyimpanan & struktur kode Berikutnya: Controller, Service & Eloquent (#58) — pengatur kode, layanan, dan penyimpanan data setelah penjaga berdiri.

Kode lengkap — laravel_request_validasi_demo.php

Simpan dan jalankan: php laravel_request_validasi_demo.php. Ini meniru penjaga validasi tanpa server Laravel — supaya ide-nya terasa dulu.

Awam: fungsi validasiBuku() mengembalikan daftar kesalahan (kosong = lolos). Baris mixed $data artinya data bisa bermacam bentuk — tidak perlu dihafal. Fokus ke alur: cek -> tolak/terima.

<?php
/**
 * Demo ide Request & validasi (Seri 4 #57).
 * Di Laravel, ide yang sama hidup di Request::validate / Form Request.
 */

declare(strict_types=1);

function kirimJson(mixed $data, int $status = 200): void
{
    http_response_code($status);
    header("Content-Type: application/json; charset=utf-8");
    echo json_encode($data, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT), PHP_EOL;
}

function validasiBuku(array $input): array
{
    $errors = [];

    if (! isset($input['judul']) || trim((string) $input['judul']) === '') {
        $errors['judul'] = 'Judul wajib diisi';
    }

    if (! isset($input['tahun']) || ! is_numeric($input['tahun'])) {
        $errors['tahun'] = 'Tahun harus angka';
    } elseif ((int) $input['tahun'] < 1900 || (int) $input['tahun'] > 2100) {
        $errors['tahun'] = 'Tahun di luar rentang';
    }

    return $errors;
}

function demo(): void
{
    echo "=== POST kotor (judul kosong) ===", PHP_EOL;
    $kotor = ['judul' => '', 'tahun' => 2024];
    $errors = validasiBuku($kotor);
    if ($errors !== []) {
        kirimJson(['pesan' => 'Data tidak valid', 'errors' => $errors], 422);
    }

    echo "=== POST bersih ===", PHP_EOL;
    $bersih = ['judul' => 'Belajar PHP', 'tahun' => 2024];
    $errors = validasiBuku($bersih);
    if ($errors === []) {
        kirimJson(['ok' => true, 'buku' => $bersih], 201);
    }
}

demo();

Output yang diharapkan:

=== POST kotor (judul kosong) ===
{
    "pesan": "Data tidak valid",
    "errors": {
        "judul": "Judul wajib diisi"
    }
}
=== POST bersih ===
{
    "ok": true,
    "buku": {
        "judul": "Belajar PHP",
        "tahun": 2024
    }
}

Kesalahan umum

Gejala Penyebab tipikal Perbaikan
Data kotor tetap diproses Langsung pakai input mentah Validasi dulu; pakai hasil yang sudah bersih
Gagal validasi tapi status 200 Lupa status error Pakai 422 (atau biarkan Laravel yang mengurus)
Bingung 404 vs 422 Semua error dianggap “tidak ketemu” 404 = tidak ada; 422 = ada tapi isian ditolak
Aturan menumpuk di route Semua cek ditumpuk di dalam file route Pindah ke Form Request (#57 (ini))
Langsung loncat database Mau simpan sebelum penjaga siap Rapatkan validasi dulu, baru penyimpanan nanti

Latihan singkat

  1. Di demo PHP, tambah aturan: judul maksimal 40 karakter; uji dengan judul yang terlalu panjang.
  2. Ubah tahun jadi 1800 dan pastikan output menampilkan error rentang (bukan sukses).
  3. Di cuplikan Laravel, tambah field penulis wajib string di aturan validate.

FAQ singkat

Harus install Laravel dulu?
Untuk memahami ide: demo PHP di atas sudah cukup. Untuk latihan framework: buat project Laravel 11+ lalu tempel cuplikan route / Form Request.

Kenapa tidak cukup cek di tampilan browser saja?
Tampilan di browser (sering disebut frontend) bisa dilewati. Penjaga di server (API) tetap wajib — itu tempat keputusan yang bisa dipercaya.

Apa bedanya $request->validate dan Form Request?
Secara awam: sama-sama penjaga. Form Request memindahkan aturan ke kelas sendiri supaya file route / pengatur kode tetap tipis dan rapi.

Lanjut ke mana?
Berikutnya: Controller, Service & Eloquent (#58) — pengatur kode, layanan, dan penyimpanan data setelah penjaga berdiri.

Kesimpulan & langkah berikutnya

Request = paket masuk. Validasi = cek kelayakan. Form Request = rumah aturan penjaga. Status 422 = “isian ditolak dengan jujur”.

Artikel ini adalah #57 (ini) — penjaga input setelah Laravel Routing & JSON (#56) membuka pintu HTTP.

Seri 4 progress: langkah #57 (ini) · 6/8 menuju Capstone Laravel · stack Laravel 2/5 · prasyarat: Laravel Routing & JSON (#56) LIVE. Berikutnya: Controller, Service & Eloquent (#58).