Middleware adalah kode yang berdiri di antara request dan controller. Tiap request lewat middleware dulu: middleware boleh memeriksanya, menolaknya, atau meneruskannya ke dalam, lalu memegang response-nya lagi saat keluar. Bayangin masuk gedung kantor. Satpam di gerbang memeriksa semua tamu, resepsionis lantai admin cuma memeriksa tamu lantai itu, dan kunci kartu cuma ada di pintu satu ruangan. Kalau satu pos menolak, kamu gak pernah sampai ke ruangan. Pulangnya kamu melewati pos yang sama dengan urutan terbalik.
🧪 Tebakan cepat: route
/admin/dashboarddipasangi dua middleware,LogRequestlaluEnsureApiKey. Masing-masing punya kode sebelum$next($request)dan kode sesudahnya. Dengan controller di tengah, kelima bagian itu jalan dengan urutan apa? Tebak dulu, jawabannya ada di bagian "Sebelum dan sesudah $next".
Bentuk sebuah middleware
Di app Perpustakaan, halaman /admin/dashboard cuma boleh dibuka aplikasi petugas yang membawa header X-Api-Key: secret123. Buat middleware-nya dengan Artisan:
php artisan make:middleware EnsureApiKey
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
class EnsureApiKey
{
public function handle(Request $request, Closure $next): Response
{
if ($request->header('X-Api-Key') !== 'secret123') {
return response()->json(['error' => 'Forbidden'], 403);
}
return $next($request);
}
}
php artisan make:middleware EnsureApiKeymembuat fileapp/Http/Middleware/EnsureApiKey.php.handle(Request $request, Closure $next)dipanggil Laravel untuk tiap request yang lewat.$nextadalah lapisan berikutnya: middleware lain, atau controller kalau sudah yang terakhir.$request->header('X-Api-Key')membaca satu header request. Huruf besar kecilnya gak berpengaruh, dan header yang gak dikirim hasilnyanull.response()->json(['error' => 'Forbidden'], 403)langsung menjawab dengan status403 Forbidden, artinya terlarang. Controller gak pernah jalan.return $next($request)meneruskan request ke dalam, lalu mengembalikan response yang dibuat di dalam.
Sebelum dan sesudah $next
Kode di atas $next($request) jalan waktu request masuk. Kode di bawahnya jalan waktu response keluar. Contohnya LogRequest, yang mengukur lama sebuah request:
class LogRequest
{
public function handle(Request $request, Closure $next): Response
{
$start = microtime(true); // sebelum: request masuk
$response = $next($request); // lapisan di dalam bekerja
$ms = round((microtime(true) - $start) * 1000);
$response->headers->set('X-Took', $ms.'ms'); // sesudah: response keluar
return $response;
}
}
Ini perjalanan GET /admin/dashboard dengan kunci yang benar:
| Langkah | Lapisan | Yang jalan |
|---|---|---|
| 1 | LogRequest |
sebelum $next: catat waktu mulai |
| 2 | EnsureApiKey |
sebelum $next: kuncinya cocok, panggil $next |
| 3 | Controller | menjawab 200 dengan {"message":"Welcome, admin!"} |
| 4 | EnsureApiKey |
sesudah $next: gak ada kode, response diteruskan keluar |
| 5 | LogRequest |
sesudah $next: pasang header X-Took: 18ms |
Masuk dari lapisan terluar ke dalam, keluar dari dalam ke luar, seperti bawang yang dikupas lalu ditutup lagi. Itu jawaban tebakannya.
Kalau kuncinya salah, langkah 3 dan 4 gak terjadi: EnsureApiKey mengembalikan 403 tanpa memanggil $next. Langkah 5 tetap jalan, karena 403 itu juga response yang lewat keluar, jadi LogRequest tetap mencatatnya.
Memasang middleware: route, group, atau global
Middleware baru gak jalan sebelum dipasang. Di Laravel 13, pengaturan middleware ada di bootstrap/app.php:
// bootstrap/app.php
->withMiddleware(function (Middleware $middleware): void {
$middleware->append(LogRequest::class); // global: semua request
$middleware->alias([
'api.key' => EnsureApiKey::class, // nama pendek untuk dipakai di route
]);
})
// routes/web.php
Route::middleware('api.key')->prefix('admin')->group(function () {
Route::get('/dashboard', [DashboardController::class, 'index']);
Route::get('/loans', [LoanController::class, 'index']);
});
| Dipasang di | Caranya | Kena ke |
|---|---|---|
| Route | ->middleware('api.key') di belakang Route::get(...) |
route itu saja |
| Group | Route::middleware('api.key')->group(...) |
semua route di dalam group: /admin/dashboard dan /admin/loans |
| Global | $middleware->append(EnsureApiKey::class) |
semua request, termasuk katalog /books yang dibuka pengunjung |
Urutannya: global dulu, lalu group, lalu route, dan di dalam satu daftar dari kiri ke kanan. Laravel juga membawa middleware dengan alias siap pakai, misalnya auth (harus login), throttle:60,1 (paling banyak 60 request per menit), verified (email sudah diverifikasi), dan can:... untuk cek izin (lihat pelajaran Role-based Access Control).
Cara menolak
| Ditulis di middleware | Yang diterima client |
|---|---|
return response()->json(['error' => 'Forbidden'], 403); |
403 dengan body {"error":"Forbidden"} |
abort(403, 'Kunci API salah.'); |
403. Client JSON menerima {"message": "Kunci API salah."}, browser menerima halaman error Forbidden |
return redirect('/login'); |
302 ke /login. Cocok untuk halaman web, bukan untuk API |
Di latihan
Editor latihan cuma punya satu file, jadi class CheckApiKey ditulis di file yang sama dengan route-nya, lalu dipasang dengan ->middleware(CheckApiKey::class). Saat dijalankan, route-nya dipanggil seperti request sungguhan, lewat middleware-mu: tanpa header yang benar, output-nya GET /admin/dashboard → 403 lalu {"error":"Forbidden"}. Middleware selalu berupa class atau alias. Nama function biasa, misalnya ->middleware("checkApiKey"), berakhir dengan error Target class [checkApiKey] does not exist.
⚠️ Jebakan yang sering ketemu
- Lupa
returndi depan$next($request). Controller tetap jalan, tapi response-nya dibuang di tengah jalan. Karenahandlebertipe: Response, PHP berhenti dengan error500:EnsureApiKey::handle(): Return value must be of type Symfony\Component\HttpFoundation\Response, none returned. - Dipasang di satu route padahal maksudnya satu area.
->middleware('api.key')cuma di/admin/dashboardmembiarkan/admin/loansterbuka. Untuk satu area, pakai group. - Aturan khusus dipasang global.
append(EnsureApiKey::class)ikut mengunci/books, jadi pengunjung biasa ditolak waktu membuka katalog. - Status yang tertukar.
401berarti belum dikenali (belum login),403berarti dikenali tapi gak boleh. Latihannya minta403.
TL;DR: Middleware berdiri di antara request dan controller. Buat dengan php artisan make:middleware EnsureApiKey, isi handle(Request $request, Closure $next): tolak dengan return response()->json(['error' => 'Forbidden'], 403) atau abort(403), teruskan dengan return $next($request). Kode sebelum $next jalan saat request masuk, kode sesudahnya saat response keluar, dan lapisan terluar masuk pertama dan keluar terakhir. Response penolakan tetap lewat keluar melewati middleware di luarnya. Pasang di satu route dengan ->middleware('auth'), di satu area dengan Route::middleware(...)->group(...), atau untuk semua request dengan $middleware->append(...) di bootstrap/app.php, tempat alias juga didaftarkan.