Tag: tutorial

  • Tutorial Laravel 12 Job Batching: Implementasi, Progress, dan Error Handling

    Bayangkan Anda perlu kirim email ke 5.000 pengguna sekaligus, atau proses 1.000 gambar setelah upload. Kalau dijalankan satu per satu lewat queue biasa, Anda tidak tahu kapan semuanya selesai, dan tidak bisa jalankan aksi “setelah semua beres”.

    Job Batching di Laravel menyelesaikan masalah ini. Artikel ini membahas cara kerjanya, implementasi lengkap dengan contoh kode, dan cara handle error dalam batch.

    Apa Itu Job Batching?

    Job Batching memungkinkan Anda mengelompokkan beberapa job ke dalam satu batch, lalu mendefinisikan callback yang dijalankan:

    • Saat semua job berhasil (then)
    • Saat ada job yang gagal (catch)
    • Saat semua job selesai (berhasil atau gagal) (finally)

    Setup: Membuat Tabel Batch

    php artisan make:batches-table
    php artisan migrate

    Ini membuat tabel job_batches yang menyimpan status setiap batch.

    Membuat Job yang Batchable

    Job harus implement ShouldQueue dan gunakan trait Batchable:

    <?php
    
    namespace AppJobs;
    
    use AppModelsUser;
    use AppMailPromotionMail;
    use IlluminateBusBatchable;
    use IlluminateBusQueueable;
    use IlluminateContractsQueueShouldQueue;
    use IlluminateFoundationBusDispatchable;
    use IlluminateQueueInteractsWithQueue;
    use IlluminateSupportFacadesMail;
    
    class SendPromotionEmail implements ShouldQueue
    {
        use Batchable, Dispatchable, InteractsWithQueue, Queueable;
    
        public function __construct(
            private User $user
        ) {}
    
        public function handle(): void
        {
            // Cek apakah batch sudah di-cancel
            if ($this->batch()->cancelled()) {
                return;
            }
    
            Mail::to($this->user)->send(new PromotionMail());
        }
    }

    Mengirim Batch

    use AppJobsSendPromotionEmail;
    use IlluminateSupportFacadesBus;
    
    $users = User::where('subscribed', true)->get();
    
    $batch = Bus::batch(
        $users->map(fn ($user) => new SendPromotionEmail($user))->toArray()
    )->then(function (Batch $batch) {
        // Dijalankan saat semua job berhasil
        Log::info("Batch selesai: {$batch->totalJobs} email terkirim.");
    })->catch(function (Batch $batch, Throwable $e) {
        // Dijalankan saat ada job yang gagal
        Log::error("Batch error: {$e->getMessage()}");
    })->finally(function (Batch $batch) {
        // Selalu dijalankan saat batch selesai (berhasil atau tidak)
        Cache::forget('promotion-batch-running');
    })->name('Kirim Email Promosi')
      ->allowFailures()  // batch tetap lanjut meski ada job gagal
      ->dispatch();
    
    // Simpan ID batch untuk monitor progress
    return $batch->id;

    Monitor Progress Batch

    Ambil info batch berdasarkan ID:

    use IlluminateSupportFacadesBus;
    
    $batch = Bus::findBatch($batchId);
    
    // Info yang tersedia
    $batch->id;
    $batch->name;
    $batch->totalJobs;
    $batch->pendingJobs;
    $batch->failedJobs;
    $batch->processedJobs();  // totalJobs - pendingJobs
    $batch->progress();       // 0-100 persen
    $batch->finished();       // apakah sudah selesai
    $batch->cancelled();      // apakah di-cancel
    

    Contoh endpoint API untuk polling progress:

    Route::get('/batches/{batchId}/progress', function (string $batchId) {
        $batch = Bus::findBatch($batchId);
    
        if (!$batch) {
            return response()->json(['error' => 'Batch tidak ditemukan'], 404);
        }
    
        return response()->json([
            'progress'     => $batch->progress(),
            'total'        => $batch->totalJobs,
            'processed'    => $batch->processedJobs(),
            'failed'       => $batch->failedJobs,
            'finished'     => $batch->finished(),
        ]);
    })->middleware('auth');

    Cancel Batch

    $batch = Bus::findBatch($batchId);
    $batch->cancel();

    Job yang belum diproses akan skip sendiri karena pengecekan $this->batch()->cancelled() di awal method handle().

    Batch dengan Job Berantai

    Anda bisa chain batch: jalankan batch kedua setelah batch pertama selesai:

    Bus::batch([
        new ProcessImages($product),
        new GenerateThumbnails($product),
    ])->then(function (Batch $batch) use ($product) {
        // Setelah gambar dan thumbnail selesai, generate sitemap
        GenerateSitemap::dispatch();
    })->dispatch();

    Praktis: Batch untuk Impor CSV

    Skenario umum: impor data dari file CSV besar.

    <?php
    
    namespace AppJobs;
    
    use IlluminateBusBatchable;
    use IlluminateBusQueueable;
    use IlluminateContractsQueueShouldQueue;
    
    class ImportCsvRow implements ShouldQueue
    {
        use Batchable, Queueable;
    
        public function __construct(private array $row) {}
    
        public function handle(): void
        {
            if ($this->batch()->cancelled()) return;
    
            Product::updateOrCreate(
                ['sku'  => $this->row['sku']],
                ['name' => $this->row['name'], 'price' => $this->row['price']]
            );
        }
    }
    
    // Di controller
    $rows  = CsvParser::parse($file);
    $jobs  = collect($rows)->map(fn ($row) => new ImportCsvRow($row));
    
    $batch = Bus::batch($jobs->toArray())
                ->name('Import Produk')
                ->allowFailures()
                ->dispatch();
    
    return redirect()->route('imports.progress', $batch->id);

    Baca Juga

    Butuh tim untuk implementasi background processing di aplikasi Laravel Anda? Lihat layanan pengembangan aplikasi kami.

  • Notification di Laravel 12: Email, Database, dan Channel Kustom

    Hampir semua aplikasi web butuh notifikasi: konfirmasi order, reset password, atau pemberitahuan saat ada komentar baru. Laravel punya sistem notifikasi yang cukup fleksibel: satu kelas bisa kirim ke email, database, Slack, dan channel lain sekaligus.

    Artikel ini membahas cara kerja notification di Laravel 12, mulai dari membuat kelas notifikasi, kirim via email dan database, sampai membuat channel kustom.

    Membuat Kelas Notifikasi

    php artisan make:notification OrderConfirmed

    File dibuat di app/Notifications/OrderConfirmed.php:

    <?php
    
    namespace AppNotifications;
    
    use AppModelsOrder;
    use IlluminateBusQueueable;
    use IlluminateNotificationsNotification;
    use IlluminateNotificationsMessagesMailMessage;
    use IlluminateContractsQueueShouldQueue;
    
    class OrderConfirmed extends Notification implements ShouldQueue
    {
        use Queueable;
    
        public function __construct(
            private Order $order
        ) {}
    
        public function via(object $notifiable): array
        {
            return ['mail', 'database'];
        }
    
        public function toMail(object $notifiable): MailMessage
        {
            return (new MailMessage)
                ->subject("Order #{$this->order->id} Dikonfirmasi")
                ->greeting("Halo, {$notifiable->name}!")
                ->line("Order Anda telah kami terima dan sedang diproses.")
                ->action('Lihat Detail Order', route('orders.show', $this->order))
                ->line("Terima kasih sudah berbelanja.");
        }
    
        public function toDatabase(object $notifiable): array
        {
            return [
                'order_id' => $this->order->id,
                'message'  => "Order #{$this->order->id} dikonfirmasi",
                'url'      => route('orders.show', $this->order),
            ];
        }
    }

    Notifiable Trait

    Model yang ingin menerima notifikasi harus menggunakan trait Notifiable. Model User bawaan Laravel sudah menyertakan ini:

    use IlluminateNotificationsNotifiable;
    
    class User extends Authenticatable
    {
        use Notifiable;
    }

    Untuk model lain — misalnya Admin atau Customer — tambahkan trait yang sama.

    Mengirim Notifikasi

    // Lewat instance model
    $user->notify(new OrderConfirmed($order));
    
    // Lewat Notification facade (tanpa model spesifik)
    Notification::send($users, new OrderConfirmed($order));
    
    // On-demand — kirim ke email tanpa model User
    Notification::route('mail', 'admin@example.com')
                ->notify(new OrderConfirmed($order));

    Karena kelas ini implement ShouldQueue, notifikasi akan diproses di background queue, tidak memperlambat response HTTP.

    Notifikasi Email: Kustomisasi Template

    Untuk kontrol penuh atas tampilan email, publish template bawaan Laravel:

    php artisan vendor:publish --tag=laravel-mail

    Template disimpan di resources/views/vendor/mail/. Edit sesuai kebutuhan brand Anda.

    Kalau mau pakai Markdown template yang lebih kaya:

    php artisan make:notification OrderShipped --markdown=mail.order.shipped

    Template dibuat di resources/views/mail/order/shipped.blade.php:

    @component('mail::message')
    # Order Anda Dalam Perjalanan
    
    Pesanan **#{{ $order->id }}** sedang dikirim ke alamat Anda.
    
    @component('mail::button', ['url' => $trackingUrl])
    Lacak Pengiriman
    @endcomponent
    
    Estimasi tiba: **{{ $order->estimated_arrival }}**
    
    Salam,
    {{ config('app.name') }}
    @endcomponent

    Notifikasi Database

    Untuk simpan notifikasi di database (biasanya untuk fitur notifikasi in-app), buat tabel dulu:

    php artisan make:notifications-table
    php artisan migrate

    Ambil notifikasi yang belum dibaca:

    // Semua notifikasi
    $notifications = auth()->user()->notifications;
    
    // Hanya yang belum dibaca
    $unread = auth()->user()->unreadNotifications;
    
    // Tandai sudah dibaca
    auth()->user()->unreadNotifications->markAsRead();
    
    // Tandai satu notifikasi sudah dibaca
    $notification->markAsRead();

    Channel Kustom

    Kalau perlu kirim via WhatsApp, Telegram, atau sistem internal, buat channel kustom:

    <?php
    
    namespace AppNotificationsChannels;
    
    use IlluminateNotificationsNotification;
    
    class WhatsAppChannel
    {
        public function send(object $notifiable, Notification $notification): void
        {
            $message = $notification->toWhatsApp($notifiable);
    
            // Kirim via WhatsApp API
            app(WhatsAppService::class)->send(
                $notifiable->phone,
                $message
            );
        }
    }

    Daftarkan di method via():

    public function via(object $notifiable): array
    {
        return [WhatsAppChannel::class, 'database'];
    }
    
    public function toWhatsApp(object $notifiable): string
    {
        return "Order #{$this->order->id} Anda sudah dikonfirmasi.";
    }

    Baca Juga

    Butuh tim yang bantu implementasi sistem notifikasi di aplikasi Laravel Anda? Lihat layanan pengembangan aplikasi kami.

  • Laravel Volt: Pengertian, Cara Install di Laravel 12, dan Statusnya di Livewire 4

    Laravel Volt adalah API fungsional untuk Livewire yang memungkinkan logika PHP dan template Blade sebuah komponen ditulis dalam satu file. Volt dipasang sebagai paket terpisah, livewire/volt, dengan dua perintah: composer require livewire/volt lalu php artisan volt:install. Panduan ini menjelaskan cara kerja Volt, cara install di Laravel 12, contoh komponen yang sudah kami uji, dan satu hal yang jarang dibahas: sejak Livewire 4, komponen satu file sudah bawaan Livewire, sehingga Volt kini bersifat opsional.

    Apa Itu Laravel Volt dan Cara Kerjanya

    Livewire adalah library untuk membuat antarmuka interaktif di Laravel cukup dengan PHP dan Blade, tanpa menulis JavaScript sendiri. Di Livewire 3, satu komponen biasanya terdiri dari dua file: class PHP di app/Livewire dan view Blade di resources/views/livewire.

    Volt menyatukan keduanya. Kamu menulis state dan aksi di blok <?php ?> di bagian atas file Blade, lalu template di bawahnya. Di balik layar, Volt mengompilasi kode itu menjadi class komponen Livewire biasa. Jadi semua fitur Livewire tetap berlaku: wire:click, wire:model, validasi, pagination, dan lainnya.

    Contoh komponen counter dengan Volt:

    <?php
    
    use function LivewireVolt{state};
    
    state(['count' => 0]);
    
    $increment = fn () => $this->count++;
    $decrement = fn () => $this->count--;
    
    ?>
    
    <div>
        <h1>{{ $count }}</h1>
        <button wire:click="increment">+</button>
        <button wire:click="decrement">-</button>
    </div>

    Tombol diklik, Livewire mengirim request ke server, method increment dijalankan, dan angka di halaman berubah tanpa reload.

    Status Volt di 2026: Livewire 4 dan Starter Kit Laravel

    Bagian ini penting supaya kamu tidak salah memilih. Berikut kondisi per Oktober 2026, berdasarkan dokumentasi resmi Livewire dan Laravel:

    • Livewire 4 sudah punya komponen satu file bawaan. Perintah php artisan make:livewire sekarang membuat file Blade dengan blok PHP di atasnya, memakai sintaks yang sama dengan Volt versi class. Dokumentasi Livewire menulis bahwa Volt opsional di Livewire 4 dan sebagian besar aplikasi tidak membutuhkannya.
    • Volt tetap dirawat. Versi terbaru (1.11) mendukung Livewire 3 dan 4, serta Laravel 10 sampai 13. Volt masih berguna kalau kamu suka gaya fungsional berbasis closure seperti contoh di atas.
    • Starter kit Livewire di Laravel 12 saat pertama rilis (Februari 2025) memakai Volt dan Flux UI untuk semua halamannya, termasuk login dan pengaturan profil. Starter kit versi terbaru sudah beralih ke Livewire 4 tanpa Volt.

    Kesimpulannya: kalau kamu memulai project baru dan suka gaya class, komponen satu file Livewire 4 sudah cukup. Kalau kamu mewarisi project yang memakai Volt (misalnya dari starter kit Laravel 12 versi awal), atau lebih suka gaya fungsional, Volt masih aman dipakai.

    Cara Install Laravel Volt di Laravel 12

    Kami menguji langkah ini di project Laravel 12 yang baru dibuat (Laravel 12.70, PHP 8.5). Composer otomatis memasang Livewire 4.4 bersama Volt 1.11.

    composer require livewire/volt
    php artisan volt:install

    Perintah volt:install membuat file app/Providers/VoltServiceProvider.php dan mendaftarkannya di bootstrap/providers.php. Service provider ini menentukan folder tempat Volt mencari komponen:

    public function boot(): void
    {
        Volt::mount([
            config('livewire.view_path', resource_path('views/livewire')),
            resource_path('views/pages'),
        ]);
    }

    Secara bawaan, komponen Volt dicari di resources/views/livewire dan resources/views/pages. Tambahkan folder lain ke array itu kalau perlu.

    Satu koreksi untuk tutorial lama: Volt tidak otomatis ikut terpasang bersama paket livewire/livewire. Kamu tetap harus menjalankan composer require livewire/volt. Jalur lama lewat php artisan breeze:install livewire juga sudah tidak disarankan, karena Laravel Breeze tidak lagi mendapat pembaruan sejak Laravel 12. Perbandingan lengkapnya ada di artikel perbedaan Laravel Volt dan Breeze.

    Cek apakah paketnya terpasang:

    composer show livewire/volt

    Membuat Komponen Volt Pertama

    php artisan make:volt counter

    File dibuat di resources/views/livewire/counter.blade.php. Isi dengan kode counter di atas, lalu tampilkan di view Blade mana saja:

    <livewire:counter />

    Layout halaman yang memuat komponen Livewire perlu directive @livewireStyles di dalam <head> dan @livewireScripts sebelum </body>. Kalau kamu belum punya layout, jalankan:

    php artisan livewire:layout

    Perintah itu membuat resources/views/layouts/app.blade.php yang sudah berisi kedua directive tersebut. Di Livewire 4, file inilah yang dipakai sebagai layout bawaan untuk komponen halaman penuh.

    Functional API vs Class API

    Volt punya dua gaya penulisan. Contoh counter di atas memakai functional API. Untuk komponen yang lebih besar, class API biasanya lebih mudah dibaca. Buat dengan opsi --class:

    php artisan make:volt todo --class
    <?php
    
    use LivewireVoltComponent;
    
    new class extends Component {
        public string $task = '';
        public array $tasks = [];
    
        public function add(): void
        {
            $this->validate(['task' => 'required|min:3']);
    
            $this->tasks[] = $this->task;
            $this->reset('task');
        }
    }; ?>
    
    <div>
        <form wire:submit="add">
            <input type="text" wire:model="task" placeholder="Tugas baru">
            <button type="submit">Tambah</button>
            @error('task') <span>{{ $message }}</span> @enderror
        </form>
    
        <ul>
            @foreach ($tasks as $item)
                <li>{{ $item }}</li>
            @endforeach
        </ul>
    </div>
    Functional API Class API
    Cara menulis state state(['count' => 0]) Properti publik public int $count = 0;
    Cara menulis aksi Closure: $increment = fn () => ... Method biasa
    Cocok untuk Komponen kecil dan sederhana Komponen dengan banyak aksi, validasi, atau trait
    Pindah ke Livewire 4 tanpa Volt Perlu ditulis ulang ke gaya class Cukup ganti satu baris import

    Komponen Volt sebagai Halaman Penuh

    Komponen Volt bisa langsung dipasang ke URL tanpa controller:

    use LivewireVoltVolt;
    
    Volt::route('/todo', 'todo');

    Buka /todo dan komponen akan tampil di dalam layout layouts/app. Kalau muncul error No hint path defined for [layouts], berarti file layout belum ada. Jalankan php artisan livewire:layout seperti di atas.

    Menguji Komponen Volt

    Volt menyediakan helper test yang bisa dipakai di PHPUnit maupun Pest:

    use LivewireVoltVolt;
    
    public function test_counter_bertambah(): void
    {
        Volt::test('counter')
            ->call('increment')
            ->call('increment')
            ->assertSet('count', 2);
    }

    Volt atau Komponen Satu File Livewire 4?

    Ini komponen yang sama dengan sintaks bawaan Livewire 4, tanpa Volt. Kami membuatnya dengan php artisan make:livewire search-box:

    <?php
    
    use LivewireComponent;
    
    new class extends Component
    {
        public string $query = '';
    };
    ?>
    
    <div>
        <input type="text" wire:model.live="query" placeholder="Cari...">
        <p>Kata kunci: {{ $query }}</p>
    </div>

    Bedanya dengan Volt class API hanya pada baris import (LivewireComponent, bukan LivewireVoltComponent). Livewire 4 menyimpan file ini di resources/views/components dengan awalan emoji ⚡ di nama file (bisa dimatikan lewat konfigurasi). Untuk route, pakai Route::livewire('/cari', 'search-box').

    Kalau suatu saat ingin melepas Volt, panduan upgrade Livewire 4 menyebut langkahnya:

    1. Ganti use LivewireVoltComponent; menjadi use LivewireComponent;.
    2. Ganti Volt::route() menjadi Route::livewire().
    3. Di test, ganti Volt::test() menjadi Livewire::test().
    4. Hapus app/Providers/VoltServiceProvider.php dan entrinya di bootstrap/providers.php.
    5. Jalankan composer remove livewire/volt.

    Komponen dengan functional API (state(), closure) perlu ditulis ulang ke gaya class sebelum langkah di atas.

    Kapan Volt Cocok, Kapan Tidak

    Volt dan komponen satu file cocok untuk komponen yang berdiri sendiri: form pencarian, counter, toggle, form kontak, atau halaman pengaturan. Logika dan tampilan ada di satu tempat, jadi mudah dirawat.

    Pertimbangkan komponen class terpisah (atau multi-file component di Livewire 4) kalau logikanya sudah panjang, perlu dipakai ulang lewat inheritance, atau tim kamu lebih nyaman memisahkan PHP dan Blade. Logika bisnis yang berat, misalnya perhitungan stok atau integrasi pembayaran, sebaiknya tetap di class service atau controller, lalu dipanggil dari komponen.

    Laravel Volt Bukan Laravolt

    Nama yang mirip sering membuat bingung. Laravel Volt (livewire/volt) adalah API komponen untuk Livewire yang dibahas di artikel ini. Laravolt adalah proyek berbeda: platform untuk membangun aplikasi Laravel (paket Composer laravolt/laravolt), dengan dokumentasi sendiri di laravolt.dev. Keduanya tidak berhubungan.

    Pertanyaan yang Sering Diajukan

    Apakah Laravel Volt sudah termasuk di Livewire?

    Tidak. Volt adalah paket terpisah, livewire/volt, yang harus dipasang lewat Composer. Yang sudah bawaan di Livewire 4 adalah komponen satu file dengan gaya class, yang sintaksnya sama dengan Volt class API.

    Apakah Volt masih layak dipelajari di 2026?

    Layak kalau kamu merawat project yang sudah memakai Volt, termasuk project dari starter kit Livewire versi awal Laravel 12. Untuk project baru, komponen satu file Livewire 4 lebih sederhana karena tidak perlu paket tambahan.

    Livewire itu dipakai untuk apa?

    Livewire dipakai untuk membuat halaman interaktif, seperti form dengan validasi langsung, pencarian tanpa reload, tabel dengan filter, dan dashboard, cukup dengan PHP dan Blade. Cocok untuk tim yang lebih kuat di backend Laravel dibanding framework JavaScript.

    Apakah Volt bisa dipakai di Laravel 11 atau 13?

    Bisa. Volt 1.11 mendukung Laravel 10, 11, 12, dan 13, dengan Livewire 3.6 ke atas atau Livewire 4.

    Ingin membangun dashboard atau aplikasi internal dengan Laravel dan Livewire tetapi tidak punya waktu mengerjakannya sendiri? Tim Arrazy Inovasi di Purwokerto mengerjakan sistem aplikasi custom berbasis Laravel. Materi Laravel lainnya ada di halaman tutorial Laravel.

    Diskusikan kebutuhan aplikasi kamu →

  • Migration Laravel: Cara Membuat, Menjalankan, dan Rollback di Laravel 12

    Migration Laravel adalah file PHP yang berisi perubahan struktur database, seperti membuat tabel, menambah kolom, atau memasang foreign key, sehingga perubahan itu tercatat di kode dan bisa dijalankan ulang di komputer mana pun. Buat migration dengan php artisan make:migration create_categories_table, lalu jalankan dengan php artisan migrate. Panduan ini membahas cara membuat migration di Laravel 12, tipe kolom yang sering dipakai, foreign key, mengubah kolom, dan semua perintah migrate (rollback, refresh, fresh, dan lainnya) beserta bedanya. Semua contoh sudah kami jalankan di Laravel 12.70.

    Belum punya project? Mulai dari cara install Laravel 12. Urutan materi lengkap ada di halaman tutorial Laravel.

    Kenapa Memakai Migration

    Tanpa migration, struktur tabel biasanya dibuat lewat phpMyAdmin atau file SQL yang dikirim lewat chat. Masalahnya, sulit tahu tabel mana yang sudah diubah di laptop siapa, dan server production sering tertinggal. Dengan migration:

    • Setiap perubahan struktur tersimpan di folder database/migrations dan ikut masuk Git.
    • Rekan satu tim cukup menjalankan php artisan migrate untuk menyamakan struktur database.
    • Laravel mencatat migration yang sudah dijalankan di tabel migrations, jadi perintah yang sama tidak menjalankan file dua kali.
    • Perubahan bisa dibatalkan dengan migrate:rollback karena setiap file punya method down().

    Ini juga jawaban untuk pertanyaan “cara memindahkan project Laravel ke laptop lain”: clone repository, jalankan composer install, siapkan .env, lalu php artisan migrate. Struktur tabel akan terbentuk sama persis.

    Cara Membuat Migration di Laravel 12

    php artisan make:migration create_categories_table

    File baru muncul di database/migrations dengan nama berawalan waktu, misalnya 2026_10_19_052752_create_categories_table.php. Awalan waktu itu menentukan urutan eksekusi. Laravel juga menebak nama tabel dari nama migration: pola create_xxx_table menghasilkan kerangka Schema::create('xxx'), sedangkan pola seperti add_category_id_to_products_table menghasilkan kerangka Schema::table('products') untuk mengubah tabel yang sudah ada.

    Migration juga bisa dibuat bersamaan dengan model:

    php artisan make:model Category -mfs

    Opsi -m membuat migration, -f factory, dan -s seeder. Daftar opsi lainnya ada di artikel perintah PHP Artisan.

    Struktur file migration

    <?php
    
    use Illuminate\Database\Migrations\Migration;
    use Illuminate\Database\Schema\Blueprint;
    use Illuminate\Support\Facades\Schema;
    
    return new class extends Migration
    {
        public function up(): void
        {
            Schema::create('categories', function (Blueprint $table) {
                $table->id();
                $table->string('name', 100);
                $table->string('slug')->unique();
                $table->text('description')->nullable();
                $table->boolean('is_active')->default(true);
                $table->timestamps();
            });
        }
    
        public function down(): void
        {
            Schema::dropIfExists('categories');
        }
    };

    Method up() dijalankan saat migrate, sedangkan down() saat rollback. Isi down() harus membatalkan apa yang dilakukan up(). Kalau up() membuat tabel, down() menghapusnya.

    Tipe Kolom dan Modifier yang Sering Dipakai

    Method Hasil
    $table->id() Primary key id, big integer auto increment
    $table->string('name', 100) VARCHAR, panjang bawaan 255
    $table->text('description') Teks panjang
    $table->integer('qty') / unsignedInteger / unsignedBigInteger Bilangan bulat
    $table->decimal('weight', 8, 2) Desimal presisi tetap
    $table->boolean('is_active') Benar/salah
    $table->date('birth_date') / dateTime / timestamp Tanggal dan waktu
    $table->json('meta') Data JSON
    $table->enum('status', ['draft', 'published']) Pilihan terbatas
    $table->foreignId('category_id') Kolom unsigned big integer untuk relasi
    $table->timestamps() Kolom created_at dan updated_at
    $table->softDeletes() Kolom deleted_at untuk soft delete

    Modifier ditulis berantai setelah tipe kolom: ->nullable() (boleh kosong), ->default(0), ->unique(), ->index(), ->comment('...'), dan ->after('id') (posisi kolom, hanya berlaku di MySQL/MariaDB).

    Untuk uang dalam rupiah, simpan sebagai bilangan bulat (unsignedBigInteger) daripada float. Float bisa menghasilkan selisih pembulatan.

    Foreign Key dan Relasi Antar Tabel

    Misalnya tabel products sudah ada dan ingin dihubungkan ke categories:

    php artisan make:migration add_category_id_to_products_table
    public function up(): void
    {
        Schema::table('products', function (Blueprint $table) {
            $table->foreignId('category_id')
                ->nullable()
                ->after('id')
                ->constrained()
                ->nullOnDelete();
        });
    }
    
    public function down(): void
    {
        Schema::table('products', function (Blueprint $table) {
            $table->dropConstrainedForeignId('category_id');
        });
    }

    constrained() otomatis menebak tabel tujuan dari nama kolom (category_id ke tabel categories, kolom id). Pilih aksi saat data induk dihapus:

    • ->cascadeOnDelete(): produk ikut terhapus saat kategorinya dihapus.
    • ->nullOnDelete(): category_id produk diisi NULL. Kolomnya harus nullable(). Kami menguji ini: setelah kategori dihapus, category_id produk berubah menjadi null.
    • ->restrictOnDelete(): kategori tidak bisa dihapus selama masih dipakai produk.

    Urutan file penting. Migration tabel categories harus berjalan sebelum migration yang memasang foreign key ke tabel itu.

    Menjalankan Migration: Semua Perintah dan Bedanya

    Perintah Yang terjadi Data hilang?
    php artisan migrate Menjalankan migration yang belum pernah dijalankan Tidak
    php artisan migrate:status Menampilkan migration yang sudah (Ran) dan belum (Pending), beserta nomor batch Tidak
    php artisan migrate --pretend Menampilkan query SQL tanpa menjalankannya Tidak
    php artisan migrate:rollback Membatalkan batch terakhir (semua file yang dijalankan di migrate terakhir) Ya, untuk tabel/kolom yang di-rollback
    php artisan migrate:rollback --step=1 Membatalkan satu file migration terakhir Ya, sebagian
    php artisan migrate:reset Menjalankan down() semua migration Ya, semua
    php artisan migrate:refresh Rollback semua lewat down(), lalu migrate lagi Ya, semua
    php artisan migrate:fresh Menghapus semua tabel langsung (tanpa down()), lalu migrate lagi Ya, semua
    php artisan migrate:fresh --seed Seperti di atas, lalu menjalankan seeder Ya, diganti data contoh

    Beda migrate:refresh dan migrate:fresh: refresh memanggil down() setiap file satu per satu, sehingga ikut menguji apakah method down() kamu benar. fresh langsung menghapus semua tabel, jadi lebih cepat dan tetap jalan walaupun ada down() yang rusak. Keduanya menghapus semua data. Jangan pernah dijalankan di database production.

    Menjalankan satu file migration saja

    Pakai opsi --path dengan path file relatif dari root project:

    php artisan migrate --path=database/migrations/2026_10_19_052753_add_category_id_to_products_table.php

    Hanya file itu yang dijalankan, migration lain yang masih pending dibiarkan. Kalau file tersebut sudah pernah dijalankan, hasilnya Nothing to migrate.

    Di server production

    php artisan migrate --force

    Di environment production, Laravel meminta konfirmasi sebelum menjalankan migration. --force melewati konfirmasi itu, biasanya dipakai di skrip deploy. Kalau aplikasi di-deploy ke beberapa server sekaligus, tambahkan --isolated agar migration tidak dijalankan dua server bersamaan.

    Menjalankan migration dari kode

    use Illuminate\Support\Facades\Artisan;
    
    Artisan::call('migrate', ['--force' => true]);

    Nilai kembalian 0 berarti berhasil. Cara ini kadang dipakai di installer aplikasi atau job deploy.

    Mengubah, Mengganti Nama, dan Menghapus Kolom

    Jangan mengedit file migration yang sudah dijalankan di server atau sudah dipakai rekan satu tim. Buat migration baru untuk setiap perubahan.

    php artisan make:migration change_price_on_products_table
    public function up(): void
    {
        Schema::table('products', function (Blueprint $table) {
            $table->unsignedBigInteger('price')->default(0)->change();
        });
    }
    
    public function down(): void
    {
        Schema::table('products', function (Blueprint $table) {
            $table->unsignedInteger('price')->change();
        });
    }

    Aturan penting dari dokumentasi Laravel: saat memakai change(), tulis ulang semua modifier yang ingin dipertahankan (nullable, default, unsigned, comment). Modifier yang tidak ditulis akan hilang. Sejak Laravel 11, change() tidak lagi butuh paket doctrine/dbal.

    Mengganti nama dan menghapus kolom:

    Schema::table('products', function (Blueprint $table) {
        $table->renameColumn('stock', 'quantity');
        $table->dropColumn(['old_code', 'notes']);
    });

    Error Migration yang Sering Muncul

    • Base table or view already exists. Tabelnya sudah dibuat manual atau dari migration lain, tetapi tidak tercatat di tabel migrations. Di lokal, cara tercepat adalah migrate:fresh. Di production, hapus atau ubah migration yang bentrok, jangan menghapus tabel berisi data.
    • Error foreign key saat migrate. Tabel tujuan belum ada karena urutan file salah, atau tipe kolomnya berbeda. Pakai foreignId() agar tipenya cocok dengan id().
    • SQLSTATE[HY000] [1045] Access denied atau could not find driver. Masalah koneksi, bukan migration. Cek DB_* di .env dan ekstensi pdo_mysql/pdo_sqlite. Panduan lengkapnya ada di artikel instalasi Laravel 12.
    • Rollback gagal. Method down() kosong atau tidak membalik up() dengan benar. Uji dengan migrate:refresh di lokal sebelum dipakai di server.
    • Perintah meminta konfirmasi lalu berhenti di skrip deploy. Environment-nya production. Tambahkan --force.

    Pertanyaan yang Sering Diajukan

    Bagaimana cara menjalankan satu migration tertentu?

    Pakai php artisan migrate --path=database/migrations/nama_file.php. Untuk membatalkan satu migration terakhir, pakai php artisan migrate:rollback --step=1.

    Apa beda migrate:fresh dan migrate:refresh?

    migrate:refresh menjalankan down() setiap migration lalu migrate ulang. migrate:fresh langsung menghapus semua tabel lalu migrate ulang. Keduanya menghapus semua data.

    Apakah boleh mengedit file migration lama?

    Boleh hanya kalau migration itu belum pernah dijalankan di tempat lain selain laptop kamu. Kalau sudah ada di server atau dipakai tim, buat migration baru.

    Apa itu database migration secara umum?

    Istilah ini punya dua arti. Di Laravel, migration berarti versi struktur database dalam bentuk kode. Di luar konteks framework, “migrasi data” juga bisa berarti memindahkan data dari satu sistem ke sistem lain, yang merupakan pekerjaan berbeda.

    Setelah tabel siap, hubungkan ke URL lewat route Laravel dan logika CRUD di controller Laravel 12. Kalau database kamu PostgreSQL, lihat juga tutorial PostgreSQL di Laravel.

    Perlu memindahkan data dari sistem lama atau Excel ke aplikasi Laravel baru? Tim Arrazy Inovasi di Purwokerto mengerjakan pembuatan sistem aplikasi custom, termasuk perancangan database dan migrasi datanya.

    Diskusikan project kamu →

  • Tutorial PostgreSQL di Laravel: Setup, JSONB, dan Full-Text Search

    Laravel secara default menggunakan MySQL. Tapi kalau proyek Anda butuh fitur seperti JSON columns yang lebih canggih, full-text search bawaan, atau JSONB, PostgreSQL adalah pilihan yang solid.

    Artikel ini membahas cara setup PostgreSQL di Laravel, termasuk konfigurasi, perbedaan dengan MySQL, dan fitur-fitur PostgreSQL yang bisa dimanfaatkan langsung dari Eloquent.

    Instalasi dan Konfigurasi

    Pastikan extension PHP untuk PostgreSQL sudah aktif:

    # Ubuntu/Debian
    sudo apt install php-pgsql
    
    # Cek apakah sudah aktif
    php -m | grep pdo_pgsql

    Edit file .env:

    DB_CONNECTION=pgsql
    DB_HOST=127.0.0.1
    DB_PORT=5432
    DB_DATABASE=nama_database
    DB_USERNAME=postgres
    DB_PASSWORD=password_anda

    Tidak perlu ubah konfigurasi lain. Laravel sudah punya driver PostgreSQL bawaan.

    Perbedaan Sintaks Migration

    Sebagian besar sintaks migration sama antara MySQL dan PostgreSQL. Perbedaan utama ada di beberapa tipe data:

    Schema::create('products', function (Blueprint $table) {
        $table->id();
        $table->string('name');
        $table->decimal('price', 10, 2);
    
        // PostgreSQL: pakai jsonb, bukan json biasa
        $table->jsonb('metadata')->nullable();
    
        // PostgreSQL: UUID native
        $table->uuid('external_id')->unique();
    
        // Enum di PostgreSQL butuh cast berbeda
        $table->string('status')->default('active');
    
        $table->timestamps();
    });

    Tips: Di PostgreSQL, pakai jsonb daripada json. JSONB disimpan dalam format binary yang lebih efisien untuk query dan indexing.

    Query JSON dengan PostgreSQL

    PostgreSQL punya operator JSON yang sangat powerful. Di Laravel, Anda bisa pakai operator -> dan ->> langsung di Eloquent:

    // Cari produk berdasarkan field di dalam JSON
    $products = Product::where('metadata->category', 'elektronik')->get();
    
    // Atau dengan whereJsonContains
    $products = Product::whereJsonContains('metadata->tags', 'promo')->get();
    
    // Query nested JSON
    $products = Product::where('metadata->specs->weight', '>', 500)->get();
    

    Contoh migration dengan index JSONB:

    Schema::create('products', function (Blueprint $table) {
        $table->id();
        $table->string('name');
        $table->jsonb('metadata')->nullable();
        $table->timestamps();
    });
    
    // Tambah index GIN untuk performa query JSONB
    DB::statement('CREATE INDEX products_metadata_gin ON products USING GIN (metadata jsonb_path_ops)');
    

    Full-Text Search

    PostgreSQL punya full-text search bawaan yang bisa langsung dipakai tanpa plugin tambahan:

    // Migration: tambah kolom tsvector
    $table->tsvector('search_vector')->nullable();
    
    // Isi kolom tsvector saat insert/update
    DB::statement("
        UPDATE articles
        SET search_vector = to_tsvector('indonesian', coalesce(title, '') || ' ' || coalesce(content, ''))
    ");
    
    // Query full-text search
    $results = Article::whereRaw(
        "search_vector @@ plainto_tsquery('indonesian', ?)",
        [$keyword]
    )->get();

    Untuk proyek besar, buat trigger PostgreSQL agar search_vector otomatis diperbarui saat data berubah.

    UUID sebagai Primary Key

    PostgreSQL mendukung UUID natively. Cara pakai di Laravel 12:

    Schema::create('orders', function (Blueprint $table) {
        $table->uuid('id')->primary()->default(DB::raw('gen_random_uuid()'));
        $table->string('status');
        $table->timestamps();
    });

    Di model:

    <?php
    
    namespace AppModels;
    
    use IlluminateDatabaseEloquentModel;
    use IlluminateDatabaseEloquentConcernsHasUuids;
    
    class Order extends Model
    {
        use HasUuids;
    }

    Transaksi dan Savepoint

    PostgreSQL mendukung nested transaction lewat savepoint. Di Laravel ini otomatis dipakai saat Anda nested DB::transaction():

    DB::transaction(function () {
        $order = Order::create([...]);
    
        DB::transaction(function () use ($order) {
            // Ini pakai savepoint di PostgreSQL
            // Kalau gagal, hanya bagian ini yang di-rollback
            foreach ($order->items as $item) {
                Inventory::decrement($item->product_id, $item->qty);
            }
        });
    
        $order->update(['status' => 'confirmed']);
    });

    Koneksi Multiple Database

    Kalau Anda pakai MySQL dan PostgreSQL bersamaan, tambahkan konfigurasi di config/database.php:

    'connections' => [
        'mysql' => [
            'driver' => 'mysql',
            // ...
        ],
        'pgsql' => [
            'driver'   => 'pgsql',
            'host'     => env('PGSQL_HOST', '127.0.0.1'),
            'port'     => env('PGSQL_PORT', '5432'),
            'database' => env('PGSQL_DATABASE'),
            'username' => env('PGSQL_USERNAME'),
            'password' => env('PGSQL_PASSWORD'),
        ],
    ],

    Di model, tentukan koneksi yang dipakai:

    class AnalyticsEvent extends Model
    {
        protected $connection = 'pgsql';
    }

    Baca Juga

    Butuh tim yang berpengalaman setup aplikasi Laravel dengan PostgreSQL di production? Lihat layanan pengembangan aplikasi kami.

  • Apa Itu PHP Artisan? Perintah Artisan Laravel yang Wajib Dikuasai

    PHP Artisan adalah command line bawaan Laravel. Lewat perintah php artisan, kamu bisa menjalankan server lokal, membuat file controller dan model, menjalankan migration database, membersihkan cache, sampai menyalakan mode maintenance, tanpa menulis kode itu satu per satu. Artikel ini menjelaskan cara kerja Artisan dan perintah yang paling sering dipakai di Laravel 12, lengkap dengan contoh. Semua perintah sudah kami jalankan di project Laravel 12.70.

    Kalau yang kamu cari adalah cara membuat perintah Artisan sendiri, bacaannya ada di membuat custom Artisan command di Laravel 12. Artikel ini fokus pada perintah bawaan.

    Apa Itu PHP Artisan dan Cara Menjalankannya

    Artisan sebenarnya adalah file PHP bernama artisan di root project Laravel. Perintah php artisan berarti “jalankan file artisan dengan PHP”. Karena itu, perintahnya harus dijalankan dari folder project, yaitu folder yang berisi file artisan, composer.json, dan .env.

    cd toko-online
    php artisan list

    php artisan list (atau cukup php artisan) menampilkan semua perintah yang tersedia. Di project Laravel 12 yang baru, jumlahnya lebih dari seratus. Perintah dikelompokkan dengan awalan, misalnya make: untuk membuat file, migrate: untuk database, dan route: untuk routing.

    Lupa opsi sebuah perintah? Tambahkan --help:

    php artisan make:model --help
    php artisan list make

    Perintah kedua hanya menampilkan perintah dengan awalan make:. Paket yang kamu pasang (misalnya Livewire atau Sanctum) juga bisa menambah perintah baru ke daftar ini.

    Perintah PHP Artisan yang Paling Sering Dipakai

    Perintah Fungsi
    Menjalankan aplikasi
    php artisan serve Menjalankan server lokal di http://127.0.0.1:8000
    php artisan tinker Konsol interaktif untuk mencoba kode dan query Eloquent
    php artisan about Ringkasan versi Laravel, PHP, environment, cache, dan driver
    Membuat file (generator)
    php artisan make:controller Membuat controller
    php artisan make:model Membuat model Eloquent, bisa sekaligus migration, factory, seeder, dan controller
    php artisan make:migration Membuat file migration
    php artisan make:seeder / make:factory Membuat seeder dan factory data
    php artisan make:request Membuat Form Request untuk validasi
    php artisan make:middleware Membuat middleware
    Database
    php artisan migrate Menjalankan migration yang belum dijalankan
    php artisan migrate:fresh --seed Menghapus semua tabel, migrate ulang, lalu mengisi data contoh
    php artisan db:seed Menjalankan seeder
    Routing dan cache
    php artisan route:list Menampilkan semua route
    php artisan optimize Men-cache config, route, event, dan view untuk production
    php artisan optimize:clear Menghapus semua cache tersebut
    php artisan config:clear / cache:clear / view:clear Menghapus cache tertentu saja
    Lain-lain
    php artisan key:generate Mengisi APP_KEY di .env
    php artisan storage:link Membuat symlink public/storage agar file upload bisa diakses publik
    php artisan queue:work Memproses job di antrian
    php artisan schedule:work Menjalankan scheduler di lokal
    php artisan down / up Menyalakan dan mematikan mode maintenance
    php artisan install:api Membuat routes/api.php dan memasang Sanctum
    php artisan test Menjalankan test

    php artisan serve: Menjalankan Server Lokal

    php artisan serve
    
    # ganti port
    php artisan serve --port=8080
    
    # bisa diakses dari HP di jaringan yang sama
    php artisan serve --host=0.0.0.0 --port=8000

    Kalau port 8000 sedang dipakai, serve otomatis mencoba port berikutnya (8001, 8002, dan seterusnya, sampai 10 kali). Perhatikan alamat yang tercetak di terminal. Di Laravel 12 ada juga composer run dev, yang menjalankan serve, queue worker, log, dan Vite sekaligus. Penjelasannya ada di panduan cara install Laravel 12.

    php artisan serve hanya untuk development. Di server production, gunakan web server seperti Nginx atau Apache dengan PHP-FPM.

    Perintah make: untuk Membuat File

    Perintah make: membuat file dari template (stub) di lokasi yang benar, lengkap dengan namespace. Yang paling hemat waktu adalah make:model dengan opsi gabungan:

    # model + migration + factory + seeder
    php artisan make:model Category -mfs
    
    # model + migration + resource controller
    php artisan make:model Product -mcr
    
    # semua: migration, seeder, factory, policy, resource controller, form request
    php artisan make:model Order --all
    Opsi Yang ikut dibuat
    -m Migration
    -f Factory
    -s Seeder
    -c Controller
    -r Controller dijadikan resource controller
    --api Controller dijadikan API resource controller
    -R Form request untuk store dan update
    --policy Policy
    -a / --all Semua di atas (migration, seeder, factory, policy, resource controller, form request)

    Detail tiap jenis controller ada di panduan membuat controller di Laravel 12, dan isi file migration dibahas di cara membuat migration Laravel.

    Laravel 12 juga punya generator untuk class PHP biasa: make:class, make:enum, make:interface, dan make:trait.

    php artisan tinker: Mencoba Kode Langsung

    Tinker adalah konsol interaktif yang sudah memuat seluruh aplikasi. Berguna untuk mengecek data atau mencoba query tanpa membuat route:

    php artisan tinker
    
    > App\Models\User::count();
    > App\Models\Category::where('is_active', true)->pluck('name');
    > exit

    Untuk menjalankan satu baris tanpa masuk ke konsol, pakai --execute:

    php artisan tinker --execute="echo App\Models\Category::count();"

    Tinker bekerja pada database sungguhan. Hati-hati menjalankan delete() atau update() di server production.

    Perintah Cache dan Optimasi

    # production, setelah deploy
    php artisan optimize
    
    # saat .env atau route diubah tetapi perubahan tidak terbaca
    php artisan optimize:clear

    Setelah config:cache (bagian dari optimize), Laravel tidak lagi membaca file .env secara langsung. Perubahan .env baru terbaca setelah cache dihapus atau dibuat ulang. Ini penyebab paling umum keluhan “sudah ganti password database di .env tapi masih error”.

    Mode Maintenance

    php artisan down
    php artisan up

    Selama mode maintenance aktif, semua pengunjung mendapat halaman 503. Untuk tetap bisa mengakses aplikasi sambil maintenance, pakai php artisan down --secret="kode-rahasia", lalu buka https://domainkamu.com/kode-rahasia sekali di browser.

    Menjalankan Artisan dari Kode

    Perintah Artisan juga bisa dipanggil dari kode PHP, misalnya dari controller atau job, lewat facade Artisan:

    use Illuminate\Support\Facades\Artisan;
    
    $exitCode = Artisan::call('migrate', ['--force' => true]);
    $output = Artisan::output();

    Nilai 0 berarti perintah berhasil. Opsi --force diperlukan karena perintah migrate meminta konfirmasi saat environment-nya production.

    Error Artisan yang Sering Muncul

    • Could not open input file: artisan. Terminal tidak sedang berada di folder project. Masuk dulu dengan cd nama-project.
    • Command "xxx" is not defined. Salah ketik, atau perintah itu milik paket yang belum dipasang. Cek dengan php artisan list.
    • Error versi PHP atau vendor/autoload.php tidak ditemukan. Dependency belum dipasang. Jalankan composer install terlebih dahulu, dan pastikan php -v sesuai kebutuhan Laravel 12 (PHP 8.2 ke atas).
    • db:show atau db:table gagal dengan pesan ekstensi intl. Perintah ini memformat angka dan butuh ekstensi PHP intl. Aktifkan ekstensinya, atau pakai migrate:status untuk sekadar mengecek migration.

    Pertanyaan yang Sering Diajukan

    Apa itu php artisan serve?

    Perintah untuk menjalankan server pengembangan bawaan PHP pada project Laravel, secara bawaan di http://127.0.0.1:8000. Hanya untuk lokal, bukan untuk production.

    Apa beda Artisan dan Composer?

    Composer adalah package manager PHP: memasang dan memperbarui library. Artisan adalah command line milik Laravel untuk mengelola project yang sudah ada. Composer membuat project Laravel, Artisan dipakai setelahnya.

    Apakah Laravel gratis?

    Ya. Laravel adalah framework open source berlisensi MIT, termasuk Artisan. Biaya baru muncul untuk layanan pihak ketiga seperti hosting atau layanan berbayar di ekosistem Laravel.

    Bagaimana cara membuat perintah Artisan sendiri?

    Dengan php artisan make:command NamaCommand. Langkah lengkap, termasuk argumen, opsi, dan penjadwalan, ada di artikel custom Artisan command.

    Lanjut belajar: atur URL aplikasi lewat routing Laravel, atau lihat urutan materi lengkap di halaman tutorial Laravel.

    Butuh aplikasi berbasis Laravel untuk bisnis, tetapi tim internal belum punya waktu? Tim Arrazy Inovasi di Purwokerto mengerjakan pembuatan sistem aplikasi custom dari perancangan sampai server production.

    Konsultasi project Laravel →

  • Membuat Controller di Laravel 12: Resource, API, dan Invokable

    Membuat Controller di Laravel 12: Resource, API, dan Invokable

    Cara membuat controller di Laravel 12 adalah menjalankan php artisan make:controller NamaController. File-nya akan dibuat di app/Http/Controllers, lalu kamu menghubungkannya ke URL lewat routes/web.php atau routes/api.php. Panduan ini membahas empat jenis controller yang paling sering dipakai: controller biasa, resource controller untuk CRUD, API controller, dan invokable controller. Semua contoh kode di sini sudah kami jalankan dan uji dengan feature test di Laravel 12.70 (PHP 8.5), jadi bisa langsung kamu pakai.

    Belum punya project? Ikuti dulu cara install Laravel 12, lalu kembali ke sini.

    Apa Itu Controller di Laravel?

    Controller adalah class yang mengumpulkan logika penanganan request. Daripada menulis semua logika di dalam closure di file route, kamu memindahkannya ke method di controller. Hasilnya, file route tetap pendek dan logika untuk satu fitur (misalnya produk) ada di satu tempat.

    Di Laravel 12, base class App\Http\Controllers\Controller sengaja dibuat kosong. Berbeda dengan Laravel 10 ke bawah, base controller tidak lagi membawa trait AuthorizesRequests dan ValidatesRequests. Akibatnya, kode lama yang memanggil $this->authorize() atau $this->validate() di controller akan error. Gantinya, pakai Gate::authorize() dan $request->validate(), atau Form Request seperti contoh di bawah.

    Jenis Controller dan Perintah Artisan-nya

    Jenis Perintah Method yang dibuat Dipakai untuk
    Biasa php artisan make:controller ProductController Kosong Halaman atau aksi bebas
    Resource php artisan make:controller ProductController --resource index, create, store, show, edit, update, destroy CRUD dengan halaman form (Blade)
    API php artisan make:controller ProductController --api index, store, show, update, destroy REST API (tanpa create dan edit)
    Invokable php artisan make:controller ExportProductController --invokable __invoke Satu aksi saja, misalnya ekspor

    Opsi tambahan yang berguna:

    • --model=Product membuat type-hint model di parameter method, sehingga route model binding langsung aktif.
    • --requests (dipakai bersama --resource atau --api) membuat class StoreProductRequest dan UpdateProductRequest untuk validasi.
    • Nama dengan garis miring, misalnya Api/ProductController, membuat controller di subfolder dengan namespace App\Http\Controllers\Api.

    Mau tahu perintah Artisan lain? Lihat juga panduan membuat custom Artisan command di Laravel 12.

    Contoh Kasus: Data Produk

    Semua contoh di artikel ini memakai satu model, Product, dengan kolom nama, harga, dan stok. Buat model sekaligus migration-nya:

    php artisan make:model Product -m

    Isi migration di database/migrations/xxxx_create_products_table.php:

    public function up(): void
    {
        Schema::create('products', function (Blueprint $table) {
            $table->id();
            $table->string('name');
            $table->unsignedInteger('price');
            $table->unsignedInteger('stock')->default(0);
            $table->timestamps();
        });
    }

    Lalu izinkan mass assignment di app/Models/Product.php:

    <?php
    
    namespace App\Models;
    
    use Illuminate\Database\Eloquent\Model;
    
    class Product extends Model
    {
        protected $fillable = ['name', 'price', 'stock'];
    }

    Jalankan php artisan migrate. Penjelasan lengkap soal tipe kolom, foreign key, dan perintah rollback ada di panduan migration Laravel.

    Membuat Controller Biasa

    Controller biasa cocok untuk halaman yang tidak mengikuti pola CRUD.

    php artisan make:controller PageController
    <?php
    
    namespace App\Http\Controllers;
    
    use Illuminate\View\View;
    
    class PageController extends Controller
    {
        public function about(): View
        {
            return view('about');
        }
    }

    Daftarkan di routes/web.php (dasar-dasar route ada di artikel routing Laravel). Perhatikan baris use di atas, karena ini sumber error paling umum:

    use App\Http\Controllers\PageController;
    use Illuminate\Support\Facades\Route;
    
    Route::get('/tentang', [PageController::class, 'about'])->name('about');

    Resource Controller untuk CRUD

    Resource controller membuat tujuh method CRUD sekaligus. Dengan opsi --model dan --requests, Laravel juga menyiapkan type-hint model dan class validasi:

    php artisan make:controller ProductController --model=Product --resource --requests

    Perintah itu membuat tiga file: ProductController, StoreProductRequest, dan UpdateProductRequest. Isi aturan validasinya dulu. Perhatikan method authorize(): stub bawaan mengembalikan false, sehingga semua request akan ditolak dengan 403 kalau tidak kamu ubah.

    <?php
    
    namespace App\Http\Requests;
    
    use Illuminate\Foundation\Http\FormRequest;
    
    class StoreProductRequest extends FormRequest
    {
        public function authorize(): bool
        {
            return true; // stub bawaan bernilai false, sesuaikan dengan aturan akses
        }
    
        public function rules(): array
        {
            return [
                'name'  => ['required', 'string', 'max:255'],
                'price' => ['required', 'integer', 'min:0'],
                'stock' => ['nullable', 'integer', 'min:0'],
            ];
        }
    }

    Isi UpdateProductRequest dengan aturan yang sama. Sekarang controller-nya:

    <?php
    
    namespace App\Http\Controllers;
    
    use App\Http\Requests\StoreProductRequest;
    use App\Http\Requests\UpdateProductRequest;
    use App\Models\Product;
    use Illuminate\Http\RedirectResponse;
    use Illuminate\View\View;
    
    class ProductController extends Controller
    {
        public function index(): View
        {
            $products = Product::latest()->paginate(10);
    
            return view('products.index', compact('products'));
        }
    
        public function create(): View
        {
            return view('products.create');
        }
    
        public function store(StoreProductRequest $request): RedirectResponse
        {
            Product::create($request->validated());
    
            return redirect()->route('products.index')
                ->with('status', 'Produk berhasil ditambahkan.');
        }
    
        public function show(Product $product): View
        {
            return view('products.show', compact('product'));
        }
    
        public function edit(Product $product): View
        {
            return view('products.edit', compact('product'));
        }
    
        public function update(UpdateProductRequest $request, Product $product): RedirectResponse
        {
            $product->update($request->validated());
    
            return redirect()->route('products.show', $product)
                ->with('status', 'Produk berhasil diperbarui.');
        }
    
        public function destroy(Product $product): RedirectResponse
        {
            $product->delete();
    
            return redirect()->route('products.index')
                ->with('status', 'Produk berhasil dihapus.');
        }
    }

    Kalau validasi gagal, Laravel otomatis mengembalikan pengguna ke form sebelumnya beserta pesan error. Kalau ID produk tidak ada, route model binding otomatis menghasilkan 404. Kamu tidak perlu menulis findOrFail sendiri.

    Cukup satu baris di routes/web.php:

    use App\Http\Controllers\ProductController;
    
    Route::resource('products', ProductController::class);

    Baris itu menghasilkan tujuh route berikut. Cek dengan php artisan route:list --except-vendor.

    Method HTTP URI Method controller Nama route
    GET /products index products.index
    GET /products/create create products.create
    POST /products store products.store
    GET /products/{product} show products.show
    GET /products/{product}/edit edit products.edit
    PUT/PATCH /products/{product} update products.update
    DELETE /products/{product} destroy products.destroy

    Butuh sebagian saja? Pakai Route::resource('products', ProductController::class)->only(['index', 'show']); atau ->except(['destroy']).

    Form HTML hanya mengenal GET dan POST. Untuk update dan hapus, tambahkan directive @method di form Blade:

    <form method="POST" action="{{ route('products.update', $product) }}">
        @csrf
        @method('PUT')
        <input name="name" value="{{ old('name', $product->name) }}">
        <input name="price" type="number" value="{{ old('price', $product->price) }}">
        <button type="submit">Simpan</button>
    </form>

    API Controller di Laravel 12

    Di Laravel 12, file routes/api.php tidak ada secara bawaan. Buat dulu dengan:

    php artisan install:api

    Perintah ini memasang Laravel Sanctum, membuat routes/api.php, dan mendaftarkannya di bootstrap/app.php. Semua route di file ini otomatis berawalan /api. Setelah itu buat API controller di subfolder agar tidak bentrok dengan controller web:

    php artisan make:controller Api/ProductController --api --model=Product
    <?php
    
    namespace App\Http\Controllers\Api;
    
    use App\Http\Controllers\Controller;
    use App\Http\Requests\StoreProductRequest;
    use App\Http\Requests\UpdateProductRequest;
    use App\Models\Product;
    use Illuminate\Http\JsonResponse;
    use Illuminate\Http\Response;
    
    class ProductController extends Controller
    {
        public function index(): JsonResponse
        {
            return response()->json(Product::latest()->paginate(10));
        }
    
        public function store(StoreProductRequest $request): JsonResponse
        {
            $product = Product::create($request->validated());
    
            return response()->json($product, 201);
        }
    
        public function show(Product $product): JsonResponse
        {
            return response()->json($product);
        }
    
        public function update(UpdateProductRequest $request, Product $product): JsonResponse
        {
            $product->update($request->validated());
    
            return response()->json($product);
        }
    
        public function destroy(Product $product): Response
        {
            $product->delete();
    
            return response()->noContent();
        }
    }

    Daftarkan di routes/api.php:

    use App\Http\Controllers\Api\ProductController;
    use Illuminate\Support\Facades\Route;
    
    Route::name('api.')->group(function () {
        Route::apiResource('products', ProductController::class);
    });

    Kenapa dibungkus Route::name('api.')? Saat kami menguji, Route::apiResource('products', ...) di routes/api.php menghasilkan nama route yang sama persis dengan resource web: products.index, products.store, dan seterusnya. Awalan URL /api tidak ikut mengubah nama route. Akibatnya route('products.index') di controller web bisa mengarah ke URL API. Dengan awalan nama api., nama route API menjadi api.products.index dan tidak bentrok lagi.

    Perilaku yang kamu dapat tanpa kode tambahan: request POST /api/products dengan data tidak valid mendapat respons 422 berisi pesan error dalam JSON, ID yang tidak ada mendapat 404, dan hapus yang berhasil mendapat 204 No Content. Untuk mengatur bentuk JSON lebih rapi (menyembunyikan kolom tertentu atau mengganti nama field), lanjutkan dengan API Resource (php artisan make:resource ProductResource).

    Invokable Controller (Controller Satu Aksi)

    Kalau sebuah aksi tidak masuk pola CRUD dan cukup kompleks, beri ia controller sendiri dengan satu method __invoke. Contohnya ekspor produk ke CSV:

    php artisan make:controller ExportProductController --invokable
    <?php
    
    namespace App\Http\Controllers;
    
    use App\Models\Product;
    use Symfony\Component\HttpFoundation\StreamedResponse;
    
    class ExportProductController extends Controller
    {
        public function __invoke(): StreamedResponse
        {
            return response()->streamDownload(function () {
                $out = fopen('php://output', 'w');
                fputcsv($out, ['id', 'name', 'price', 'stock']);
    
                Product::orderBy('id')->each(function (Product $product) use ($out) {
                    fputcsv($out, [$product->id, $product->name, $product->price, $product->stock]);
                });
    
                fclose($out);
            }, 'products.csv', ['Content-Type' => 'text/csv']);
        }
    }

    Route-nya tidak perlu menyebut nama method:

    use App\Http\Controllers\ExportProductController;
    
    Route::get('/products/export', ExportProductController::class)->name('products.export');
    Route::resource('products', ProductController::class);

    Urutan itu penting. Route /products/export harus didaftarkan sebelum Route::resource. Kalau terbalik, URL tersebut tertangkap route /products/{product} dan Laravel mencari produk dengan ID “export”, lalu mengembalikan 404. Penjelasan lebih dalam soal pola ini ada di artikel invokable controller di Laravel.

    Middleware di Controller Laravel 12

    Cara lama $this->middleware('auth') di constructor tidak berlaku lagi. Ada dua pilihan. Pertama, pasang di route:

    Route::resource('products', ProductController::class)->middleware('auth');

    Kedua, implementasikan interface HasMiddleware di controller:

    <?php
    
    namespace App\Http\Controllers;
    
    use Illuminate\Routing\Controllers\HasMiddleware;
    use Illuminate\Routing\Controllers\Middleware;
    
    class ReportController extends Controller implements HasMiddleware
    {
        public static function middleware(): array
        {
            return [
                'auth',
                new Middleware('verified', only: ['download']),
            ];
        }
    
        public function index()
        {
            // semua pengguna yang sudah login
        }
    
        public function download()
        {
            // hanya pengguna dengan email terverifikasi
        }
    }

    Catatan: middleware verified hanya berpengaruh kalau model User mengimplementasikan interface MustVerifyEmail. Untuk aturan hak akses yang lebih detail, misalnya hanya pemilik data yang boleh mengedit, pakai Policy. Panduannya ada di Policy dan Gate di Laravel 12.

    Error yang Sering Muncul Saat Membuat Controller

    • Target class [ProductController] does not exist. Baris use App\Http\Controllers\ProductController; lupa ditulis di file route, atau namespace di file controller salah. Format string lama 'ProductController@index' juga tidak bisa dipakai tanpa konfigurasi namespace, jadi gunakan sintaks array [ProductController::class, 'index'].
    • 403 This action is unauthorized padahal belum memasang hak akses. Method authorize() di Form Request masih return false.
    • Call to undefined method ... authorize(). Kode dari Laravel 10 ke bawah memakai $this->authorize(). Ganti dengan Gate::authorize('update', $product) (import Illuminate\Support\Facades\Gate).
    • 405 Method Not Allowed. Form update atau hapus tidak memakai @method('PUT') atau @method('DELETE').
    • 404 pada route tambahan seperti /products/export. Route tambahan didaftarkan setelah Route::resource.
    • Route model binding tidak jalan (model selalu kosong). Nama parameter di method harus sama dengan parameter di route. Untuk resource products, parameternya {product}, jadi method harus menerima Product $product.

    Pertanyaan yang Sering Diajukan

    Apa beda resource controller dan API controller?

    Resource controller punya tujuh method, termasuk create dan edit yang menampilkan form HTML. API controller (opsi --api) hanya lima method karena API tidak butuh halaman form.

    Apakah controller di Laravel 12 masih harus extends Controller?

    Tidak wajib. Dokumentasi Laravel menyebut controller tidak harus meng-extend base class. Stub bawaan tetap meng-extend Controller supaya kamu punya satu tempat untuk menaruh method bersama.

    Di mana file controller disimpan?

    Di app/Http/Controllers. Kalau nama yang kamu berikan mengandung garis miring, misalnya Admin/ProductController, file disimpan di subfolder Admin dengan namespace yang sesuai.

    Apakah cara membuat controller di Laravel 11 dan 12 sama?

    Sama. Perintah make:controller, base controller kosong, HasMiddleware, dan install:api sudah ada sejak Laravel 11. Yang berubah ada pada perbandingan dengan Laravel 10 ke bawah.

    Langkah berikutnya: kalau ingin halaman interaktif tanpa menulis JavaScript, pelajari Laravel Volt dan Livewire. Semua materi Laravel kami ada di halaman tutorial Laravel.

    Butuh sistem berbasis Laravel untuk bisnis, misalnya aplikasi inventori, kasir, atau API untuk aplikasi mobile? Tim Arrazy Inovasi mengerjakan pembuatan sistem aplikasi custom dari perancangan sampai server production.

    Konsultasi project Laravel →

  • Membuat Controller di Laravel 11

    Membuat Controller di Laravel 11

    Pada artikel ini kita akan membahas cara membuat controller di laravel 11 secara praktis, dari persiapan lingkungan hingga implementasi praktis dan best practice. Laravel sebagai framework PHP modern memudahkan pengelolaan arsitektur aplikasi dengan konsep controller, routing, dan dependency injection. Dalam konteks proyek nyata, memahami cara membangun controller yang bersih, terstruktur, dan maintainable adalah pondasi utama. Panduan ini dirancang untuk pemula hingga developer berpengalaman, dengan contoh kode yang bisa langsung dicoba di lingkungan lokal. Kami juga akan membahas bagaimana menjaga kode tetap scalable, mudah dites, dan mudah dipelihara seiring bertambahnya kompleksitas proyek. Kami menekankan bahwa praktik yang tepat saat membuat controller di laravel 11 akan mempercepat pengembangan, mengurangi bug, serta meningkatkan kemudahan kolaborasi antar tim.

    Persiapan Lingkungan Laravel 11

    Untuk memulai, pastikan lingkungan pengembangan sudah siap. Anda memerlukan PHP >= 8.1, Composer, dan Laravel 11. Instalasi bisa dilakukan melalui Composer create-project atau laravel/installer. Pastikan ekstensi yang diperlukan seperti PDO, BCMath, OpenSSL tersedia. Setelah itu, buat project baru:

    composer create-project laravel/laravel blog-laravel11 "11.x"

    Langkah ini menyiapkan fondasi MVC standar, sehingga Anda bisa mulai membangun membuat controller di laravel 11 dengan perintah Artisan. Bila Anda membutuhkan referensi langkah demi langkah, lihat Layanan Arrazy Inovasi untuk solusi terkait pengembangan web.

    Selain itu, sebaiknya Anda merujuk juga pada Dokumentasi Laravel Resmi dan Dokumentasi PHP untuk memahami opsi konfigurasi serta best practice penerapan arsitektur pada proyek nyata.

    Membuat Controller Pertama dengan Artisan

    Setelah proyek siap, langkah selanjutnya adalah membuat controller pertama. Gunakan Artisan untuk meng-generate controller secara cepat:

    php artisan make:controller HelloWorldController

    Untuk sebuah API atau halaman berita, Anda juga bisa membuat membuat controller di laravel 11 dengan opsi resource:

    php artisan make:controller ArticleController --resource

    Berikut contoh struktur dasar controller yang bisa Anda adaptasi:

    namespace AppHttpControllers;
    
    use AppHttpControllersController;
    use IlluminateHttpRequest;
    
    class HelloWorldController extends Controller
    {
    public function index()
    {
    return view('hello.index');
    }
    }

    Pelajari bagaimana alur request menuju controller bisa divalidasi sederhana dengan Request type hinting, sehingga membuat controller di laravel 11 menjadi lebih elegan dan mudah di-test. Untuk referensi praktis, kunjungi Layanan Arrazy Inovasi.

    Routing dan Pemanfaatan Controller

    Setelah controller dibuat, ikuti langkah berikut untuk menghubungkannya dengan routing. Untuk halaman biasa:

    // routes/web.php
    use AppHttpControllersHelloWorldController;
    Route::get('/hello', [HelloWorldController::class, 'index']);

    Untuk struktur CRUD dengan resource controller:

    Route::resource('articles', ArticleController::class);

    Dengan routing yang terdefinisi, membuat controller di laravel 11 akan segera bisa diakses melalui URL. Jika Anda ingin solusi end-to-end, lihat Layanan Arrazy Inovasi Teknologi.

    Resource Controller untuk CRUD

    Resource controller memudahkan implementasi operasi Create, Read, Update, Delete. Dengan satu deklarasi, Laravel akan mengerti semua rute konvensional. Berikut contoh metode inti yang umum dipakai:

    public function index() { ... }
    public function show($id) { ... }
    public function store(Request $request) { ... }
    public function update(Request $request, $id) { ... }
    public function destroy($id) { ... }

    Contoh Struktur File Controller

    Berikut contoh file Controller lengkap untuk Article dengan beberapa metode:

    namespace App\Http\Controllers;
    
    use App\Http\Controllers\Controller;
    use Illuminate\Http\Request;
    use App\Models\Article;
    
    class ArticleController extends Controller
    {
    public function index()
    {
    $articles = Article::all();
    return view('articles.index', compact('articles'));
    }
    
    public function create()
    {
    return view('articles.create');
    }
    
    public function store(Request $request)
    {
    $data = $request->validate(['title' => 'required|string', 'content' => 'required']);
    Article::create($data);
    return redirect()->route('articles.index');
    }
    
    public function show(Article $article)
    {
    return view('articles.show', compact('article'));
    }
    
    public function edit(Article $article)
    {
    return view('articles.edit', compact('article'));
    }
    
    public function update(Request $request, Article $article)
    {
    $data = $request->validate(['title' => 'required|string', 'content' => 'required']);
    $article->update($data);
    return redirect()->route('articles.index');
    }
    
    public function destroy(Article $article)
    {
    $article->delete();
    return redirect()->route('articles.index');
    }
    }

    Dengan struktur seperti ini, membuat controller di laravel 11 tidak lagi menjadi pekerjaan yang menakutkan. Untuk mengecek integrasi dengan view dan model, bagian berikutnya akan membantu Anda memahami pola penggunaan view dan dependency injection.

    Integrasi dengan View dan Dependency Injection

    Controller di Laravel biasanya bekerja bersama View (Blade) dan model. Anda bisa mengembalikan data ke view dengan compact, atau menggunakan resource responses untuk API. Dependency Injection memudahkan pengambilan layanan (misalnya repository) tanpa membuat instance secara manual. Contoh sederhana:

    public function index()
    {
    $items = Item::all();
    return view('items.index', compact('items'));
    }

    Menyiapkan layanan khusus dapat dilakukan melalui constructor injection, sehingga logika bisnis tidak tertumpuk di controller. Jika Anda ingin solusi end-to-end, telusuri lebih lanjut di Layanan Arrazy Inovasi untuk memahami bagaimana arsitektur layanan dapat meningkatkan kualitas proyek Anda.

    Praktik Terbaik dan Keamanan

    Beberapa praktik terbaik saat membuat controller di laravel 11 meliputi:

    – Memisahkan logika presentasi dari logika bisnis dengan menggunakan service class atau repository.

    – Menggunakan Form Request untuk validasi agar controller tetap bersih.

    – Mengunci akses dengan middleware sesuai kebutuhan (auth, role-based access, dll).

    – Menjaga ukuran controller tetap kecil dengan membagi fungsi ke dalam kelas yang jelas.

    – Menambahkan komentar yang relevan dan dokumentasi singkat di tiap metode penting. Pelajari bagaimana Laravel menangani request lifecycle melalui Dokumentasi Laravel Resmi untuk pemahaman yang lebih dalam.

    Kalau Anda butuh dukungan implementasi nyata, Layanan Arrazy Inovasi siap membantu Anda—mulai dari pengembangan website hingga solusi integrasi ERP berbasis Laravel.

    Di tengah tutorial ini, Anda juga bisa meninjau beberapa layanan utama yang relevan, seperti Jasa Pembuatan Website, Jasa Pembuatan Aplikasi Mobile, Jasa SEO, Jasa Pembuatan Chatbot, dan Jasa Website Sekolah untuk kebutuhan edukasi digital Anda.

    Selain itu, untuk memperkaya referensi teknis Anda, lihat juga dokumentasi terbaru pada Dokumentasi PHP dan Dokumentasi Laravel Resmi sebagai pedoman standar industri.

    Penutup yang Menginspirasi

    Dengan memahami cara membuat controller di laravel 11 melalui pendekatan yang terstruktur, Anda akan lebih percaya diri mengelola proyek Laravel berukuran kecil maupun besar. Kunci utamanya adalah memulai dari konsep sederhana, lalu secara bertahap menambah kompleksitas dengan pola desain yang telah teruji. Jika Anda ingin mempercepat waktu implementasi, Layanan Arrazy Inovasi siap membantu Anda menyusun arsitektur aplikasi yang kuat bersama tim ahli kami.

    Terima kasih telah mengikuti panduan ini. Semoga langkah demi langkah dalam tutorial ini membantu Anda memahami bagaimana membuat controller di laravel 11 secara praktis dan profesional. Jangan ragu untuk menjangkau tim kami untuk bantuan lebih lanjut.

     

  • Route Laravel: Panduan Routing Laravel 12 dengan Contoh Kode

    Route Laravel: Panduan Routing Laravel 12 dengan Contoh Kode

    Route di Laravel adalah aturan yang menentukan kode mana yang dijalankan ketika sebuah URL diakses. Semua route untuk halaman web ditulis di routes/web.php, misalnya Route::get('/halo', fn () => 'Halo');. Panduan ini membahas routing di Laravel 12 dari dasar: method HTTP, parameter dan validasinya, named route, route group, route ke controller, route model binding, sampai cara mengecek dan men-cache route. Semua contoh sudah kami jalankan dan uji dengan feature test di Laravel 12.70.

    Belum punya project? Ikuti dulu cara install Laravel 12. Materi lain tersusun berurutan di halaman tutorial Laravel.

    File Route di Laravel 12

    File Isi Keterangan
    routes/web.php Route halaman web Mendapat session, cookie, dan proteksi CSRF
    routes/console.php Command Artisan berbasis closure dan jadwal (scheduler) Bukan route HTTP
    routes/api.php Route API, otomatis berawalan /api Tidak ada secara bawaan. Buat dengan php artisan install:api

    Semua file route didaftarkan di bootstrap/app.php. Laravel 12 tidak lagi memakai RouteServiceProvider seperti Laravel 10 ke bawah.

    Route Dasar dan Method HTTP

    use Illuminate\Http\Request;
    use Illuminate\Support\Facades\Route;
    
    Route::get('/halo', function () {
        return 'Halo dari Laravel 12';
    });
    
    Route::post('/kontak', function (Request $request) {
        return 'Pesan dari ' . $request->input('nama');
    });

    Method yang tersedia mengikuti method HTTP: Route::get, post, put, patch, delete, dan options. Untuk beberapa method sekaligus pakai Route::match(['get', 'post'], '/url', ...), atau Route::any untuk semua method.

    Form yang mengirim ke route POST, PUT, PATCH, atau DELETE di web.php wajib menyertakan @csrf. Tanpa itu, Laravel menolak request dengan status 419 Page Expired.

    Route untuk view dan redirect

    Kalau sebuah URL hanya menampilkan view atau mengalihkan ke URL lain, tidak perlu closure:

    Route::view('/tentang-kami', 'about');
    Route::redirect('/blog', '/artikel-baru', 301);

    Tanpa argumen ketiga, Route::redirect memakai status 302. Untuk pindah alamat permanen, pakai 301 atau Route::permanentRedirect.

    Parameter Route

    Bagian URL yang berubah-ubah ditulis di dalam kurung kurawal dan diterima sebagai argumen fungsi:

    Route::get('/user/{id}', function (string $id) {
        return "User {$id}";
    })->whereNumber('id');

    whereNumber membatasi parameter hanya angka. Akses /user/abc akan menghasilkan 404, bukan error di kode kamu. Ada juga whereAlpha, whereAlphaNumeric, whereUuid, dan where dengan regex bebas:

    Route::get('/artikel/{slug}', function (string $slug) {
        return $slug;
    })->where('slug', '[a-z0-9-]+');

    Parameter opsional

    Beri tanda tanya pada parameter dan nilai default pada argumen fungsinya:

    Route::get('/cari/{kata?}', function (?string $kata = null) {
        return $kata ? "Mencari: {$kata}" : 'Masukkan kata kunci';
    });

    Tanpa nilai default = null, akses /cari akan error karena argumennya kosong.

    Named Route

    Named route memberi nama pada route, sehingga link di view tidak perlu menulis URL secara manual:

    Route::get('/profil/saya', function () {
        return view('profile');
    })->name('profil');

    Pakai nama itu di Blade atau controller:

    <a href="{{ route('profil') }}">Profil saya</a>
    
    // di controller
    return redirect()->route('profil');

    Untuk route berparameter, kirim nilainya sebagai argumen kedua: route('orders.show', ['id' => 7]) atau cukup route('orders.show', 7), hasilnya /orders/7. Keuntungannya, kalau suatu saat URL diubah, semua link ikut berubah tanpa mencari satu per satu.

    Route Group: Prefix, Name, dan Middleware

    Route yang berbagi awalan URL, awalan nama, atau middleware bisa dikelompokkan:

    Route::prefix('admin')
        ->name('admin.')
        ->middleware('auth')
        ->group(function () {
            Route::get('/dashboard', fn () => view('admin.dashboard'))->name('dashboard');
            Route::get('/laporan', fn () => view('admin.laporan'))->name('laporan');
        });

    Hasilnya route /admin/dashboard bernama admin.dashboard, dan keduanya hanya bisa diakses pengguna yang sudah login. Pengunjung yang belum login dialihkan ke route bernama login, atau mendapat 401 untuk request JSON. Pastikan route login memang ada (starter kit sudah menyediakannya). Kalau belum ada, muncul error Route [login] not defined.

    Route ke Controller

    Untuk aplikasi nyata, logika sebaiknya dipindah dari closure ke controller:

    use App\Http\Controllers\OrderController;
    
    Route::get('/orders', [OrderController::class, 'index'])->name('orders.index');

    Kalau banyak route memakai controller yang sama, gunakan Route::controller supaya tidak menulis nama class berulang kali:

    Route::controller(OrderController::class)->group(function () {
        Route::get('/orders', 'index')->name('orders.index');
        Route::get('/orders/{id}', 'show')->whereNumber('id')->name('orders.show');
    });

    Untuk CRUD lengkap, satu baris Route::resource('products', ProductController::class) langsung membuat tujuh route. Detailnya, termasuk resource controller, API controller, dan controller satu aksi, ada di panduan membuat controller di Laravel 12.

    Route Model Binding

    Kalau argumen fungsi diberi type-hint model Eloquent dan namanya sama dengan parameter route, Laravel otomatis mengambil data dari database. Kalau data tidak ada, hasilnya 404:

    use App\Models\Category;
    
    Route::get('/kategori/{category:slug}', function (Category $category) {
        return $category->name;
    });

    Bagian :slug membuat Laravel mencari berdasarkan kolom slug, bukan id. Jadi /kategori/kopi-robusta langsung mengembalikan kategori dengan slug tersebut. Tanpa :slug, pencarian memakai primary key. Pastikan nama parameter ({category}) sama dengan nama variabel ($category). Kalau berbeda, binding tidak berjalan dan kamu mendapat model kosong.

    Fallback Route

    Fallback route dijalankan ketika tidak ada route lain yang cocok. Taruh di baris paling akhir file route:

    Route::fallback(function () {
        return response('Halaman tidak ditemukan', 404);
    });

    Kalau ingin tampilan khusus, ganti dengan response()->view('nama-view', [], 404). Fallback hanya menangkap request GET yang tidak cocok dengan route mana pun, dan juga terpanggil saat batasan seperti whereNumber tidak terpenuhi.

    Mengecek dan Men-cache Route

    # semua route milik aplikasi (tanpa route bawaan paket)
    php artisan route:list --except-vendor
    
    # filter berdasarkan nama atau path
    php artisan route:list --name=admin
    php artisan route:list --path=orders
    
    # production: cache route agar registrasi lebih cepat
    php artisan route:cache
    php artisan route:clear

    Setelah route:cache, perubahan di file route tidak terbaca sampai kamu menjalankan route:clear atau route:cache lagi. Ini penyebab umum “route baru tidak muncul” di server. Perintah Artisan lain yang sering dipakai ada di artikel apa itu PHP Artisan.

    Error Routing yang Sering Muncul

    • 404 padahal route sudah ditulis. Cek urutan: route spesifik seperti /products/export harus ditulis sebelum route berparameter /products/{product}. Cek juga apakah route sedang di-cache.
    • 405 Method Not Allowed. URL-nya benar tetapi method-nya salah, misalnya form mengirim POST ke route yang hanya Route::put. Tambahkan @method('PUT') di form.
    • 419 Page Expired. Form tidak menyertakan @csrf, atau session sudah kedaluwarsa.
    • Route [nama] not defined. Nama di route('...') salah ketik, atau route-nya belum diberi ->name(). Cek dengan php artisan route:list --name=nama.
    • Target class [XController] does not exist. Baris use App\Http\Controllers\XController; belum ditulis di file route.

    Pertanyaan yang Sering Diajukan

    Apa bedanya routes/web.php dan routes/api.php?

    web.php untuk halaman yang dibuka di browser, dengan session dan proteksi CSRF. api.php untuk API yang stateless, biasanya diautentikasi dengan token, dan semua URL-nya otomatis berawalan /api. Di Laravel 12, api.php baru ada setelah menjalankan php artisan install:api.

    Apa saja jenis routing di Laravel?

    Yang paling sering dipakai: route dasar dengan closure, route ke controller, resource route untuk CRUD, route dengan parameter, named route, route group, dan fallback route. Semuanya dibahas di atas.

    Lebih baik pakai closure atau controller?

    Closure praktis untuk percobaan atau halaman sangat sederhana. Untuk aplikasi yang akan dirawat, controller lebih rapi karena logika terkumpul per fitur dan file route tetap pendek.

    Kenapa route baru tidak muncul di server production?

    Kemungkinan besar route sedang di-cache. Jalankan php artisan route:clear, atau php artisan optimize:clear untuk menghapus semua cache sekaligus.

    Setelah route, langkah berikutnya biasanya menyiapkan tabel database lewat migration Laravel, lalu menghubungkannya ke controller.

    Sedang membangun aplikasi bisnis dengan Laravel dan butuh tim yang mengerjakannya sampai production? Tim Arrazy Inovasi di Purwokerto mengerjakan pembuatan sistem aplikasi custom, termasuk dengan stack Laravel.

    Diskusikan project Laravel kamu →

  • Invokable Controller di Laravel: Cara Kerja, Contoh Kode, dan Kapan Memakainya

    Invokable Controller di Laravel: Cara Kerja, Contoh Kode, dan Kapan Memakainya

    Ada satu fitur Laravel yang sering dilewatkan developer pemula: invokable controller. Padahal kalau dipakai di tempat yang tepat, kode jadi lebih bersih dan lebih mudah dibaca.

    Artikel ini menjelaskan apa itu invokable controller, kapan sebaiknya dipakai, dan bagaimana cara implementasinya di Laravel 12.

    Apa Itu Invokable Controller?

    Invokable controller adalah controller yang hanya punya satu method: __invoke(). Karena hanya ada satu aksi, Anda tidak perlu menyebutkan nama method saat mendaftarkan route.

    Ini berbeda dengan controller biasa yang bisa punya banyak method seperti index(), store(), update(), dll.

    Buat invokable controller lewat Artisan:

    php artisan make:controller ShowDashboardController --invokable

    Hasilnya:

    <?php
    
    namespace AppHttpControllers;
    
    use IlluminateHttpRequest;
    
    class ShowDashboardController extends Controller
    {
        public function __invoke(Request $request)
        {
            //
        }
    }

    Cara Mendaftarkan ke Route

    Karena hanya ada satu aksi, cara daftarkan ke route cukup langsung pakai nama class-nya:

    use AppHttpControllersShowDashboardController;
    
    Route::get('/dashboard', ShowDashboardController::class);

    Bandingkan dengan controller biasa yang harus sebut method-nya:

    Route::get('/dashboard', [DashboardController::class, 'index']);

    Lebih ringkas, dan route-nya lebih mudah dibaca, terutama kalau ada banyak route.

    Contoh Kasus Nyata

    Misalnya Anda punya halaman yang menampilkan laporan bulanan. Logikanya spesifik untuk satu tujuan, tidak perlu dibagi ke beberapa method:

    <?php
    
    namespace AppHttpControllersReports;
    
    use IlluminateHttpRequest;
    use AppHttpControllersController;
    use AppModelsOrder;
    
    class MonthlyReportController extends Controller
    {
        public function __invoke(Request $request)
        {
            $month = $request->query('month', now()->month);
            $year  = $request->query('year', now()->year);
    
            $orders = Order::whereMonth('created_at', $month)
                           ->whereYear('created_at', $year)
                           ->with('customer')
                           ->get();
    
            return view('reports.monthly', compact('orders', 'month', 'year'));
        }
    }

    Route-nya:

    Route::get('/reports/monthly', MonthlyReportController::class)
         ->middleware('auth')
         ->name('reports.monthly');

    Rapi. Satu controller, satu tujuan.

    Kapan Pakai Invokable Controller?

    Invokable controller cocok untuk:

    • Halaman yang hanya punya satu aksi (tampil laporan, proses webhook, generate PDF)
    • Action handler — satu action spesifik yang tidak perlu dibundel dengan aksi lain
    • Single-purpose endpoint yang ingin dipisah dari controller utama agar tidak membesar

    Tidak cocok untuk resource yang punya banyak aksi CRUD. Untuk itu tetap pakai resource controller biasa.

    Dependency Injection di Invokable Controller

    Sama seperti controller biasa, Anda bisa inject service lewat constructor atau langsung di method __invoke():

    <?php
    
    namespace AppHttpControllers;
    
    use AppServicesInvoiceService;
    use IlluminateHttpRequest;
    
    class GenerateInvoiceController extends Controller
    {
        public function __construct(
            private InvoiceService $invoiceService
        ) {}
    
        public function __invoke(Request $request)
        {
            $validated = $request->validate([
                'order_id' => 'required|exists:orders,id',
            ]);
    
            $pdf = $this->invoiceService->generate($validated['order_id']);
    
            return response()->download($pdf);
        }
    }

    Baca Juga

    Kalau Anda butuh tim untuk membangun aplikasi web berbasis Laravel, dari arsitektur sampai deployment — lihat layanan pengembangan aplikasi kami.