Middleware Custom: Cek Role Sendiri
Bikin middleware cek admin dari nol.
Cara Kerja Middleware: Model Bawang
Middleware adalah lapisan konsentris seperti bawang. Request masuk melewati middleware satu per satu dari luar ke dalam sampai ke controller, lalu response keluar melewatinya lagi dengan urutan terbalik. Titik baliknya adalah $next($request): kode sebelum baris itu berjalan saat request masuk, kode sesudahnya berjalan saat response keluar.
php artisan make:middleware EnsureAdmin// app/Http/Middleware/EnsureAdmin.php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
class EnsureAdmin
{
public function handle(Request $request, Closure $next): Response
{
// === filter MASUK: cek sebelum sampai controller ===
if (! $request->user()?->is_admin) {
abort(403, 'Khusus admin.');
}
$response = $next($request);
// === filter KELUAR: modifikasi response setelah controller ===
$response->headers->set('X-Admin-Area', 'true');
return $response;
}
}Contoh filter keluar yang praktis: mengukur waktu eksekusi setiap request.
// app/Http/Middleware/LogResponseTime.php
public function handle(Request $request, Closure $next): Response
{
$start = microtime(true);
$response = $next($request);
$ms = round((microtime(true) - $start) * 1000, 2);
logger()->info('Response time', ['path' => $request->path(), 'ms' => $ms]);
return $response;
}Mendaftarkan Middleware
Di Laravel 11, pendaftaran terpusat di bootstrap/app.php. Ada tiga cara pasang:
// bootstrap/app.php
use App\Http\Middleware\EnsureAdmin;
use App\Http\Middleware\SetLocale;
use Illuminate\Foundation\Configuration\Middleware;
->withMiddleware(function (Middleware $middleware) {
// 1. Alias: dipakai per route/group
$middleware->alias(['admin' => EnsureAdmin::class]);
// 2. Global: jalan di SEMUA request web + api
$middleware->append(SetLocale::class);
// 3. Masuk ke group bawaan 'web'
$middleware->appendToGroup('web', LogResponseTime::class);
})// routes/web.php
Route::prefix('admin')->middleware(['auth', 'admin'])->group(function () {
Route::get('/laporan', [AdminController::class, 'laporan']);
});Urutan dalam array middleware adalah urutan eksekusi. ['auth', 'admin'] berarti cek login dulu, baru cek admin. Kalau dibalik, $request->user() di middleware admin adalah null untuk tamu dan kamu harus menangani null itu sendiri. Selalu taruh auth sebelum middleware yang butuh user.
Middleware Berparameter
Satu class middleware bisa melayani banyak peran dengan parameter:
// app/Http/Middleware/EnsureRole.php
public function handle(Request $request, Closure $next, string $role): Response
{
if (! $request->user()?->hasRole($role)) {
abort(403, "Butuh peran: {$role}.");
}
return $next($request);
}// bootstrap/app.php
$middleware->alias(['role' => EnsureRole::class]);
// routes/web.php
Route::middleware(['auth', 'role:editor'])->group(function () {
Route::get('/tulisan', ...);
});
Route::middleware(['auth', 'role:admin'])->group(function () {
Route::get('/pengaturan', ...);
});Parameter dipisah koma (role:admin,super) dan diterima sebagai argumen tambahan di handle(). Ini jauh lebih rapi daripada membuat EnsureAdmin, EnsureEditor, EnsureSuperAdmin sebagai tiga class terpisah.
Jebakan Umum
Pertama dan paling fatal: lupa return $next($request). Request berhenti di middleware-mu, controller tidak pernah dipanggil, dan browser menampilkan halaman kosong. Kalau route tiba-tiba blank, cek middleware dulu.
Kedua, melempar abort(403) untuk API tanpa memastikan response-nya JSON. Untuk route API, lebih baik abort(response()->json(['message' => 'Forbidden'], 403)) atau biarkan exception handler Laravel yang mengubahnya (ia otomatis JSON kalau request mengirim header Accept: application/json).
Ketiga, menaruh logika berat di middleware global. Middleware global jalan di setiap request termasuk asset dan health check. Query database di middleware global berarti query itu jalan ribuan kali per menit tanpa kamu sadari. Simpan middleware global untuk hal ringan (locale, header), dan pakai alias untuk yang butuh database.
Keempat, terminable middleware. Kalau kamu butuh kode yang jalan SETELAH response dikirim ke user (misal kirim webhook lambat), implementasikan method terminate(). Tapi ingat: terminate hanya dipanggil untuk middleware yang terdaftar di route atau global, dan tidak menerima return value.
Catatan teknis: Aturan praktisnya: middleware untuk pertanyaan "boleh lewat pintu ini?", policy untuk "boleh melakukan aksi ini pada data ini?". Mengecek kepemilikan data (
$post->user_id === auth()->id()) di middleware adalah bau kode: middleware tidak tahu model apa yang sedang diakses dan logikanya tidak bisa dipakai ulang di tempat lain. Pindahkan ke policy.
Tantangan
Middleware Bahasa
Buat middleware SetLocale yang membaca query ?lang=en|id dan memanggil app()->setLocale(). Daftarkan global (bukan alias) dan buktikan dengan route yang mengembalikan app()->getLocale().