Setelah pengguna mengirim form, menampilkan halaman sukses langsung dari handler POST menimbulkan masalah klasik: menekan tombol refresh akan mengirim ulang data yang sama. Solusi standar web adalah pola redirect: handler POST menjawab 302 berisi lokasi baru, lalu browser mengambil halaman tersebut lewat GET yang aman diulang. Chapter ini membahas seluruh bentuk redirect dari dalam controller — ke URI, ke named route, kembali ke halaman sebelumnya — beserta flash message yang menumpang gelombangnya, dan semuanya kita kunci dengan feature test.

Redirect ke URI dan Named Route #

Bentuk paling dasar menerima URI tujuan secara langsung. Tambahkan dua route pengalihan ke routes/web.php:

php
<?php

use Illuminate\Support\Facades\Route;

Route::get('/', fn () => 'Beranda Course Laravel')->name('beranda');

Route::get('/pindah', fn () => redirect('/kontak'));
Route::get('/pindah-permanen', fn () => redirect('/kontak', 301));

Argumen kedua redirect() mengubah status code: default 302 berarti sementara, sedangkan 301 memberi tahu browser dan mesin pencari bahwa pindahan tersebut permanen. Untuk controller, bentuk yang lebih tahan perubahan adalah redirect()->route() karena bergantung pada nama route, bukan URI yang bisa berubah kapan pun:

php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\View\View;

class KontakController extends Controller
{
    public function form(): View
    {
        return view('kontak');
    }

    public function kirim(Request $request): RedirectResponse
    {
        $validated = $request->validate([
            'nama' => 'required|string',
            'email' => 'required|email',
        ]);

        return redirect()
            ->route('kontak.sukses')
            ->with('status', "Terima kasih, {$validated['nama']}! Pesan Anda kami terima.");
    }

    public function sukses(Request $request): string
    {
        $status = $request->session()->get('status', 'tidak ada flash message');

        return "Halaman sukses — {$status}";
    }

    public function suka(Request $request): RedirectResponse
    {
        return back()->with('status', 'Terima kasih sudah menyukai post ini.');
    }
}

Controller tersebut didaftarkan dengan tiga route plus satu endpoint back() untuk uji coba:

php
<?php

use App\Http\Controllers\KontakController;
use Illuminate\Support\Facades\Route;

Route::get('/kontak', [KontakController::class, 'form'])->name('kontak');
Route::post('/kontak', [KontakController::class, 'kirim'])->name('kontak.kirim');
Route::get('/kontak/sukses', [KontakController::class, 'sukses'])->name('kontak.sukses');
Route::post('/suka', [KontakController::class, 'suka'])->name('suka');

Flash Message dan back() #

Rantai ->with('status', ...) menyimpan flash message ke session: data yang bertahan tepat untuk satu request berikutnya, lalu menghapus dirinya sendiri. Pola ini pas untuk redirect, karena halaman tujuan (di sini sukses()) dapat memanggil session()->get('status') untuk menampilkan sekali pesan tanpa halaman tersebut menyimpan status apa pun. Method back() mengembalikan pengguna ke halaman asal — Laravel membaca URL sebelumnya dari session, sama seperti yang dilakukan tombol back browser. Kombinasi back() dan flash message adalah cara standar memberi umpan balik atas aksi yang tidak menghasilkan halaman sendiri, misalnya tombol “suka” pada sebuah post.

withInput dan Input Lama #

Saat validasi gagal, $request->validate() otomatis me-redirect back() sambil membawa dua muatan: error validasi di session('errors') dan input lama di session('_old_input'). Form kita tinggal memakai helper old() agar pengguna tidak mengetik ulang:

blade
<h1>Form Kontak</h1>
<form method="POST" action="{{ route('kontak.kirim') }}">
    @csrf
    <input name="nama" value="{{ old('nama') }}">
    <input name="email" value="{{ old('email') }}">
    <button>Kirim</button>
</form>

Perilaku yang sama bisa dipicu manual dengan ->withInput() untuk kasus gagal non-validasi, misalnya saat saldo tidak cukup — pengguna tetap kembali ke form dengan isian utuh, hanya tanpa daftar error validasi. Detail seluruh aturan validasi menunggu Section Validasi dan Form.

action dan away #

Dua bentuk penunjuk tujuan lain melengkapi perangkat kita. redirect()->action() menunjuk method controller secara langsung — berguna saat halaman tujuan belum punya nama route:

php
Route::get('/ke-posts', fn () => redirect()->action([PostController::class, 'index']));

Sedangkan redirect()->away() mengirim pengguna ke luar aplikasi — tampilan berikut tidak memerlukan server eksternal untuk dipahami: return redirect()->away('https://laravel.com/docs/13.x/responses'); menghasilkan status 302 dengan Location menuju URL tersebut.

Menguji Setiap Redirect #

Feature test RedirectTest memeriksa seluruh bentuk redirect di atas sekaligus:

php
<?php

namespace Tests\Feature;

use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;

class RedirectTest extends TestCase
{
    use RefreshDatabase;

    public function test_form_valid_mengarah_ke_halaman_sukses(): void
    {
        $response = $this->from('/kontak')->post('/kontak', [
            'nama' => 'Budi',
            'email' => '[email protected]',
        ]);

        $response->assertRedirect('/kontak/sukses');
        $response->assertSessionHas('status', 'Terima kasih, Budi! Pesan Anda kami terima.');

        $this->get('/kontak/sukses')->assertSee('Terima kasih, Budi!');
    }

    public function test_form_tidak_valid_kembali_dengan_error_dan_input_lama(): void
    {
        $response = $this->from('/kontak')->post('/kontak', [
            'nama' => 'Siti',
            'email' => 'bukan-email',
        ]);

        $response->assertRedirect('/kontak');
        $response->assertSessionHasErrors('email');
        $this->assertEquals('Siti', session('_old_input.nama'));
    }

    public function test_back_kembali_ke_halaman_sebelumnya(): void
    {
        $response = $this->from('/kontak')->post('/suka');

        $response->assertRedirect('/kontak');
        $response->assertSessionHas('status', 'Terima kasih sudah menyukai post ini.');
    }

    public function test_redirect_uri_dan_permanen(): void
    {
        $this->get('/pindah')->assertRedirect('/kontak');
        $this->get('/pindah-permanen')->assertStatus(301)->assertRedirect('/kontak');
    }

    public function test_redirect_action_menuju_method_controller(): void
    {
        $this->get('/ke-posts')->assertRedirect('/posts');
    }
}

Output yang Dihasilkan:

text
  PASS  Tests\Feature\RedirectTest
  ✓  form valid mengarah ke halaman sukses                               0.15s  
  ✓  form tidak valid kembali dengan error dan input lama                0.01s  
  ✓  back kembali ke halaman sebelumnya                                  0.01s  
  ✓  redirect uri dan permanen                                           0.01s  
  ✓  redirect action menuju method controller                           0.01s  

  Tests:    5 passed (19 assertions)
  Duration: 0.25s

Helper $this->from('/kontak') mengatur halaman asal sehingga back() punya tujuan yang pasti dalam test, assertRedirect() memeriksa status 302 sekaligus header Location, dan assertSessionHas() memastikan flash message sampai tujuan. Sembilan belas assertion membuktikan seluruh varian redirect bekerja persis seperti yang kita rancang — daftar lengkapnya terdokumentasi di dokumentasi responses .

Redirect mengirim halaman HTML untuk browser. Untuk panggilan AJAX dan aplikasi mobile, bentuk jawaban yang jauh lebih efisien adalah JSON — topik chapter Response JSON berikutnya.