Pendahuluan — melengkapi rak perpustakaan
Artikel ini adalah #63 (ini) di Seri 4: Pemrograman Web Lanjut v2 — langkah 8/8, penutup jalur Laravel dari nol.
Di Capstone: API Perpustakaan (#62) staf sudah bisa membaca katalog dan menambah buku dengan kartu. Tapi hidup nyata butuh dua hal lagi: memperbaiki data yang salah tulis, dan menghapus buku yang sudah tidak ada di rak.
Awam: hari ini kita menambah dua loket terakhir. Loket ubah = memperbaiki kartu katalog yang salah tulis. Loket hapus = menarik buku dari rak. Keduanya wajib pakai kartu staf, sama seperti loket tambah.
Prasyarat: sudah selesai Capstone: API Perpustakaan (#62) (baca + login + tambah sudah jalan), Auth API Dasar: Login & Kartu Anggota (#61) (kartu Sanctum), dan Request & Form Request: Menjaga Input API (#59) (satpam slip). Fondasi Instal PHP, Composer & Proyek Laravel (#56) / Struktur Folder,
.env& Artisan Laravel (#57). Pakai Laravel 13+ — butuh PHP 8.3+.
Spesifikasi fitur — apa yang “selesai” hari ini?
Dua kalimat saja:
- Ubah satu buku —
PUT /api/buku/1dengan kartu Bearer + slip judul/penulis yang valid. - Hapus satu buku —
DELETE /api/buku/1dengan kartu Bearer (tidak perlu slip).
Angka 1 di alamat itu adalah nomor buku (kolom id di database). Kalau nomornya tidak ada di rak, jawabannya 404 — bukan error merah.
Awam: setelah hari ini, rak perpustakaanmu punya empat gerakan lengkap: lihat, tambah, perbaiki, buang. Itulah yang orang sebut CRUD.
Istilah — ringkas ubah & hapus
| Istilah | Arti awam | Catatan |
|---|---|---|
| CRUD | Empat gerakan data: lihat, tambah, ubah, buang | Hari ini melengkapi dua yang terakhir |
| PUT | Cara HTTP untuk “ganti isi catatan ini” | PUT /api/buku/1 |
| DELETE | Cara HTTP untuk “buang catatan ini” | DELETE /api/buku/1 |
| Nomor di alamat | Angka penunjuk buku mana yang dimaksud | Ditulis {id} saat mendaftarkan pintu |
| Bearer | Cara menunjukkan kartu staf lewat header | Ditulis Authorization: Bearer <token> (dari Auth API Dasar: Login & Kartu Anggota (#61)) |
| 404 | Nomor itu tidak ada di rak | Jawaban wajar, bukan aplikasi rusak |
| 419 | Laravel curiga kiriman datang dari luar browser | Muncul kalau pengecualian api/* belum dipasang |
Persiapan — alat yang kamu buka
Alat yang dipakai di artikel ini (fondasi Instal PHP, Composer & Proyek Laravel (#56) / Struktur Folder, .env & Artisan Laravel (#57) — tidak ada unduhan Composer baru hari ini):
- Explorer — pastikan folder
perpustakaan-apiada; cekBukuController.php,routes/web.php, danbootstrap/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 = ujicurl.exe/ PowerShell (login + ubah + hapus + baca). - Editor teks — Notepad / VS Code — menambah method
update&destroy, dua route baru, dan satu izin kecil dibootstrap\app.php. Tip:notepad app\Http\Controllers\BukuController.phpdari terminal kedua (ganti nama file sesuai yang mau dibuka). - Browser — hanya untuk cek lampu toko dan melihat daftar buku (GET). Ubah & hapus tidak bisa diuji dari bilah alamat browser.
Cara buka terminal kedua (baru pertama kali buka dua terminal sekaligus? ini caranya, jangan tutup yang pertama): Laragon — klik menu Terminal sekali lagi di jendela utama Laragon, sebuah jendela terminal baru akan muncul terpisah dari yang pertama. XAMPP — di XAMPP Control Panel, klik tombol Shell sekali lagi, jendela Shell kedua akan terbuka. Kedua jendela boleh hidup bersamaan — jendela pertama tetap menjalankan php artisan serve, jendela kedua kamu pakai untuk mengetik perintah lain.
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.
Nyalakan lampu toko di terminal pertama:
php artisan serve
Biarkan jendela itu hidup. Buka terminal kedua (caranya sudah dijelaskan di atas), cd ke folder proyek yang sama — di sini kamu menguji ubah & hapus.
Awam: Terminal 1 = lampu toko. Terminal 2 = tangan menguji dua loket baru. Editor = menulis update & destroy. Browser = cuma pengintip daftar.
Install-dari-nol: jika Sanctum / login belum ada, selesaikan dulu Auth API Dasar: Login & Kartu Anggota (#61). Jika PHP/Composer belum dikenal terminal, kembali ke Instal PHP, Composer & Proyek Laravel (#56).
Cek cepat — potongan yang harus sudah ada
Sebelum menambah loket baru, pastikan di proyekmu sudah pernah jalan:
GET /api/bukumenampilkan daftar (Controller, Service & Eloquent Laravel (#60))POST /api/loginmemberitoken(Auth API Dasar: Login & Kartu Anggota (#61))POST /api/bukuber-kartu berhasil menambah buku (Capstone: API Perpustakaan (#62))- File
app\Models\Buku.phppunya barisprotected $fillable = ['judul', 'penulis'];(dari Controller, Service & Eloquent Laravel (#60))
Kalau salah satu belum jalan, perbaiki dulu di artikel asalnya. Loket ubah & hapus hanya menumpang jalur yang sudah ada.
Awam — kenapa baris $fillable itu wajib dicek? Baris itu adalah daftar kolom yang boleh ditulis sekaligus. Loket ubah menulis judul dan penulis dalam satu gerakan, jadi kalau daftarnya kosong, Laravel diam-diam membuang perubahanmu: jawabannya tetap “Buku diperbarui”, tapi isi buku tidak berubah sama sekali. Ini satu-satunya kesalahan hari ini yang tidak memberi pesan merah, jadi buka file itu dan pastikan barisnya ada.
Nomor buku di alamat — dari mana angkanya?
Ini pertanyaan pertama pemula: “PUT /api/buku/1 — angka 1 itu saya karang sendiri?” Tidak. Angka itu diambil dari daftar buku.
Di terminal kedua, lihat dulu isi rak:
curl.exe -s http://127.0.0.1:8000/api/buku ^
-H "Accept: application/json"
Di jawaban JSON, tiap buku punya kunci "id". Itulah nomor buku. Kalau buku “Bumi” punya "id": 3, maka alamat untuk mengubahnya adalah /api/buku/3.
Awam: {id} saat mendaftarkan route adalah tempat kosong. Saat menguji, tempat kosong itu kamu isi angka nyata dari daftar. Salah nomor = jawaban 404, bukan aplikasi rusak.
Kalau jawabannya "data": [] alias kosong — berarti raknya memang belum berisi buku, dan itu wajar kalau di Controller, Service & Eloquent Laravel (#60) kamu belum sempat mengisi baris. Tidak ada nomor yang bisa diubah/dihapus. Isi dulu raknya: kerjakan bagian “Uji ubah & hapus” di bawah sampai langkah 1) Login untuk mendapat kartu, lalu tambah satu-dua buku dengan POST /api/buku mengikuti Capstone: API Perpustakaan (#62). Setelah rak berisi, kembali ke sini untuk mencatat "id"-nya.
Loket ubah — method update
Buka notepad app\Http\Controllers\BukuController.php. Jangan hapus baris namespace dan kerangka class BukuController extends Controller — kita hanya menambah.
Bagian 1 — baris use, letaknya DI ATAS class (sejajar dengan baris use yang sudah ada, bukan di dalam class):
use App\Http\Requests\StoreBukuRequest; // slip yang sama boleh dipakai ulang
use App\Models\Buku;
use Illuminate\Http\JsonResponse;
Awam — PENTING, jangan menulis dua kali: kalau salah satu baris di atas sudah ada di filemu, lewati baris itu. Biasanya Illuminate\Http\JsonResponse dan App\Models\Buku sudah kamu tulis sejak Controller, Service & Eloquent Laravel (#60), dan StoreBukuRequest sejak Capstone: API Perpustakaan (#62) — jadi sering kali tidak ada yang perlu ditambah di bagian ini. Kalau ditulis dua kali, PHP berhenti dengan pesan Cannot use ... because the name is already in use.
Awam — kenapa harus di atas class? Baris use di atas class berarti “ambil file dari folder lain”. Baris use yang ditulis di dalam class artinya lain sama sekali (menempelkan trait), dan PHP akan mengeluh Trait "App\Models\Buku" not found. Jadi perhatikan betul letaknya.
Bagian 2 — method update, letaknya DI DALAM class, di sebelah store yang sudah kamu buat di Capstone: API Perpustakaan (#62):
// Cuplikan BukuController — loket ubah buku (di dalam class)
public function update(StoreBukuRequest $request, int $id): JsonResponse
{
$buku = Buku::find($id);
if (! $buku) {
return response()->json(['message' => 'Buku tidak ditemukan'], 404);
}
$buku->update($request->validated());
return response()->json([
'message' => 'Buku diperbarui',
'data' => $buku,
]);
}
Awam: tiga langkah saja — cari buku dari nomor, kalau tidak ada jawab 404, kalau ada tulis ulang isinya lalu jawab JSON. Satpam slip (StoreBukuRequest) tetap jalan lebih dulu, jadi judul kosong tidak akan lolos.
Catatan: nama Form Request sesuaikan dengan file di Explorer-mu. Kalau belum ada, buat mengikuti Request & Form Request: Menjaga Input API (#59).
“Lho, kok tidak lewat BukuService?” Di Controller, Service & Eloquent Laravel (#60) kita memakai “dapur” (BukuService) supaya loket tidak sibuk mengurus tabel. Di sini Buku::find($id) dipanggil langsung dari loket supaya cuplikannya pendek dan kamu melihat jelas di baris mana 404 lahir. Kalau kamu sudah punya BukuService, silakan pindahkan find/update/delete ke sana sebagai method ubah() dan hapus() — hasilnya sama, dapurnya lebih rapi.
Loket hapus — method destroy
Masih di file yang sama, tetap di dalam class, tambahkan method destroy di bawah update. Bagian ini tidak butuh baris use baru — semuanya sudah tersedia dari langkah sebelumnya:
// Cuplikan BukuController — loket hapus buku (di dalam class)
public function destroy(int $id): JsonResponse
{
$buku = Buku::find($id);
if (! $buku) {
return response()->json(['message' => 'Buku tidak ditemukan'], 404);
}
$buku->delete();
return response()->json(['message' => 'Buku dihapus']);
}
Simpan file. Awam: hapus tidak butuh slip judul/penulis — cukup nomor buku dan kartu staf. Karena itu destroy tidak memakai Form Request.
Sambungkan pintu di routes
Buka notepad routes\web.php. Yang perlu kamu ketik hanya dua baris bertanda BARU di bawah ini — sisanya sudah ada di filemu sejak artikel sebelumnya, ditampilkan supaya kamu tahu letak persisnya:
// Cuplikan routes/web.php — bentuk AKHIR file setelah hari ini
use App\Http\Controllers\AuthController; // sudah ada dari Auth API
use App\Http\Controllers\BukuController; // sudah ada dari Controller/Service
Route::get('/api/buku', [BukuController::class, 'index']); // sudah ada dari Controller/Service
Route::post('/api/login', [AuthController::class, 'login']); // sudah ada dari Auth API
Route::middleware('auth:sanctum')->group(function () {
Route::get('/api/saya', [AuthController::class, 'saya']); // sudah ada dari Auth API
Route::post('/api/buku', [BukuController::class, 'store']); // sudah ada dari Capstone
Route::put('/api/buku/{id}', [BukuController::class, 'update']); // <-- BARU hari ini
Route::delete('/api/buku/{id}', [BukuController::class, 'destroy']); // <-- BARU hari ini
});
Awam — jangan menempel seluruh blok ini mentah-mentah. Kalau kamu menempelnya di bawah isi yang sudah ada, baris use jadi dobel dan PHP berhenti dengan Cannot use ... name is already in use. Kalau kamu menimpa seluruh isi file, route lain (termasuk halaman biasa milik proyekmu) bisa ikut hilang. Cari kelompok auth:sanctum yang sudah ada di filemu, lalu sisipkan dua baris Route::put dan Route::delete di dalamnya.
Simpan. Pastikan serve masih hidup di terminal pertama.
Awam: {id} itu tempat kosong untuk nomor buku. Dua pintu baru sengaja ditaruh di dalam auth:sanctum — tanpa kartu, tamu tidak boleh mengubah atau menghapus apa pun.
Pastikan Laravel benar-benar melihat dua pintu baru itu. Di terminal kedua (biarkan serve hidup di terminal pertama), jalankan:
php artisan route:list --path=api/buku
Kamu harus melihat empat baris: GET, POST, PUT, dan DELETE. Kalau PUT/DELETE belum muncul, berarti routes\web.php belum tersimpan atau kata kerjanya salah ketik — perbaiki dulu sebelum lanjut menguji.
Cek izin api/* — supaya PUT & DELETE tidak ditolak 419
Satpam lobi CSRF sudah kita pasang sejak Request & Form Request: Menjaga Input API (#59) (dan dicek ulang di Auth API Dasar: Login & Kartu Anggota (#61) / Capstone: API Perpustakaan (#62)). Tugasnya melindungi formulir web: menolak kiriman POST, PUT, dan DELETE yang tidak datang dari halaman browser milik situs itu sendiri.
Karena kita menguji dari terminal (bukan browser), tanpa izin api/* jawabannya:
{"message":"CSRF token mismatch."}
dengan kode 419. Ini bukan berarti kodemu salah — pintu kita bahkan belum sempat dijalankan.
Kalau di dalam withMiddleware sudah ada 'api/*' — lewati langkah ini. Cukup sekali per proyek; pintu baru hari ini ikut tercakup.
Belum ada / kamu loncat ke artikel ini? Pastikan terminal kedua sudah di folder proyek (cd ke perpustakaan-api), lalu notepad bootstrap\app.php. Jangan menempel blok ->withMiddleware(...) baru di dalam fungsi yang sudah ada. Kerja di dalam fungsi itu: hapus // kalau masih ada, biarkan baris lain, lalu tempel hanya:
// Tempel di dalam withMiddleware yang sudah ada — jangan buat withMiddleware baru
$middleware->preventRequestForgery(except: [
'api/*',
]);
Hasil yang benar kira-kira begini (boleh ada baris lain di sekitarnya):
->withMiddleware(function (Middleware $middleware): void {
$middleware->preventRequestForgery(except: [
'api/*',
]);
})
Simpan. Awam: except = “kecuali”. Tanda * = “apa pun setelahnya”, jadi api/buku/3 dan api/login ikut. Di Laravel 13 nama resminya preventRequestForgery; nama lama validateCsrfTokens masih sama artinya.
Apakah ini membuat API-ku tidak aman? Tidak. Pagar yang menjaga ubah & hapus adalah petugas kartu Sanctum (auth:sanctum) — tanpa kartu tetap ditolak. CSRF adalah satpam lobi untuk formulir browser; API ber-token memang memakai pagar jenis yang berbeda.
Setiap kali menyimpan bootstrap\app.php, matikan serve di terminal pertama (Ctrl+C) lalu nyalakan lagi php artisan serve. File itu hanya dibaca saat aplikasi mulai.
Uji ubah & hapus di terminal kedua
Pintu PUT dan DELETE tidak bisa dicoba dari bilah alamat browser — browser hanya bisa GET. Jadi uji lewat terminal kedua (atau alat berjendela seperti Postman di Opsi C).
Pastikan user uji masih ada. Jalankan di terminal kedua (biarkan serve tetap hidup di terminal pertama) — ini satu tembakan, bukan chat panjang:
php artisan tinker --execute="\App\Models\User::updateOrCreate(['email'=>'staf@perpustakaan.test'], ['name'=>'Staf Mini','password'=>bcrypt('password')]);"
Pilih salah satu opsi di bawah (A, B, atau C) — jangan menjalankan dua opsi pada nomor buku yang sama, karena buku yang sudah dihapus di percobaan pertama tidak ada lagi di percobaan kedua (jawabannya akan 404).
Opsi A — curl.exe (Windows 10/11 biasanya sudah punya; ketik curl.exe agar tidak tertukar di PowerShell):
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 ^ sama sekali.
1) Login — ambil token
curl.exe -s -X POST http://127.0.0.1:8000/api/login ^
-H "Content-Type: application/json" ^
-H "Accept: application/json" ^
-d "{\"email\":\"staf@perpustakaan.test\",\"password\":\"password\"}"
Awam — salin token: di jawaban JSON cari kunci "token". Salin hanya string di antara tanda kutip. Jangan salin kata Bearer dari JSON — kata Bearer ditulis di header. Ganti GANTI_DENGAN_TOKEN di bawah.
Awam — cara salin teks dari terminal Windows: blok teks dengan klik kiri lalu tahan sambil digeser (klik-drag), lepas tombol mouse untuk menyalin otomatis. Tempel dengan klik kanan di jendela terminal (bukan Ctrl+V, terminal bawaan Windows kadang tidak mendukungnya).
2) Lihat nomor buku dulu — catat salah satu "id" dari jawaban ini, lalu pakai angka itu di langkah berikutnya:
curl.exe -s http://127.0.0.1:8000/api/buku ^
-H "Accept: application/json"
Awam — dua kata yang harus kamu ganti sendiri di semua perintah di bawah: GANTI_DENGAN_TOKEN diganti token dari langkah 1, dan NOMOR diganti angka "id" yang kamu catat di langkah 2. Contoh: kalau id-nya 3, maka /api/buku/NOMOR kamu ketik menjadi /api/buku/3. Jangan biarkan tulisan NOMOR ikut terketik.
3) Ubah tanpa kartu — harus ditolak
curl.exe -s -X PUT http://127.0.0.1:8000/api/buku/NOMOR ^
-H "Content-Type: application/json" ^
-H "Accept: application/json" ^
-d "{\"judul\":\"Coba Ubah\",\"penulis\":\"Tanpa Kartu\"}"
Kamu harus melihat pesan “Belum diizinkan” / unauthenticated — bukan “Buku diperbarui”. Kalau yang muncul justru CSRF token mismatch, berarti izin api/* di bootstrap\app.php belum terpasang — kembali ke bagian sebelumnya.
4) Ubah dengan kartu — harus berhasil
curl.exe -s -X PUT http://127.0.0.1:8000/api/buku/NOMOR ^
-H "Content-Type: application/json" ^
-H "Accept: application/json" ^
-H "Authorization: Bearer GANTI_DENGAN_TOKEN" ^
-d "{\"judul\":\"Bumi (Edisi Revisi)\",\"penulis\":\"Tere Liye\"}"
Jawaban yang benar kira-kira begini (semua menyatu dalam satu baris panjang, itu normal):
{"message":"Buku diperbarui","data":{"id":3,"judul":"Bumi (Edisi Revisi)","penulis":"Tere Liye","created_at":"2026-07-20T08:11:02.000000Z","updated_at":"2026-07-27T02:45:13.000000Z"}}
Awam: created_at dan updated_at ikut muncul walaupun tidak kamu kirim — itu dua kolom waktu yang diisi Laravel sendiri. Perhatikan updated_at: jamnya baru, tanda bukunya memang tersentuh.
5) Baca katalog — pastikan judulnya benar-benar berubah
curl.exe -s http://127.0.0.1:8000/api/buku ^
-H "Accept: application/json"
Jangan lewati langkah ini. Kalau langkah 4 menjawab “Buku diperbarui” tapi di daftar ini judulnya masih yang lama, penyebabnya hampir pasti baris $fillable di app\Models\Buku.php belum berisi 'judul' dan 'penulis' (lihat “Cek cepat” di atas). Ini satu-satunya kegagalan hari ini yang menyamar sebagai keberhasilan.
6) Ubah nomor yang tidak ada — harus 404
curl.exe -s -X PUT http://127.0.0.1:8000/api/buku/9999 ^
-H "Content-Type: application/json" ^
-H "Accept: application/json" ^
-H "Authorization: Bearer GANTI_DENGAN_TOKEN" ^
-d "{\"judul\":\"Buku Hantu\",\"penulis\":\"Tidak Ada\"}"
Jawaban yang benar: {"message":"Buku tidak ditemukan"}. Angka 9999 di sini memang sengaja dikarang — tidak perlu diganti.
7) Hapus dengan kartu (perhatikan: tidak ada -d, karena hapus tidak mengirim slip). Buku yang kamu ubah di langkah 4 boleh dipakai — hasil ubahnya sudah kamu buktikan di langkah 5:
curl.exe -s -X DELETE http://127.0.0.1:8000/api/buku/NOMOR ^
-H "Accept: application/json" ^
-H "Authorization: Bearer GANTI_DENGAN_TOKEN"
Jawaban yang benar: {"message":"Buku dihapus"}.
8) Baca katalog — buku itu harus sudah hilang
curl.exe -s http://127.0.0.1:8000/api/buku ^
-H "Accept: application/json"
Buku dengan nomor tadi tidak muncul lagi di daftar. Empat gerakan CRUD sudah lengkap.
Opsi B — PowerShell (token disimpan otomatis di variabel, tidak perlu salin-tempel manual). Tetap di Shell Laragon / XAMPP kalau jendela itu sudah PowerShell (sering begitu). Kalau kamu membuka PowerShell dari Start Menu khusus untuk Opsi B: boleh, karena perintah di bawah memakai Invoke-RestMethod (tidak butuh php) — tapi untuk tinker / route:list tadi, tetap pakai Shell Laragon/XAMPP. cd ke folder proyek, lalu ketik baris-baris ini. Di PowerShell tanda sambung baris adalah backtick ` (bukan ^):
$login = Invoke-RestMethod -Method Post -Uri http://127.0.0.1:8000/api/login `
-ContentType "application/json" `
-Body '{"email":"staf@perpustakaan.test","password":"password"}'
$token = $login.token
# Lihat isi rak dulu, catat salah satu id
Invoke-RestMethod -Uri http://127.0.0.1:8000/api/buku -Headers @{ Accept = "application/json" }
# GANTI angka 3 di bawah dengan id buku milikmu
$nomor = 3
Invoke-RestMethod -Method Put -Uri "http://127.0.0.1:8000/api/buku/$nomor" `
-ContentType "application/json" `
-Headers @{ Authorization = "Bearer $token"; Accept = "application/json" } `
-Body '{"judul":"Bumi (Edisi Revisi)","penulis":"Tere Liye"}'
# Buktikan judulnya benar-benar berubah
Invoke-RestMethod -Uri http://127.0.0.1:8000/api/buku -Headers @{ Accept = "application/json" }
Invoke-RestMethod -Method Delete -Uri "http://127.0.0.1:8000/api/buku/$nomor" `
-Headers @{ Authorization = "Bearer $token"; Accept = "application/json" }
Invoke-RestMethod -Uri http://127.0.0.1:8000/api/buku -Headers @{ Accept = "application/json" }
Awam: baris yang diawali # adalah catatan untuk manusia — PowerShell mengabaikannya, jadi boleh ikut ditempel. Perhatikan juga: kalau ada yang ditolak (misalnya nomor sudah terhapus), PowerShell menampilkan tulisan merah panjang dan berhenti di situ. Itu wajar — baca baris pertamanya untuk melihat kode statusnya (404, 401, atau 419), perbaiki, lalu jalankan lagi dari baris yang gagal.
Opsi C — Postman / Insomnia (alat berjendela, paling nyaman untuk PUT/DELETE karena tidak perlu mengurus tanda kutip). Unduh dan pasang Postman dulu kalau belum ada, lalu:
- Ambil token. Buat request baru, method POST, URL
http://127.0.0.1:8000/api/login. Tab Headers: tambahkanAcceptbernilaiapplication/json. Tab Body: pilih raw lalu JSON, isi{"email":"staf@perpustakaan.test","password":"password"}. Tekan Send, lalu salin nilaitokendari jawabannya. - Cari nomor buku. Method GET, URL
http://127.0.0.1:8000/api/buku, tekan Send, catat salah satu"id". - Ubah. Method PUT, URL
http://127.0.0.1:8000/api/buku/NOMOR— ganti tulisanNOMORdengan angka"id"dari langkah 2 (bukan selalu 3). Tab Authorization: pilih type Bearer Token, tempel token dari langkah 1. Tab Body: raw + JSON, isi judul dan penulis baru. Send. - Hapus. Ganti method jadi DELETE, kosongkan Body, biarkan Authorization tetap terisi, URL tetap memakai nomor yang sama. Send.
Awam: di Postman kamu cukup menempel token sekali di tab Authorization — Postman yang menuliskan header Authorization: Bearer ... untukmu. Kode status (200, 401, 404, 419) terlihat jelas di sudut panel jawaban, jadi opsi ini paling enak untuk pemula yang masih gugup dengan terminal.
Pola Dasar — enam langkah melengkapi CRUD
-
1
Buka alat
Terminal 1serve· Terminal 2 uji · Editor · Browser pengintip. -
2
Cek fondasi
Baca, login, tambah dari Capstone masih jalan ·$fillableada di Model. -
3
Tulis dua loket
updatelaludestroydi dalam classBukuController. -
4
Daftarkan pintu
PUT&DELETEdi dalamauth:sanctum· cekroute:list. -
5
Cek izin
api/*
Sudah ada? lewati. Belum? tempel baris di dalamwithMiddleware· nyalakan ulangserve. -
6
Uji berurutan
Lihat nomor -> ubah tanpa kartu gagal -> ubah dengan kartu berhasil -> baca ulang untuk membuktikan -> nomor ngawur jawab 404 -> hapus -> daftar berkurang.
Awam: selalu uji versi “gagal” dulu (tanpa kartu, nomor ngawur), baru versi “berhasil”. Kalau yang gagal ternyata berhasil, berarti satpam kartu belum terpasang benar.
File contoh — simulasi ubah & hapus
Simpan sebagai laravel_crud_api_buku_ubah_hapus_demo.php lalu jalankan di terminal kedua: php laravel_crud_api_buku_ubah_hapus_demo.php. Ini bukan Laravel sungguhan — hanya meniru alurnya supaya terasa dulu sebelum dikerjakan di proyek.
<?php
declare(strict_types=1);
/**
* laravel_crud_api_buku_ubah_hapus_demo.php
* Simulasi ubah (PUT) dan hapus (DELETE) satu buku berdasarkan nomor.
*/
function cariBaris(array $rak, int $id): ?int
{
foreach ($rak as $baris => $buku) {
if ($buku['id'] === $id) {
return $baris;
}
}
return null;
}
function demo(): void
{
$rak = [
['id' => 1, 'judul' => 'Bumi', 'penulis' => 'Tere Liye'],
['id' => 2, 'judul' => 'Laskar Pelangi', 'penulis' => 'Andrea Hirata'],
];
echo "1) PUT /api/buku/2 dengan kartu\n";
$baris = cariBaris($rak, 2);
if ($baris !== null) {
$rak[$baris]['judul'] = 'Laskar Pelangi (Edisi Revisi)';
echo json_encode(['message' => 'Buku diperbarui', 'data' => $rak[$baris]], JSON_UNESCAPED_UNICODE), PHP_EOL;
}
echo "2) PUT /api/buku/9999 - nomor tidak ada\n";
$baris = cariBaris($rak, 9999);
echo json_encode(['message' => $baris === null ? 'Buku tidak ditemukan' : 'Buku diperbarui'], JSON_UNESCAPED_UNICODE), PHP_EOL;
echo "3) DELETE /api/buku/1 dengan kartu\n";
$baris = cariBaris($rak, 1);
if ($baris !== null) {
unset($rak[$baris]);
$rak = array_values($rak);
}
echo json_encode(['message' => 'Buku dihapus'], JSON_UNESCAPED_UNICODE), PHP_EOL;
echo "4) GET /api/buku - sisa rak\n";
echo json_encode(['message' => 'Daftar buku', 'data' => $rak], JSON_UNESCAPED_UNICODE), PHP_EOL;
echo PHP_EOL, "Langkah sungguhan: serve -> login -> Bearer PUT/DELETE /api/buku/{nomor} -> GET daftar.", PHP_EOL;
}
demo();
Awam: declare(strict_types=1); = PHP lebih ketat soal tipe data di file contoh. ?int artinya fungsi boleh menjawab angka atau “tidak ada”. Di Laravel sungguhan, pencarian baris itu dikerjakan Buku::find($id).
Kesalahan umum
Cari gejala yang kamu lihat di kolom pertama — biasanya penyebabnya cuma satu hal kecil.
| Gejala di layar | Penyebab | Perbaikan |
|---|---|---|
CSRF token mismatch / kode 419 |
Izin api/* belum dipasang, atau serve belum dinyalakan ulang setelah menyimpan bootstrap\app.php |
Pasang preventRequestForgery(except: ['api/*']) (jangan timpa baris lain di withMiddleware), lalu Ctrl+C dan php artisan serve lagi |
Cannot use ... name is already in use |
Baris use ditulis dua kali karena sudah ada sejak artikel Controller/Capstone sebelumnya |
Hapus baris use yang dobel — cukup satu per nama |
Trait "App\Models\Buku" not found |
Baris use tertempel di dalam class, bukan di atasnya |
Pindahkan baris use ke atas class BukuController |
| “Buku diperbarui” tapi daftar masih judul lama | $fillable di app\Models\Buku.php belum berisi 'judul' dan 'penulis' |
Tambahkan kedua kolom itu ke $fillable, lalu ubah lagi |
405 Method Not Allowed |
Route tersimpan tapi kata kerjanya salah (mis. Route::post padahal diuji PUT) |
Cek dengan php artisan route:list --path=api/buku |
404 tanpa pesan "Buku tidak ditemukan" |
Itu 404 dari Laravel: route PUT/DELETE belum terdaftar |
Simpan routes\web.php, cek route:list. Kalau pesannya ada, berarti normal — nomornya memang tidak ada |
Kode 401 / “Belum diizinkan” padahal token sudah ditempel |
Token tersalin sebagian, atau kata Bearer ikut tersalin dari JSON |
Salin ulang hanya isi kunci "token"; kata Bearer ditulis manual di header |
Perintah curl pecah jadi beberapa error |
Tanda ^ dipakai di PowerShell |
Ganti ^ dengan backtick `, atau ketik satu baris panjang |
| Halaman HTML panjang, bukan JSON | Header Accept belum dikirim |
Tambahkan -H "Accept: application/json" |
| Gagal koneksi / connection refused | serve mati di terminal pertama |
Nyalakan lagi php artisan serve, biarkan jendelanya hidup |
php tidak dikenali |
Terminal dibuka dari Start Menu, PATH PHP tidak ada | Pakai menu Terminal Laragon atau tombol Shell XAMPP |
Dua hal lain yang sering bikin bingung, tapi sebenarnya bukan error: mencoba PUT/DELETE dari bilah alamat browser (browser hanya bisa GET — pakai terminal kedua atau Postman), dan mengirim -d saat menghapus (tidak perlu slip untuk hapus; cukup nomor + kartu). Dan kalau dua pintu baru terlanjur ditaruh di luar auth:sanctum, siapa pun bisa menghapus buku — pastikan keduanya di dalam kelompok berkartu.
Latihan
- Ubah judul satu buku, lalu
GET /api/buku— pastikan judul barunya benar-benar tersimpan. - Coba ubah dengan slip kosong (
{}) memakai kartu — pastikan satpam Form Request menolak, bukan menyimpan judul kosong. - Hapus satu buku, lalu hapus lagi nomor yang sama — perhatikan jawaban kedua seharusnya
404. - Tambah buku baru (dari Capstone: API Perpustakaan (#62)), ubah judulnya, lalu hapus — rasakan empat gerakan CRUD dalam satu tarikan napas.
- (Opsional) Coba hapus tanpa kartu — pastikan ditolak sebelum urusan nomor.
FAQ
Kenapa PUT, bukan POST?
Keduanya mengirim data, tapi PUT berarti “ganti isi catatan yang nomornya sudah saya sebut”. Memakai kata kerja yang tepat membuat pintu API mudah dibaca orang lain.
Kalau saya pakai PATCH boleh?
Boleh. PATCH biasanya untuk mengubah sebagian kolom saja. Untuk latihan ini PUT lebih sederhana karena kita mengirim judul dan penulis sekaligus.
Kenapa Buku::find lalu cek sendiri, bukan findOrFail?
findOrFail juga benar dan lebih singkat. Kita pakai find + if supaya kamu melihat sendiri kapan angka 404 itu muncul, bukan “ajaib” dari Laravel.
Apakah perlu mengunduh sesuatu lagi hari ini?
Tidak. Ubah & hapus hanya menambah dua method di Controller, dua baris di routes, dan satu izin kecil di bootstrap\app.php — tidak ada paket Composer baru. Kalau Sanctum belum terpasang, lakukan install-dari-nol di Auth API Dasar: Login & Kartu Anggota (#61) dulu.
Kenapa muncul 419 / CSRF token mismatch saat uji dari terminal?
Karena satpam lobi CSRF menahan semua kiriman POST, PUT, dan DELETE dari luar browser — termasuk login, tambah, ubah, dan hapus. Izin api/* dipasang sejak Request & Form Request: Menjaga Input API (#59); cukup sekali per proyek dan berlaku untuk semua pintu api/. Kalau kamu melihat 419 hari ini, hampir pasti izin itu belum ada atau serve belum dinyalakan ulang.
Kalau nanti saya pindah ke routes/api.php, izin itu masih perlu?
Tidak. Route di routes/api.php memang tidak dijaga CSRF sejak awal, jadi pengecualian itu bisa dihapus. Kita tetap di web.php hari ini supaya kamu tidak perlu memasang berkas route tambahan — sesuai jalur yang dipakai sejak Routing & Jawaban JSON API Perpustakaan (#58).
Token yang mana yang disalin?
Hanya string di kunci "token" pada jawaban login. Panjang, tanpa spasi. Lalu tulis di header: Authorization: Bearer + string itu.
Terminal mana yang dipakai?
Terminal 1: serve. Terminal 2: login, lihat nomor, ubah, hapus. Editor: update & destroy + routes. Browser: hanya melihat daftar.
Kenapa masih Shell XAMPP / Laragon?
Agar PATH PHP sama seperti saat proyek dibuat. CMD dari Start Menu sering menjawab “php tidak dikenal”.
Buku yang dihapus bisa dikembalikan?
Dengan delete() biasa: tidak. Laravel punya fitur “hapus lunak” (soft delete) untuk itu — bahan bagus untuk belajar lanjutan setelah jalur ini tamat.
Kalau saya mau lanjut belajar setelah jalur dasar ini selesai, mulai dari mana?
Mulai dari Relasi Eloquent: Anggota & Peminjaman (#64). Di situ kamu belajar cara menghubungkan buku, anggota, dan slip pinjam sebelum masuk ke daftar berhalaman, policy, resource, dan test.
Ringkasan
#63 (ini) melengkapi CRUD rak buku: PUT /api/buku/{id} untuk memperbaiki data dan DELETE /api/buku/{id} untuk membuangnya, keduanya wajib kartu Sanctum dan mengembalikan 404 bila nomornya tidak ada. Semua bertumpu pada
Capstone: API Perpustakaan (#62),
Auth API Dasar: Login & Kartu Anggota (#61), dan
Request & Form Request: Menjaga Input API (#59).
Ke mana setelah ini?
Jalur “Laravel dari nol” tamat di sini — kamu sudah bisa membangun API kecil yang utuh dan berpagar. Langkah alami berikutnya adalah Relasi Eloquent: Anggota & Peminjaman (#64), supaya buku, anggota, dan slip pinjam bisa saling terhubung rapi. Kalau ingin menyambungkan API ke perangkat nyata, lihat jalur Full Stack IoT.
Seri 4 progress: langkah #63 (ini) · 8/8 jalur Laravel — tamat · prasyarat: Capstone: API Perpustakaan (#62) LIVE · lanjut alami: Relasi Eloquent: Anggota & Peminjaman (#64).