Pendahuluan — satpam di pintu toko

Artikel ini adalah #59 (ini) di Seri 4: Pemrograman Web Lanjut v2. Di Routing & Jawaban JSON API Perpustakaan (#58) kamu sudah buka pintu HTTP dan jawab JSON. Sekarang langkah 4/8: jaga isi permintaan yang masuk lewat pintu supaya data tidak berantakan.

Awam: pintu sudah ada. Satpam (validasi) memeriksa slip formulir: judul buku ada? penulis diisi? Kalau slip kosong/kotor, satpam menolak dengan jawaban rapi — bukan menerima data asal-asalan.

Domain tetap perpustakaan mini. Hari ini kita menerima permintaan tambah buku (POST) dengan aturan sederhana. Menyimpan ke tabel database dan login datang belakangan.

Prasyarat: sudah selesai Routing & Jawaban JSON API Perpustakaan (#58) — route GET /api/buku pernah jalan. Fondasi di Struktur Folder, .env & Artisan Laravel (#57) dan Instal PHP, Composer & Proyek Laravel (#56). Pakai Laravel 13+ — butuh PHP 8.3+.

Spesifikasi fitur — apa yang kita kuasai?

Daftar singkat yang bisa kamu centang di akhir artikel:

  1. Mengerti Request sebagai isi permintaan yang masuk lewat pintu HTTP.
  2. Memakai $request->validate([...]) untuk cek judul & penulis.
  3. Mengenal Form Request sebagai file aturan khusus (lebih rapi).
  4. Membaca jawaban gagal 422 sebagai “data belum lolos cek”.

Awam: urutan nyaman: paham Request -> cek di route -> pindah aturan ke Form Request -> uji gagal & sukses. Belum perlu menyimpan ke tabel database di sini.

Istilah — ringkas untuk satpam & slip

Istilah Arti awam Contoh di artikel ini
Request Isi permintaan yang datang lewat pintu Judul & penulis buku baru
Validasi Cek slip: wajib diisi? bentuknya benar? judul & penulis required
Form Request File aturan satpam khusus (bukan di route) StoreBukuRequest
Status 422 Ditolak karena data belum lolos cek Judul kosong -> JSON error
HTTP POST Permintaan “tolong terima data baru” POST /api/buku

Urutan belajar: baca istilah -> lihat alur -> cek di PHP -> Form Request -> uji.

Kenapa jaga input dulu?

Tanpa validasi, API menerima judul kosong atau data aneh. Nanti saat menyimpan ke database, masalahnya makin sulit dilacak.

Awam: satpam memeriksa slip sebelum buku masuk rak. Lebih baik ditolak di pintu daripada rak penuh kertas kosong.

Artikel ini tetap install-dari-nol untuk validasi: tidak ada paket baru. Kita pakai Request & Artisan bawaan Laravel. Fondasi PHP/Composer/Laravel + denah + routing sudah di Instal PHP, Composer & Proyek Laravel (#56), Struktur Folder, .env & Artisan Laravel (#57), dan Routing & Jawaban JSON API Perpustakaan (#58).

Alur — dari POST sampai lolos/ditolak

Alur: POST -> Request -> Cek aturan -> JSON Browser POST /api/buku Request isi slip Cek aturan JSON OK / 422 Setelah pintu JSON siap: terima POST, cek judul & penulis, lalu jawab sukses atau 422. Menyimpan ke tabel & login datang belakangan. Urutan ini mengikuti langkah #59 (ini) — belum pengatur terpisah / tabel database.
#59 (ini): POST -> Request -> cek aturan -> JSON OK/422.

Persiapan — toko & pintu masih hidup

Alat yang dipakai di artikel ini (semua sudah dari fondasi Instal PHP, Composer & Proyek Laravel (#56) / Struktur Folder, .env & Artisan Laravel (#57) — tidak ada unduhan wajib baru):

  • Explorer — pastikan folder perpustakaan-api ada; cek routes/web.php dan nanti bootstrap/app.php.
  • Terminal — Laragon: menu Terminal · XAMPP: tombol Shell. Jangan asal CMD/PowerShell dari Start Menu (PATH PHP bisa hilang).
  • Terminal kedua — wajib: terminal pertama = php artisan serve. Terminal kedua = uji curl.exe / PowerShell / edit file.
  • Editor teks — Notepad / VS Code. Tip dari terminal kedua: notepad routes\web.php atau notepad bootstrap\app.php (harus sudah cd ke folder proyek).
  • Browser — cukup untuk memastikan toko hidup (seperti di Routing & Jawaban JSON API Perpustakaan (#58)). Menguji POST tidak cukup ketik URL di bilah alamat; butuh perintah di terminal (atau alat uji API berjendela).

Buka terminal Laragon/Shell XAMPP (terminal pertama), masuk folder proyek:

cd C:\laragon\www\perpustakaan-api

Di XAMPP biasanya: cd C:\xampp\htdocs\perpustakaan-api. Sesuaikan path jika foldermu beda. Lalu:

php artisan serve

Biarkan jendela terminal ini tetap hidup (lampu toko). Cara buka terminal kedua: Laragon — klik menu Terminal sekali lagi; XAMPP — klik tombol Shell sekali lagi. Jangan matikan yang sedang serve.

Pastikan GET /api/buku dari Routing & Jawaban JSON API Perpustakaan (#58) masih ada (boleh dicek sekali di browser: http://127.0.0.1:8000/api/buku). Hari ini kita menambah pintu POST.

Awam: GET = “tolong kirim daftar”. POST = “tolong terima data baru”. Matikan toko sementara dengan Ctrl+C di terminal yang menjalankan serve.

PHP dulu — cek slip tanpa Laravel

Sebelum cuplikan Laravel, rasakan ide satpam di PHP biasa. Cuplikan di bawah boleh dibaca dulu — belum wajib disimpan. Kalau mau menjalankan di terminal, pakai file demo di bagian Demo satpam nanti (lebih lengkap).

<?php

declare(strict_types=1);

$input = [
    'judul' => '',
    'penulis' => 'Ayu',
];

$errors = [];
if (trim((string) ($input['judul'] ?? '')) === '') {
    $errors['judul'] = 'Judul wajib diisi';
}
if (trim((string) ($input['penulis'] ?? '')) === '') {
    $errors['penulis'] = 'Penulis wajib diisi';
}

if ($errors !== []) {
    echo "Status awam: 422 (data belum lolos cek)", PHP_EOL;
    echo json_encode(['message' => 'Validasi gagal', 'errors' => $errors], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE), PHP_EOL;
} else {
    echo "Status awam: OK — slip bersih", PHP_EOL;
}

Awam: array $errors = catatan satpam. Kalau ada isi, tolak dulu. Ide yang sama dipakai Laravel lewat validate / Form Request.

Validasi cepat di route

Buka editor teks, buka file routes/web.php di folder proyek. Tambahkan route POST (cuplikan — tempel di bawah route GET kamu), lalu simpan file:

// Cuplikan routes/web.php — POST tambah buku (validasi di route)
use Illuminate\Http\Request;

Route::post('/api/buku', function (Request $request) {
    $data = $request->validate([
        'judul' => ['required', 'string', 'max:120'],
        'penulis' => ['required', 'string', 'max:80'],
    ]);

    return response()->json([
        'message' => 'Slip bersih — buku siap diproses (belum disimpan ke tabel)',
        'data' => $data,
    ], 201);
});

Awam: required = wajib diisi. max:120 = maksimal 120 huruf/angka (supaya judul tidak kepanjangan). Kalau gagal, Laravel otomatis menjawab JSON error (sering status 422). Angka 201 = “data baru diterima” (belum berarti sudah di rak database).

Form Request — aturan di file sendiri

Kalau aturan makin panjang, pindahkan ke file khusus supaya route tidak penuh. Di terminal kedua (folder perpustakaan-api masih sama; serve tetap hidup di terminal pertama):

php artisan make:request StoreBukuRequest

Buka file yang dibuat di editor (biasanya app/Http/Requests/StoreBukuRequest.php). Di dalam kelas itu sudah ada kerangka fungsi — ganti isi authorize dan rules menjadi seperti cuplikan berikut, lalu simpan:

// Cuplikan StoreBukuRequest — aturan satpam
public function authorize(): bool
{
    return true; // belum login — semua boleh coba kirim (untuk belajar)
}

public function rules(): array
{
    return [
        'judul' => ['required', 'string', 'max:120'],
        'penulis' => ['required', 'string', 'max:80'],
    ];
}

Lalu di route, minta Laravel memakai satpam itu:

// Cuplikan routes/web.php — pakai Form Request
use App\Http\Requests\StoreBukuRequest;

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

    return response()->json([
        'message' => 'Slip bersih lewat Form Request',
        'data' => $data,
    ], 201);
});

Awam: validated() = ambil hanya field yang sudah lolos cek. authorize(): true di sini berarti “belum ada kartu anggota” — login dibahas belakangan. Artisan make:request sudah ada di proyek Laravel-mu (fondasi Instal PHP, Composer & Proyek Laravel (#56) / Struktur Folder, .env & Artisan Laravel (#57)) — tidak perlu unduh paket baru.

Cek izin api/* — supaya POST tidak ditolak 419

Sebelum menguji, ada satu satpam lobi lain yang perlu kita sapa. Laravel punya penjaga bawaan bernama CSRF yang melindungi formulir web: ia menolak semua kiriman POST (juga PUT dan DELETE) yang tidak datang dari halaman browser milik situs itu sendiri.

Masalahnya, kita menguji dari terminal pakai curl.exe — dan terminal bukan browser. Jadi tanpa izin khusus, uji POST di bawah akan dijawab:

{"message":"CSRF token mismatch."}

dengan kode 419. Ini bukan berarti slip atau satpam Form Request-mu salah — pintu kita bahkan belum sempat dijalankan.

Awam: bayangkan satpam lobi yang hanya mengizinkan orang masuk lewat pintu depan resmi. Kita datang dari pintu samping (terminal), jadi dia menahan kita. Kita perlu bilang: “alamat yang berawalan api/ itu memang dilayani dari luar lobi, tolong jangan ditahan.”

Kalau di dalam withMiddleware sudah ada tulisan 'api/*' — jangan ubah apa-apa; lanjut ke bagian Uji di bawah. Izin ini cukup sekali per proyek.

Belum ada? Pastikan terminal kedua sudah di folder proyek (cd ke perpustakaan-api, sama seperti saat serve). Lalu buka: notepad bootstrap\app.php. Cari bagian ->withMiddleware(...).

Awam — PENTING: jangan menempel blok ->withMiddleware(...) baru di dalam fungsi yang sudah ada (itu membuat fungsi bersarang dan Laravel error). Kerja di dalam fungsi yang sudah ada: kalau isinya masih //, hapus baris // itu; kalau sudah ada baris lain, biarkan. Lalu tempel hanya baris di bawah ini ke dalam fungsi:

// Tempel di dalam withMiddleware yang sudah ada — jangan buat withMiddleware baru
$middleware->preventRequestForgery(except: [
    'api/*',
]);

Setelah disimpan, isi fungsimu kira-kira seperti ini (boleh ada baris lain di atas/bawahnya):

->withMiddleware(function (Middleware $middleware): void {
    $middleware->preventRequestForgery(except: [
        'api/*',
    ]);
})

Simpan file. Awam: except artinya “kecuali”. Bacanya: “periksa CSRF untuk semua halaman, kecuali alamat berawalan api/.” Tanda * = “apa pun setelahnya”, jadi api/buku ikut dikecualikan.

Catatan nama: di Laravel 13 nama resminya preventRequestForgery. Nama lama validateCsrfTokens masih diterima dan artinya sama — kalau kamu melihatnya di tempat lain, itu bukan kesalahan.

Apakah ini membuat API-ku tidak aman? Tidak. CSRF adalah pagar khusus formulir browser. Pengecualian api/* resmi untuk API yang diuji dari luar browser. Siapa yang boleh masuk pintu API dibahas belakangan di seri ini — hari ini kita hanya memastikan satpam lobi tidak menahan uji dari terminal.

Setiap kali menyimpan bootstrap\app.php, matikan serve di terminal pertama dengan Ctrl+C, lalu nyalakan lagi php artisan serve. File itu hanya dibaca saat aplikasi mulai. Izin ini berlaku untuk semua artikel berikutnya di seri ini.

Uji gagal & sukses

Pastikan php artisan serve masih hidup di terminal pertama. Buka terminal kedua untuk menguji. Kita kirim data JSON — bilah alamat browser saja tidak cukup untuk POST.

Opsi A — curl di terminal (Windows 10/11 biasanya sudah punya; di PowerShell ketik curl.exe agar tidak tertukar dengan perintah lain):

# Sengaja kosongkan judul — harus ditolak (422)
curl.exe -s -X POST http://127.0.0.1:8000/api/buku ^
  -H "Content-Type: application/json" ^
  -H "Accept: application/json" ^
  -d "{\"judul\":\"\",\"penulis\":\"Ayu\"}"

Awam — soal tanda ^ di ujung baris: tanda itu berarti “perintah ini masih lanjut ke baris berikutnya”, dan berlaku di CMD / Shell XAMPP / Terminal Laragon. Kalau kamu memakai PowerShell, ^ tidak dikenal — ganti setiap ^ dengan tanda backtick `, atau yang paling aman: ketik seluruh perintah dalam satu baris panjang tanpa ^. Di Mac/Linux, ganti ^ dengan \ atau satu baris.

# Slip bersih — harus 201 + JSON data
curl.exe -s -X POST http://127.0.0.1:8000/api/buku ^
  -H "Content-Type: application/json" ^
  -H "Accept: application/json" ^
  -d "{\"judul\":\"Belajar Laravel\",\"penulis\":\"Budi\"}"

Opsi B — PowerShell (kalau kutip di curl ribet), tempel satu perintah per uji:

# Judul kosong — harap 422
Invoke-RestMethod -Method Post -Uri http://127.0.0.1:8000/api/buku `
  -ContentType 'application/json' -Headers @{ Accept = 'application/json' } `
  -Body '{"judul":"","penulis":"Ayu"}'
# Slip bersih — harap data + status sukses
Invoke-RestMethod -Method Post -Uri http://127.0.0.1:8000/api/buku `
  -ContentType 'application/json' -Headers @{ Accept = 'application/json' } `
  -Body '{"judul":"Belajar Laravel","penulis":"Budi"}'

Opsi C — alat uji API berjendela (misalnya Postman atau Insomnia — opsional, gratis). Isi: metode POST, URL http://127.0.0.1:8000/api/buku, header Accept: application/json + Content-Type: application/json, body JSON sama seperti di atas. Ide utamanya sama: kirim slip, baca OK atau 422.

Awam: baris Accept: application/json meminta jawaban berbentuk JSON (bukan halaman HTML error). Kalau muncul error HTML panjang, hampir selalu karena header Accept belum ada.

Pola Dasar — empat langkah satpam bersih

  1. 1
    Nyalakan serve
    php artisan serve + pintu GET dari artikel routing JSON sebelumnya masih ada.
  2. 2
    Cek di route dulu
    POST /api/buku + $request->validate untuk judul & penulis.
  3. 3
    Pindah ke Form Request
    php artisan make:request StoreBukuRequest lalu validated().
  4. 4
    Uji 422 & 201
    Di terminal kedua: curl.exe / PowerShell / alat berjendela — judul kosong (ditolak) lalu slip bersih.

Demo satpam — file mandiri

Latihan ide satpam tanpa menyentuh Laravel:

  1. Buka editor teks, buat file baru, tempel cuplikan di bawah, simpan sebagai laravel_request_validasi_api_demo.php (boleh di Desktop atau folder latihan — tidak harus di dalam perpustakaan-api).
  2. Buka terminal di folder tempat file itu disimpan (Windows Explorer: Shift+klik kanan folder -> “Open in Terminal” / “Buka di Terminal”, atau cd manual).
  3. Jalankan: php laravel_request_validasi_api_demo.php — layar harus menampilkan kasus 422 lalu OK.

File ini hanya mensimulasikan cek slip — tidak mengubah proyek Laravel-mu:

<?php

declare(strict_types=1);

/**
 * Demo satpam Request/validasi — simulasi teks untuk awam.
 * File: laravel_request_validasi_api_demo.php
 */

function cekSlip(array $input): array
{
    $errors = [];
    if (trim((string) ($input['judul'] ?? '')) === '') {
        $errors['judul'] = 'Judul wajib diisi';
    }
    if (trim((string) ($input['penulis'] ?? '')) === '') {
        $errors['penulis'] = 'Penulis wajib diisi';
    }

    return $errors;
}

function demo(): void
{
    $kasus = [
        ['judul' => '', 'penulis' => 'Ayu'],
        ['judul' => 'Dasar Laravel', 'penulis' => 'Budi'],
    ];

    foreach ($kasus as $i => $input) {
        $n = $i + 1;
        $errors = cekSlip($input);
        echo "=== Kasus {$n} ===", PHP_EOL;
        if ($errors !== []) {
            echo "Hasil: 422 — data belum lolos cek", PHP_EOL;
            echo json_encode(['errors' => $errors], JSON_UNESCAPED_UNICODE), PHP_EOL;
        } else {
            echo "Hasil: OK — slip bersih", PHP_EOL;
            echo json_encode(['data' => $input], JSON_UNESCAPED_UNICODE), PHP_EOL;
        }
        echo PHP_EOL;
    }
}

demo();

Awam: demo() hanya menampilkan dua kasus di terminal. Setelah paham, kerjakan langkah yang sama di routes/web.php / Form Request. declare(strict_types=1); membuat tipe lebih ketat — boleh diikuti, tidak wajib dihafal.

Kesalahan umum

Gejala Penyebab tipikal Perbaikan awam
CSRF token mismatch / kode 419 Izin api/* di bootstrap\app.php belum dipasang, atau serve belum dinyalakan ulang setelah menyimpannya Pasang preventRequestForgery(except: ['api/*']) (jangan timpa baris lain di withMiddleware), lalu Ctrl+C dan php artisan serve lagi
HTML error page, bukan JSON Lupa baris Accept: application/json Tambah Accept saat uji POST
curl tidak dikenal / error aneh di PowerShell Perintah tertukar atau belum memakai curl.exe Pakai curl.exe, Opsi B PowerShell, atau alat berjendela (Opsi C)
Connection refused / gagal sambung php artisan serve belum hidup / terminal salah folder Nyalakan serve di folder perpustakaan-api, uji lagi di terminal kedua
405 Method Not Allowed Masih memakai GET untuk kirim data Pakai POST ke /api/buku
Class StoreBukuRequest not found File belum dibuat / nama kelas atau folder salah Jalankan ulang make:request, cek folder app/Http/Requests
Selalu 422 meski sudah isi Nama field beda (title vs judul) Samakan kunci JSON dengan aturan rules()

Latihan

  1. Jalankan demo PHP di atas — pastikan kasus judul kosong ditolak.
  2. Buat StoreBukuRequest, hubungkan ke POST /api/buku, uji 422 lalu 201.
  3. Jelaskan ke teman: beda singkat “pintu (route)” dan “satpam (validasi/Form Request)” dengan bahasa toko.

FAQ

Harus Form Request dari hari pertama?
Tidak. Boleh mulai dari $request->validate di route. Form Request berguna saat aturan makin panjang atau dipakai di banyak tempat.

Harus install Postman?
Tidak wajib. Terminal + curl.exe / PowerShell sudah cukup. Alat berjendela hanya opsi kalau kutip di terminal terasa ribet.

Kenapa browser saja tidak cukup?
Browser mudah untuk GET (ketik URL). POST butuh mengirim body JSON — itu kerja terminal atau alat uji API, bukan bilah alamat.

Kenapa belum disimpan ke database?
Supaya fokus satu hal: menjaga input. Menyimpan rapi (pengatur kode + tabel) datang setelah fondasi satpam nyaman.

Apa hubungan dengan routing?
Routing & Jawaban JSON API Perpustakaan (#58) memasang pintu. #59 (ini) memasang satpam di pintu itu.

Ke mana setelah ini?
Berikutnya: Controller, Service & Eloquent Laravel (#60) — merapikan loket, dapur, dan cara baca baris di tabel.

Kesimpulan

Kamu sudah menjaga input API di Laravel: memahami Request, memakai validasi, mengenal Form Request, dan membedakan jawaban OK vs 422. Ini langkah 4/8 jalur Laravel di Seri 4.

Seri 4 progress: langkah #59 (ini) · 4/8 jalur Laravel · prasyarat: Routing & Jawaban JSON API Perpustakaan (#58) LIVE. Berikutnya: Controller, Service & Eloquent Laravel (#60).