Daftar Isi
- Pendahuluan
- Requirement & Persiapan
- Instalasi Laravel 10
- Konfigurasi Database
- Setup Authentication
- Membuat Fitur CRUD
- Testing Aplikasi
- Kesimpulan
Pendahuluan
Dalam tutorial ini, kita akan membuat aplikasi web sederhana menggunakan Laravel 10 yang dilengkapi dengan:
- Basic Authentication: Register, Login, Logout
- CRUD Operations: Create, Read, Update, Delete untuk manajemen produk
- User Protection: Hanya user yang login yang bisa mengakses fitur CRUD
Aplikasi ini adalah MVP (Minimum Viable Product) yang cocok untuk dipelajari pemula dan bisa dikembangkan lebih lanjut.
Requirement & Persiapan
Sistem Requirements
- PHP >= 8.1
- Composer
- MySQL/MariaDB atau database lainnya
- Node.js & NPM (untuk frontend assets)
Cek Versi PHP
php -v
Cek Composer
composer --version
Instalasi Laravel 10
1. Buat Project Baru
composer create-project laravel/laravel:^10.0 laravel-crud-app
cd laravel-crud-app
2. Jalankan Development Server
php artisan serve
Buka browser dan akses http://localhost:8000 untuk memastikan Laravel terinstall dengan benar.
Konfigurasi Database
1. Buat Database
Buat database baru di MySQL:
CREATE DATABASE laravel_crud_db;
2. Setup File .env
Edit file .env di root project:
APP_NAME="Laravel CRUD App"
APP_ENV=local
APP_KEY=base64:xxxxx
APP_DEBUG=true
APP_URL=http://localhost:8000
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=laravel_crud_db
DB_USERNAME=root
DB_PASSWORD=
Catatan: Sesuaikan DB_USERNAME dan DB_PASSWORD dengan konfigurasi MySQL Anda.
3. Test Koneksi Database
php artisan migrate
Jika berhasil, tabel-tabel default Laravel akan dibuat.
Setup Authentication
Laravel 10 menyediakan beberapa cara untuk setup authentication. Kita akan menggunakan Laravel Breeze karena paling sederhana dan cocok untuk MVP.
1. Install Laravel Breeze
composer require laravel/breeze --dev
2. Install Breeze Scaffolding
php artisan breeze:install blade
Pilih opsi:
- Blade (untuk templating tradisional)
- Dark mode: Optional (No)
- PHPUnit: Yes
3. Install Dependencies & Build Assets
npm install
npm run dev
Buka terminal baru dan jalankan:
php artisan serve
4. Jalankan Migrasi
php artisan migrate
5. Test Authentication
Akses http://localhost:8000/register untuk register user baru, kemudian login di http://localhost:8000/login.
Membuat Fitur CRUD
Kita akan membuat fitur CRUD untuk mengelola Products (Produk).
1. Buat Model, Migration, dan Controller
php artisan make:model Product -mcr
Penjelasan flags:
-
-m: Membuat migration -
-c: Membuat controller -
-r: Membuat resource controller (dengan method CRUD lengkap)
2. Setup Migration untuk Products
Edit file database/migrations/xxxx_create_products_table.php:
<?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('products', function (Blueprint $table) {
$table->id();
$table->foreignId('user_id')->constrained()->onDelete('cascade');
$table->string('name');
$table->text('description')->nullable();
$table->decimal('price', 10, 2);
$table->integer('stock')->default(0);
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('products');
}
};
Jalankan migration:
php artisan migrate
3. Setup Model Product
Edit file app/Models/Product.php:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Product extends Model
{
use HasFactory;
protected $fillable = [
'user_id',
'name',
'description',
'price',
'stock',
];
// Relasi dengan User
public function user()
{
return $this->belongsTo(User::class);
}
}
4. Update User Model
Edit app/Models/User.php dan tambahkan relasi:
// Tambahkan di dalam class User
public function products()
{
return $this->hasMany(Product::class);
}
5. Setup ProductController
Edit file app/Http/Controllers/ProductController.php:
<?php
namespace App\Http\Controllers;
use App\Models\Product;
use Illuminate\Http\Request;
class ProductController extends Controller
{
// Tampilkan semua produk
public function index()
{
$products = Product::where('user_id', auth()->id())
->latest()
->paginate(10);
return view('products.index', compact('products'));
}
// Form tambah produk
public function create()
{
return view('products.create');
}
// Simpan produk baru
public function store(Request $request)
{
$validated = $request->validate([
'name' => 'required|max:255',
'description' => 'nullable',
'price' => 'required|numeric|min:0',
'stock' => 'required|integer|min:0',
]);
auth()->user()->products()->create($validated);
return redirect()->route('products.index')
->with('success', 'Produk berhasil ditambahkan!');
}
// Tampilkan detail produk
public function show(Product $product)
{
// Pastikan user hanya bisa lihat produknya sendiri
if ($product->user_id !== auth()->id()) {
abort(403);
}
return view('products.show', compact('product'));
}
// Form edit produk
public function edit(Product $product)
{
// Pastikan user hanya bisa edit produknya sendiri
if ($product->user_id !== auth()->id()) {
abort(403);
}
return view('products.edit', compact('product'));
}
// Update produk
public function update(Request $request, Product $product)
{
// Pastikan user hanya bisa update produknya sendiri
if ($product->user_id !== auth()->id()) {
abort(403);
}
$validated = $request->validate([
'name' => 'required|max:255',
'description' => 'nullable',
'price' => 'required|numeric|min:0',
'stock' => 'required|integer|min:0',
]);
$product->update($validated);
return redirect()->route('products.index')
->with('success', 'Produk berhasil diupdate!');
}
// Hapus produk
public function destroy(Product $product)
{
// Pastikan user hanya bisa hapus produknya sendiri
if ($product->user_id !== auth()->id()) {
abort(403);
}
$product->delete();
return redirect()->route('products.index')
->with('success', 'Produk berhasil dihapus!');
}
}
6. Setup Routes
Edit file routes/web.php:
<?php
use App\Http\Controllers\ProfileController;
use App\Http\Controllers\ProductController;
use Illuminate\Support\Facades\Route;
Route::get('/', function () {
return view('welcome');
});
Route::get('/dashboard', function () {
return view('dashboard');
})->middleware(['auth', 'verified'])->name('dashboard');
Route::middleware('auth')->group(function () {
Route::get('/profile', [ProfileController::class, 'edit'])->name('profile.edit');
Route::patch('/profile', [ProfileController::class, 'update'])->name('profile.update');
Route::delete('/profile', [ProfileController::class, 'destroy'])->name('profile.destroy');
// Routes untuk Products CRUD
Route::resource('products', ProductController::class);
});
require __DIR__.'/auth.php';
7. Buat Views untuk CRUD
a. Layout Navigation (Update)
Edit file resources/views/layouts/navigation.blade.php dan tambahkan menu Products:
<!-- Tambahkan setelah menu Dashboard -->
<div class="hidden space-x-8 sm:-my-px sm:ml-10 sm:flex">
<x-nav-link :href="route('products.index')" :active="request()->routeIs('products.*')">
{{ __('Products') }}
</x-nav-link>
</div>
b. Index View (List Products)
Buat file resources/views/products/index.blade.php:
<x-app-layout>
<x-slot name="header">
<div class="flex justify-between">
<h2 class="font-semibold text-xl text-gray-800 leading-tight">
{{ __('Daftar Produk') }}
</h2>
<a href="{{ route('products.create') }}" class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded">
Tambah Produk
</a>
</div>
</x-slot>
<div class="py-12">
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
@if (session('success'))
<div class="bg-green-100 border border-green-400 text-green-700 px-4 py-3 rounded mb-4">
{{ session('success') }}
</div>
@endif
<div class="bg-white overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6 text-gray-900">
@if($products->count() > 0)
<table class="min-w-full divide-y divide-gray-200">
<thead class="bg-gray-50">
<tr>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Nama</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Harga</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Stok</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Aksi</th>
</tr>
</thead>
<tbody class="bg-white divide-y divide-gray-200">
@foreach($products as $product)
<tr>
<td class="px-6 py-4 whitespace-nowrap">{{ $product->name }}</td>
<td class="px-6 py-4 whitespace-nowrap">Rp {{ number_format($product->price, 0, ',', '.') }}</td>
<td class="px-6 py-4 whitespace-nowrap">{{ $product->stock }}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium">
<a href="{{ route('products.show', $product) }}" class="text-blue-600 hover:text-blue-900 mr-3">Lihat</a>
<a href="{{ route('products.edit', $product) }}" class="text-indigo-600 hover:text-indigo-900 mr-3">Edit</a>
<form action="{{ route('products.destroy', $product) }}" method="POST" class="inline">
@csrf
@method('DELETE')
<button type="submit" class="text-red-600 hover:text-red-900" onclick="return confirm('Yakin ingin menghapus?')">Hapus</button>
</form>
</td>
</tr>
@endforeach
</tbody>
</table>
<div class="mt-4">
{{ $products->links() }}
</div>
@else
<p class="text-gray-500">Belum ada produk. <a href="{{ route('products.create') }}" class="text-blue-600 hover:text-blue-800">Tambah produk pertama</a></p>
@endif
</div>
</div>
</div>
</div>
</x-app-layout>
c. Create View (Form Tambah)
Buat file resources/views/products/create.blade.php:
<x-app-layout>
<x-slot name="header">
<h2 class="font-semibold text-xl text-gray-800 leading-tight">
{{ __('Tambah Produk Baru') }}
</h2>
</x-slot>
<div class="py-12">
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
<div class="bg-white overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6 text-gray-900">
<form method="POST" action="{{ route('products.store') }}">
@csrf
<!-- Name -->
<div class="mb-4">
<label for="name" class="block text-gray-700 text-sm font-bold mb-2">Nama Produk</label>
<input type="text" name="name" id="name" value="{{ old('name') }}"
class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline @error('name') border-red-500 @enderror">
@error('name')
<p class="text-red-500 text-xs italic mt-1">{{ $message }}</p>
@enderror
</div>
<!-- Description -->
<div class="mb-4">
<label for="description" class="block text-gray-700 text-sm font-bold mb-2">Deskripsi</label>
<textarea name="description" id="description" rows="4"
class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline @error('description') border-red-500 @enderror">{{ old('description') }}</textarea>
@error('description')
<p class="text-red-500 text-xs italic mt-1">{{ $message }}</p>
@enderror
</div>
<!-- Price -->
<div class="mb-4">
<label for="price" class="block text-gray-700 text-sm font-bold mb-2">Harga</label>
<input type="number" step="0.01" name="price" id="price" value="{{ old('price') }}"
class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline @error('price') border-red-500 @enderror">
@error('price')
<p class="text-red-500 text-xs italic mt-1">{{ $message }}</p>
@enderror
</div>
<!-- Stock -->
<div class="mb-6">
<label for="stock" class="block text-gray-700 text-sm font-bold mb-2">Stok</label>
<input type="number" name="stock" id="stock" value="{{ old('stock') }}"
class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline @error('stock') border-red-500 @enderror">
@error('stock')
<p class="text-red-500 text-xs italic mt-1">{{ $message }}</p>
@enderror
</div>
<div class="flex items-center justify-between">
<button type="submit" class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline">
Simpan Produk
</button>
<a href="{{ route('products.index') }}" class="text-gray-600 hover:text-gray-800">
Batal
</a>
</div>
</form>
</div>
</div>
</div>
</div>
</x-app-layout>
d. Edit View (Form Edit)
Buat file resources/views/products/edit.blade.php:
<x-app-layout>
<x-slot name="header">
<h2 class="font-semibold text-xl text-gray-800 leading-tight">
{{ __('Edit Produk') }}
</h2>
</x-slot>
<div class="py-12">
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
<div class="bg-white overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6 text-gray-900">
<form method="POST" action="{{ route('products.update', $product) }}">
@csrf
@method('PUT')
<!-- Name -->
<div class="mb-4">
<label for="name" class="block text-gray-700 text-sm font-bold mb-2">Nama Produk</label>
<input type="text" name="name" id="name" value="{{ old('name', $product->name) }}"
class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline @error('name') border-red-500 @enderror">
@error('name')
<p class="text-red-500 text-xs italic mt-1">{{ $message }}</p>
@enderror
</div>
<!-- Description -->
<div class="mb-4">
<label for="description" class="block text-gray-700 text-sm font-bold mb-2">Deskripsi</label>
<textarea name="description" id="description" rows="4"
class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline @error('description') border-red-500 @enderror">{{ old('description', $product->description) }}</textarea>
@error('description')
<p class="text-red-500 text-xs italic mt-1">{{ $message }}</p>
@enderror
</div>
<!-- Price -->
<div class="mb-4">
<label for="price" class="block text-gray-700 text-sm font-bold mb-2">Harga</label>
<input type="number" step="0.01" name="price" id="price" value="{{ old('price', $product->price) }}"
class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline @error('price') border-red-500 @enderror">
@error('price')
<p class="text-red-500 text-xs italic mt-1">{{ $message }}</p>
@enderror
</div>
<!-- Stock -->
<div class="mb-6">
<label for="stock" class="block text-gray-700 text-sm font-bold mb-2">Stok</label>
<input type="number" name="stock" id="stock" value="{{ old('stock', $product->stock) }}"
class="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline @error('stock') border-red-500 @enderror">
@error('stock')
<p class="text-red-500 text-xs italic mt-1">{{ $message }}</p>
@enderror
</div>
<div class="flex items-center justify-between">
<button type="submit" class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline">
Update Produk
</button>
<a href="{{ route('products.index') }}" class="text-gray-600 hover:text-gray-800">
Batal
</a>
</div>
</form>
</div>
</div>
</div>
</div>
</x-app-layout>
e. Show View (Detail Produk)
Buat file resources/views/products/show.blade.php:
<x-app-layout>
<x-slot name="header">
<h2 class="font-semibold text-xl text-gray-800 leading-tight">
{{ __('Detail Produk') }}
</h2>
</x-slot>
<div class="py-12">
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
<div class="bg-white overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6 text-gray-900">
<div class="mb-6">
<h3 class="text-2xl font-bold mb-4">{{ $product->name }}</h3>
<div class="grid grid-cols-2 gap-4 mb-4">
<div>
<p class="text-gray-600 font-semibold">Harga:</p>
<p class="text-xl">Rp {{ number_format($product->price, 0, ',', '.') }}</p>
</div>
<div>
<p class="text-gray-600 font-semibold">Stok:</p>
<p class="text-xl">{{ $product->stock }} unit</p>
</div>
</div>
@if($product->description)
<div class="mb-4">
<p class="text-gray-600 font-semibold">Deskripsi:</p>
<p class="mt-2">{{ $product->description }}</p>
</div>
@endif
<div class="text-sm text-gray-500 mb-4">
<p>Dibuat: {{ $product->created_at->format('d M Y H:i') }}</p>
<p>Terakhir diupdate: {{ $product->updated_at->format('d M Y H:i') }}</p>
</div>
</div>
<div class="flex space-x-4">
<a href="{{ route('products.edit', $product) }}" class="bg-indigo-500 hover:bg-indigo-700 text-white font-bold py-2 px-4 rounded">
Edit
</a>
<a href="{{ route('products.index') }}" class="bg-gray-500 hover:bg-gray-700 text-white font-bold py-2 px-4 rounded">
Kembali
</a>
<form action="{{ route('products.destroy', $product) }}" method="POST" class="inline">
@csrf
@method('DELETE')
<button type="submit" class="bg-red-500 hover:bg-red-700 text-white font-bold py-2 px-4 rounded" onclick="return confirm('Yakin ingin menghapus produk ini?')">
Hapus
</button>
</form>
</div>
</div>
</div>
</div>
</div>
</x-app-layout>
Testing Aplikasi
1. Jalankan Server
Pastikan server development berjalan:
php artisan serve
Dan di terminal lain (untuk compile assets):
npm run dev
2. Flow Testing
A. Test Authentication
- Buka
http://localhost:8000/register - Daftar dengan email dan password
- Login ke aplikasi
- Pastikan redirect ke dashboard
B. Test CRUD Operations
CREATE (Tambah Produk)
- Klik menu "Products"
- Klik tombol "Tambah Produk"
- Isi form:
- Nama: "Laptop ASUS"
- Deskripsi: "Laptop gaming dengan spesifikasi tinggi"
- Harga: 15000000
- Stok: 10
- Klik "Simpan Produk"
- Pastikan muncul pesan sukses dan produk tampil di list
READ (Lihat Produk)
- Di halaman Products, klik "Lihat" pada salah satu produk
- Pastikan detail produk tampil dengan benar
UPDATE (Edit Produk)
- Di halaman Products, klik "Edit" pada produk
- Ubah data (misalnya ubah harga atau stok)
- Klik "Update Produk"
- Pastikan perubahan tersimpan
DELETE (Hapus Produk)
- Di halaman Products, klik "Hapus" pada produk
- Konfirmasi penghapusan
- Pastikan produk terhapus dari list
C. Test Security
- Logout dari aplikasi
- Coba akses
http://localhost:8000/productslangsung - Pastikan di-redirect ke halaman login (protection berfungsi)
3. Test dengan Data Banyak
Untuk testing dengan data banyak, buat seeder:
php artisan make:seeder ProductSeeder
Edit database/seeders/ProductSeeder.php:
<?php
namespace Database\Seeders;
use App\Models\Product;
use App\Models\User;
use Illuminate\Database\Seeder;
class ProductSeeder extends Seeder
{
public function run(): void
{
$user = User::first(); // Ambil user pertama
if ($user) {
$products = [
['name' => 'Laptop', 'price' => 15000000, 'stock' => 5],
['name' => 'Mouse Gaming', 'price' => 250000, 'stock' => 20],
['name' => 'Keyboard Mechanical', 'price' => 800000, 'stock' => 15],
['name' => 'Monitor 24"', 'price' => 2500000, 'stock' => 8],
['name' => 'Headset', 'price' => 350000, 'stock' => 30],
];
foreach ($products as $product) {
Product::create([
'user_id' => $user->id,
'name' => $product['name'],
'description' => 'Deskripsi untuk ' . $product['name'],
'price' => $product['price'],
'stock' => $product['stock'],
]);
}
}
}
}
Jalankan seeder:
php artisan db:seed --class=ProductSeeder
Kesimpulan
Selamat! Anda telah berhasil membuat aplikasi Laravel 10 dengan fitur:
โ Fitur yang Telah Dibuat
-
Authentication System
- Register user baru
- Login & Logout
- Protected routes
-
CRUD Operations
- Create: Menambah produk baru
- Read: Melihat list dan detail produk
- Update: Mengubah data produk
- Delete: Menghapus produk
-
Security Features
- Middleware authentication
- User-specific data (user hanya bisa kelola produknya sendiri)
- Form validation
- CSRF protection
๐ Pengembangan Lebih Lanjut
Aplikasi ini bisa dikembangkan dengan:
- Upload Gambar Produk
php artisan storage:link
Tambahkan field image di migration dan form
Search & Filter
Tambahkan form pencarian di halaman indexKategori Produk
Buat model Category dan relasi dengan ProductExport Data
Implementasi export ke Excel/PDF menggunakan package seperti Laravel ExcelAPI
Buat API endpoints untuk mobile appRoles & Permissions
Implementasi role (Admin, User) menggunakan package Spatie PermissionSoft Deletes
Tambahkan fitur soft delete untuk recovery dataImage Upload
Implementasi upload gambar produk
๐ Sumber Belajar Lanjutan
- Laravel Documentation
- Laracasts - Video tutorial Laravel
- Laravel Daily - YouTube channel
๐ก Tips Development
- Gunakan Git untuk version control
- Tulis tests untuk fitur-fitur penting
- Gunakan .env untuk konfigurasi berbeda di development/production
- Buat backup database secara rutin
- Follow Laravel best practices
Troubleshooting
Error: Class not found
composer dump-autoload
Error: Permission denied
chmod -R 775 storage bootstrap/cache
Error: Mix manifest not found
npm install && npm run dev
Error: Database connection
- Pastikan MySQL service running
- Cek kredensial di file
.env - Test koneksi:
php artisan tinkerkemudianDB::connection()->getPdo();
Happy Coding! ๐
Top comments (0)