Tutorial Laravel #14: CRUD Sederhana dengan Laravel

1. Overview CRUD Laravel

Di tutorial ini kita akan membangun CRUD artikel sederhana menggunakan Eloquent, Controller, Route Resource, dan Blade. Ini adalah inti dari hampir semua fitur di aplikasi web Laravel.

2. Setup Awal

# Buat model + migration + resource controller sekaligus
php artisan make:model Artikel -m
php artisan make:controller ArtikelController --resource --model=Artikel

3. Migration dan Model

// Migration
Schema::create('artikels', function (Blueprint $table) {
    $table->id();
    $table->string('judul');
    $table->text('konten');
    $table->enum('status', ['draft', 'published'])->default('draft');
    $table->timestamps();
});
// Model
class Artikel extends Model
{
    protected $fillable = ['judul', 'konten', 'status'];
}
php artisan migrate

4. Route Resource

// routes/web.php
Route::resource('artikel', ArtikelController::class);

Satu baris ini membuat 7 route sekaligus:

  • GET /artikel - index()
  • GET /artikel/create - create()
  • POST /artikel - store()
  • GET /artikel/{id} - show()
  • GET /artikel/{id}/edit - edit()
  • PUT/PATCH /artikel/{id} - update()
  • DELETE /artikel/{id} - destroy()

5. Resource Controller Lengkap

<?php
namespace AppHttpControllers;

use AppModelsArtikel;
use IlluminateHttpRequest;

class ArtikelController extends Controller
{
    public function index()
    {
        $artikels = Artikel::latest()->paginate(10);
        return view('artikel.index', compact('artikels'));
    }

    public function create()
    {
        return view('artikel.create');
    }

    public function store(Request $request)
    {
        $validated = $request->validate([
            'judul'  => 'required|string|max:200',
            'konten' => 'required|string',
            'status' => 'required|in:draft,published',
        ]);

        Artikel::create($validated);

        return redirect()->route('artikel.index')
                         ->with('success', 'Artikel berhasil ditambahkan!');
    }

    public function show(Artikel $artikel)
    {
        return view('artikel.show', compact('artikel'));
    }

    public function edit(Artikel $artikel)
    {
        return view('artikel.edit', compact('artikel'));
    }

    public function update(Request $request, Artikel $artikel)
    {
        $validated = $request->validate([
            'judul'  => 'required|string|max:200',
            'konten' => 'required|string',
            'status' => 'required|in:draft,published',
        ]);

        $artikel->update($validated);

        return redirect()->route('artikel.index')
                         ->with('success', 'Artikel berhasil diperbarui!');
    }

    public function destroy(Artikel $artikel)
    {
        $artikel->delete();
        return redirect()->route('artikel.index')
                         ->with('success', 'Artikel berhasil dihapus!');
    }
}

6. View Blade - Index

@extends('layouts.app')
@section('content')
<div class="d-flex justify-content-between mb-3">
    <h1>Daftar Artikel</h1>
    <a href="{{ route('artikel.create') }}" class="btn btn-primary">+ Tambah</a>
</div>

@if(session('success'))
    <div class="alert alert-success">{{ session('success') }}</div>
@endif

<table class="table table-bordered">
    <thead><tr><th>#</th><th>Judul</th><th>Status</th><th>Aksi</th></tr></thead>
    <tbody>
    @forelse($artikels as $artikel)
        <tr>
            <td>{{ $loop->iteration }}</td>
            <td>{{ $artikel->judul }}</td>
            <td>{{ $artikel->status }}</td>
            <td>
                <a href="{{ route('artikel.edit', $artikel) }}" class="btn btn-sm btn-warning">Edit</a>
                <form action="{{ route('artikel.destroy', $artikel) }}" method="POST" class="d-inline">
                    @csrf @method('DELETE')
                    <button onclick="return confirm('Hapus?')" class="btn btn-sm btn-danger">Hapus</button>
                </form>
            </td>
        </tr>
    @empty
        <tr><td colspan="4" class="text-center">Belum ada artikel.</td></tr>
    @endforelse
    </tbody>
</table>

{{ $artikels->links() }}
@endsection

7. Form Create dan Edit

@extends('layouts.app')
@section('content')
<h1>{{ isset($artikel) ? 'Edit' : 'Tambah' }} Artikel</h1>

<form method="POST" action="{{ isset($artikel) ? route('artikel.update', $artikel) : route('artikel.store') }}">
    @csrf
    @isset($artikel) @method('PUT') @endisset

    <div class="mb-3">
        <label>Judul</label>
        <input type="text" name="judul" class="form-control @error('judul') is-invalid @enderror"
               value="{{ old('judul', $artikel->judul ?? '') }}">
        @error('judul') <div class="invalid-feedback">{{ $message }}</div> @enderror
    </div>

    <div class="mb-3">
        <label>Konten</label>
        <textarea name="konten" rows="8" class="form-control @error('konten') is-invalid @enderror">{{ old('konten', $artikel->konten ?? '') }}</textarea>
        @error('konten') <div class="invalid-feedback">{{ $message }}</div> @enderror
    </div>

    <div class="mb-3">
        <label>Status</label>
        <select name="status" class="form-select">
            <option value="draft" {{ old('status', $artikel->status ?? '') == 'draft' ? 'selected' : '' }}>Draft</option>
            <option value="published" {{ old('status', $artikel->status ?? '') == 'published' ? 'selected' : '' }}>Published</option>
        </select>
    </div>

    <button type="submit" class="btn btn-primary">Simpan</button>
    <a href="{{ route('artikel.index') }}" class="btn btn-secondary">Batal</a>
</form>
@endsection

8. Ringkasan

  • Route::resource membuat 7 route CRUD sekaligus dalam satu baris
  • Route model binding memungkinkan Laravel otomatis inject model dari ID di URL
  • @csrf wajib di setiap form, @method('PUT') untuk simulate PUT/DELETE
  • Gunakan old() untuk mengisi ulang form setelah validasi gagal

Tutorial berikutnya membahas form validation di Laravel secara lebih mendalam.


ariq fadhil

Im Ariq Tech, a Top Rated Fullstack Developer with 5+ years of experience, delivering high-quality solutions across 50+ projects.