Daftar Isi
- Pendahuluan
- Persiapan Environment
- Instalasi Laravel 9
- Konfigurasi Database
- Membuat Model dan Migration
- Membuat Controller
- Membuat Routes
- Membuat Views
- Testing Aplikasi
- Kesimpulan
Pendahuluan
CRUD (Create, Read, Update, Delete) adalah operasi dasar dalam pengembangan aplikasi web. Dalam tutorial ini, kita akan membuat aplikasi sederhana untuk mengelola data product menggunakan Laravel 9 dan MySQL.
Apa yang akan kita buat?
- Sistem manajemen data product
- Fitur tambah, lihat, edit, dan hapus product
- Interface yang user-friendly dengan Bootstrap
Persiapan Environment
Kebutuhan Sistem
- PHP 8.0 atau lebih tinggi
- Composer
- MySQL/MariaDB
- Node.js dan NPM (opsional)
Instalasi XAMPP (Windows)
- Download XAMPP dari https://www.apachefriends.org
- Install dan jalankan Apache dan MySQL
- Buka phpMyAdmin di
http://localhost/phpmyadmin
Instalasi Composer
- Download dari https://getcomposer.org
- Install sesuai OS yang digunakan
- Verifikasi dengan command:
composer --version
Instalasi Laravel 9
1. Create Project Laravel
composer create-project laravel/laravel crud-product "9.*"
cd crud-product
2. Menjalankan Server Development
php artisan serve
Buka browser dan akses http://localhost:8000
Konfigurasi Database
1. Buat Database MySQL
Buka phpMyAdmin dan buat database baru:
CREATE DATABASE crud_product;
2. Konfigurasi File .env
Edit file .env
di root project:
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=crud_product
DB_USERNAME=root
DB_PASSWORD=
3. Test Koneksi Database
php artisan migrate
Membuat Model dan Migration
1. Generate Model dan Migration
php artisan make:model Product -m
2. Edit Migration File
Buka file database/migrations/xxxx_xx_xx_xxxxxx_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()
{
Schema::create('products', function (Blueprint $table) {
$table->id();
$table->string('code', 20)->unique();
$table->string('name', 100);
$table->text('description');
$table->decimal('price', 10, 2);
$table->integer('stock')->default(0);
$table->string('category', 50);
$table->string('image')->nullable();
$table->enum('status', ['active', 'inactive'])->default('active');
$table->timestamps();
});
}
public function down()
{
Schema::dropIfExists('products');
}
};
3. Edit Model Product
Buka 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 = [
'code',
'name',
'description',
'price',
'stock',
'category',
'image',
'status'
];
protected $casts = [
'price' => 'decimal:2',
];
// Accessor untuk format harga
public function getFormattedPriceAttribute()
{
return 'Rp ' . number_format($this->price, 0, ',', '.');
}
// Scope untuk product aktif
public function scopeActive($query)
{
return $query->where('status', 'active');
}
}
4. Jalankan Migration
php artisan migrate
Membuat Controller
1. Generate Controller
php artisan make:controller ProductController --resource
2. Edit ProductController
Buka file app/Http/Controllers/ProductController.php
:
<?php
namespace App\Http\Controllers;
use App\Models\Product;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;
use Illuminate\Support\Facades\Storage;
class ProductController extends Controller
{
/**
* Display a listing of the resource.
*/
public function index(Request $request)
{
$query = Product::query();
// Search functionality
if ($request->filled('search')) {
$search = $request->search;
$query->where(function($q) use ($search) {
$q->where('name', 'like', "%{$search}%")
->orWhere('code', 'like', "%{$search}%")
->orWhere('category', 'like', "%{$search}%");
});
}
// Filter by category
if ($request->filled('category')) {
$query->where('category', $request->category);
}
// Filter by status
if ($request->filled('status')) {
$query->where('status', $request->status);
}
$products = $query->latest()->paginate(10);
$products->appends($request->query());
// Get categories for filter
$categories = Product::distinct()->pluck('category');
return view('products.index', compact('products', 'categories'));
}
/**
* Show the form for creating a new resource.
*/
public function create()
{
return view('products.create');
}
/**
* Store a newly created resource in storage.
*/
public function store(Request $request)
{
$validator = Validator::make($request->all(), [
'code' => 'required|unique:products|max:20',
'name' => 'required|max:100',
'description' => 'required',
'price' => 'required|numeric|min:0',
'stock' => 'required|integer|min:0',
'category' => 'required|max:50',
'image' => 'nullable|image|mimes:jpeg,png,jpg,gif|max:2048',
'status' => 'required|in:active,inactive'
]);
if ($validator->fails()) {
return redirect()->back()
->withErrors($validator)
->withInput();
}
$data = $request->all();
// Handle image upload
if ($request->hasFile('image')) {
$image = $request->file('image');
$imageName = time() . '.' . $image->getClientOriginalExtension();
$image->storeAs('public/products', $imageName);
$data['image'] = $imageName;
}
Product::create($data);
return redirect()->route('products.index')
->with('success', 'Product berhasil ditambahkan!');
}
/**
* Display the specified resource.
*/
public function show(Product $product)
{
return view('products.show', compact('product'));
}
/**
* Show the form for editing the specified resource.
*/
public function edit(Product $product)
{
return view('products.edit', compact('product'));
}
/**
* Update the specified resource in storage.
*/
public function update(Request $request, Product $product)
{
$validator = Validator::make($request->all(), [
'code' => 'required|max:20|unique:products,code,' . $product->id,
'name' => 'required|max:100',
'description' => 'required',
'price' => 'required|numeric|min:0',
'stock' => 'required|integer|min:0',
'category' => 'required|max:50',
'image' => 'nullable|image|mimes:jpeg,png,jpg,gif|max:2048',
'status' => 'required|in:active,inactive'
]);
if ($validator->fails()) {
return redirect()->back()
->withErrors($validator)
->withInput();
}
$data = $request->all();
// Handle image upload
if ($request->hasFile('image')) {
// Delete old image
if ($product->image) {
Storage::delete('public/products/' . $product->image);
}
$image = $request->file('image');
$imageName = time() . '.' . $image->getClientOriginalExtension();
$image->storeAs('public/products', $imageName);
$data['image'] = $imageName;
}
$product->update($data);
return redirect()->route('products.index')
->with('success', 'Product berhasil diperbarui!');
}
/**
* Remove the specified resource from storage.
*/
public function destroy(Product $product)
{
// Delete image if exists
if ($product->image) {
Storage::delete('public/products/' . $product->image);
}
$product->delete();
return redirect()->route('products.index')
->with('success', 'Product berhasil dihapus!');
}
}
Membuat Routes
Edit file routes/web.php
<?php
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\ProductController;
/*
|--------------------------------------------------------------------------
| Web Routes
|--------------------------------------------------------------------------
*/
Route::get('/', function () {
return redirect()->route('products.index');
});
Route::resource('products', ProductController::class);
Create Storage Link
php artisan storage:link
Membuat Views
1. Buat Folder dan Layout
Buat folder resources/views/products/
dan file layout:
resources/views/layouts/app.blade.php
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>@yield('title', 'CRUD Product')</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
<style>
.product-image {
width: 50px;
height: 50px;
object-fit: cover;
border-radius: 5px;
}
.product-detail-image {
width: 200px;
height: 200px;
object-fit: cover;
border-radius: 10px;
}
</style>
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-dark bg-primary">
<div class="container">
<a class="navbar-brand" href="{{ route('products.index') }}">
<i class="fas fa-box"></i> Product Management
</a>
</div>
</nav>
<div class="container mt-4">
@if(session('success'))
<div class="alert alert-success alert-dismissible fade show" role="alert">
{{ session('success') }}
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
@endif
@if(session('error'))
<div class="alert alert-danger alert-dismissible fade show" role="alert">
{{ session('error') }}
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
@endif
@yield('content')
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
@yield('scripts')
</body>
</html>
2. View Index (Daftar Product)
resources/views/products/index.blade.php
@extends('layouts.app')
@section('title', 'Daftar Product')
@section('content')
<div class="row">
<div class="col-md-12">
<div class="card">
<div class="card-header d-flex justify-content-between align-items-center">
<h5 class="mb-0">
<i class="fas fa-box"></i> Daftar Product
</h5>
<a href="{{ route('products.create') }}" class="btn btn-primary">
<i class="fas fa-plus"></i> Tambah Product
</a>
</div>
<div class="card-body">
<!-- Filter Form -->
<form method="GET" class="mb-4">
<div class="row">
<div class="col-md-4">
<input type="text" name="search" class="form-control"
placeholder="Cari product..." value="{{ request('search') }}">
</div>
<div class="col-md-3">
<select name="category" class="form-select">
<option value="">Semua Kategori</option>
@foreach($categories as $category)
<option value="{{ $category }}" {{ request('category') == $category ? 'selected' : '' }}>
{{ $category }}
</option>
@endforeach
</select>
</div>
<div class="col-md-3">
<select name="status" class="form-select">
<option value="">Semua Status</option>
<option value="active" {{ request('status') == 'active' ? 'selected' : '' }}>Active</option>
<option value="inactive" {{ request('status') == 'inactive' ? 'selected' : '' }}>Inactive</option>
</select>
</div>
<div class="col-md-2">
<button type="submit" class="btn btn-outline-primary w-100">
<i class="fas fa-search"></i> Filter
</button>
</div>
</div>
</form>
@if($products->count() > 0)
<div class="table-responsive">
<table class="table table-striped table-hover">
<thead class="table-dark">
<tr>
<th>No</th>
<th>Image</th>
<th>Code</th>
<th>Name</th>
<th>Price</th>
<th>Stock</th>
<th>Category</th>
<th>Status</th>
<th>Action</th>
</tr>
</thead>
<tbody>
@foreach($products as $key => $product)
<tr>
<td>{{ $products->firstItem() + $key }}</td>
<td>
@if($product->image)
<img src="{{ asset('storage/products/' . $product->image) }}"
alt="Product Image" class="product-image">
@else
<div class="product-image bg-light d-flex align-items-center justify-content-center">
<i class="fas fa-image text-muted"></i>
</div>
@endif
</td>
<td>{{ $product->code }}</td>
<td>{{ $product->name }}</td>
<td>{{ $product->formatted_price }}</td>
<td>
<span class="badge {{ $product->stock > 0 ? 'bg-success' : 'bg-danger' }}">
{{ $product->stock }}
</span>
</td>
<td>{{ $product->category }}</td>
<td>
<span class="badge {{ $product->status == 'active' ? 'bg-success' : 'bg-secondary' }}">
{{ ucfirst($product->status) }}
</span>
</td>
<td>
<div class="btn-group" role="group">
<a href="{{ route('products.show', $product) }}"
class="btn btn-info btn-sm" title="Detail">
<i class="fas fa-eye"></i>
</a>
<a href="{{ route('products.edit', $product) }}"
class="btn btn-warning btn-sm" title="Edit">
<i class="fas fa-edit"></i>
</a>
<form action="{{ route('products.destroy', $product) }}"
method="POST" class="d-inline"
onsubmit="return confirm('Yakin ingin menghapus product ini?')">
@csrf
@method('DELETE')
<button type="submit" class="btn btn-danger btn-sm" title="Hapus">
<i class="fas fa-trash"></i>
</button>
</form>
</div>
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
<!-- Pagination -->
<div class="d-flex justify-content-center">
{{ $products->links() }}
</div>
@else
<div class="text-center">
<i class="fas fa-box fa-3x text-muted mb-3"></i>
<p class="text-muted">Belum ada product.</p>
<a href="{{ route('products.create') }}" class="btn btn-primary">
<i class="fas fa-plus"></i> Tambah Product Pertama
</a>
</div>
@endif
</div>
</div>
</div>
</div>
@endsection
3. View Create (Tambah Product)
resources/views/products/create.blade.php
@extends('layouts.app')
@section('title', 'Tambah Product')
@section('content')
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card">
<div class="card-header">
<h5 class="mb-0">
<i class="fas fa-plus"></i> Tambah Product Baru
</h5>
</div>
<div class="card-body">
<form action="{{ route('products.store') }}" method="POST" enctype="multipart/form-data">
@csrf
<div class="row">
<div class="col-md-6">
<div class="mb-3">
<label for="code" class="form-label">Product Code <span class="text-danger">*</span></label>
<input type="text" class="form-control @error('code') is-invalid @enderror"
id="code" name="code" value="{{ old('code') }}" placeholder="Masukkan kode product">
@error('code')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
</div>
<div class="col-md-6">
<div class="mb-3">
<label for="name" class="form-label">Product Name <span class="text-danger">*</span></label>
<input type="text" class="form-control @error('name') is-invalid @enderror"
id="name" name="name" value="{{ old('name') }}" placeholder="Masukkan nama product">
@error('name')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
</div>
</div>
<div class="mb-3">
<label for="description" class="form-label">Description <span class="text-danger">*</span></label>
<textarea class="form-control @error('description') is-invalid @enderror"
id="description" name="description" rows="3"
placeholder="Masukkan deskripsi product">{{ old('description') }}</textarea>
@error('description')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
<div class="row">
<div class="col-md-4">
<div class="mb-3">
<label for="price" class="form-label">Price <span class="text-danger">*</span></label>
<input type="number" step="0.01" class="form-control @error('price') is-invalid @enderror"
id="price" name="price" value="{{ old('price') }}" placeholder="0.00">
@error('price')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
</div>
<div class="col-md-4">
<div class="mb-3">
<label for="stock" class="form-label">Stock <span class="text-danger">*</span></label>
<input type="number" class="form-control @error('stock') is-invalid @enderror"
id="stock" name="stock" value="{{ old('stock') }}" placeholder="0">
@error('stock')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
</div>
<div class="col-md-4">
<div class="mb-3">
<label for="category" class="form-label">Category <span class="text-danger">*</span></label>
<select class="form-select @error('category') is-invalid @enderror" id="category" name="category">
<option value="">Pilih Kategori</option>
<option value="Electronics" {{ old('category') == 'Electronics' ? 'selected' : '' }}>Electronics</option>
<option value="Clothing" {{ old('category') == 'Clothing' ? 'selected' : '' }}>Clothing</option>
<option value="Books" {{ old('category') == 'Books' ? 'selected' : '' }}>Books</option>
<option value="Home & Garden" {{ old('category') == 'Home & Garden' ? 'selected' : '' }}>Home & Garden</option>
<option value="Sports" {{ old('category') == 'Sports' ? 'selected' : '' }}>Sports</option>
<option value="Other" {{ old('category') == 'Other' ? 'selected' : '' }}>Other</option>
</select>
@error('category')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
</div>
</div>
<div class="row">
<div class="col-md-6">
<div class="mb-3">
<label for="image" class="form-label">Product Image</label>
<input type="file" class="form-control @error('image') is-invalid @enderror"
id="image" name="image" accept="image/*">
<small class="text-muted">Format: JPEG, PNG, JPG, GIF. Max: 2MB</small>
@error('image')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
</div>
<div class="col-md-6">
<div class="mb-3">
<label for="status" class="form-label">Status <span class="text-danger">*</span></label>
<select class="form-select @error('status') is-invalid @enderror" id="status" name="status">
<option value="">Pilih Status</option>
<option value="active" {{ old('status') == 'active' ? 'selected' : '' }}>Active</option>
<option value="inactive" {{ old('status') == 'inactive' ? 'selected' : '' }}>Inactive</option>
</select>
@error('status')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
</div>
</div>
<div class="d-flex justify-content-between">
<a href="{{ route('products.index') }}" class="btn btn-secondary">
<i class="fas fa-arrow-left"></i> Kembali
</a>
<button type="submit" class="btn btn-primary">
<i class="fas fa-save"></i> Simpan
</button>
</div>
</form>
</div>
</div>
</div>
</div>
@endsection
4. View Show (Detail Product)
resources/views/products/show.blade.php
@extends('layouts.app')
@section('title', 'Detail Product')
@section('content')
<div class="row justify-content-center">
<div class="col-md-10">
<div class="card">
<div class="card-header">
<h5 class="mb-0">
<i class="fas fa-box"></i> Detail Product
</h5>
</div>
<div class="card-body">
<div class="row">
<div class="col-md-4 text-center">
@if($product->image)
<img src="{{ asset('storage/products/' . $product->image) }}"
alt="Product Image" class="product-detail-image mb-3">
@else
<div class="product-detail-image bg-light d-flex align-items-center justify-content-center mx-auto mb-3">
<i class="fas fa-image fa-3x text-muted"></i>
</div>
@endif
<h4>{{ $product->name }}</h4>
<p class="text-muted">{{ $product->code }}</p>
<div class="mb-3">
<span class="badge {{ $product->status == 'active' ? 'bg-success' : 'bg-secondary' }} fs-6">
{{ ucfirst($product->status) }}
</span>
</div>
</div>
<div class="col-md-8">
<table class="table table-borderless">
<tr>
<th width="30%">Product Code</th>
<td>: {{ $product->code }}</td>
</tr>
<tr>
<th>Product Name</th>
<td>: {{ $product->name }}</td>
</tr>
<tr>
<th>Description</th>
<td>: {{ $product->description }}</td>
</tr>
<tr>
<th>Price</th>
<td>: <strong class="text-primary">{{ $product->formatted_price }}</strong></td>
</tr>
<tr>
<th>Stock</th>
<td>:
<span class="badge {{ $product->stock > 0 ? 'bg-success' : 'bg-danger' }}">
{{ $product->stock }} items
</span>
</td>
</tr>
<tr>
<th>Category</th>
<td>: {{ $product->category }}</td>
</tr>
<tr>
<th>Status</th>
<td>:
<span class="badge {{ $product->status == 'active' ? 'bg-success' : 'bg-secondary' }}">
{{ ucfirst($product->status) }}
</span>
</td>
</tr>
<tr>
<th>Created</th>
<td>: {{ $product->created_at->format('d F Y H:i') }}</td>
</tr>
<tr>
<th>Last Updated</th>
<td>: {{ $product->updated_at->format('d F Y H:i') }}</td>
</tr>
</table>
</div>
</div>
<div class="d-flex justify-content-between mt-4">
<a href="{{ route('products.index') }}" class="btn btn-secondary">
<i class="fas fa-arrow-left"></i> Kembali
</a>
<div>
<a href="{{ route('products.edit', $product) }}" class="btn btn-warning">
<i class="fas fa-edit"></i> Edit
</a>
<form action="{{ route('products.destroy', $product) }}" method="POST" class="d-inline"
onsubmit="return confirm('Yakin ingin menghapus product ini?')">
@csrf
@method('DELETE')
<button type="submit" class="btn btn-danger">
<i class="fas fa-trash"></i> Hapus
</button>
</form>
</div>
</div>
</div>
</div>
</div>
</div>
@endsection
5. View Edit (Edit Product)
resources/views/products/edit.blade.php
@extends('layouts.app')
@section('title', 'Edit Product')
@section('content')
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card">
<div class="card-header">
<h5 class="mb-0">
<i class="fas fa-edit"></i> Edit Product
</h5>
</div>
<div class="card-body">
<form action="{{ route('products.update', $product) }}" method="POST" enctype="multipart/form-data">
@csrf
@method('PUT')
<div class="row">
<div class="col-md-6">
<div class="mb-3">
<label for="code" class="form-label">Product Code <span class="text-danger">*</span></label>
<input type="text" class="form-control @error('code') is-invalid @enderror"
id="code" name="code" value="{{ old('code', $product->code) }}" placeholder="Masukkan kode product">
@error('code')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
</div>
<div class="col-md-6">
<div class="mb-3">
<label for="name" class="form-label">Product Name <span class="text-danger">*</span></label>
<input type="text" class="form-control @error('name') is-invalid @enderror"
id="name" name="name" value="{{ old('name', $product->name) }}" placeholder="Masukkan nama product">
@error('name')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
</div>
</div>
<div class="mb-3">
<label for="description" class="form-label">Description <span class="text-danger">*</span></label>
<textarea class="form-control @error('description') is-invalid @enderror"
id="description" name="description" rows="3"
placeholder="Masukkan deskripsi product">{{ old('description', $product->description) }}</textarea>
@error('description')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
<div class="row">
<div class="col-md-4">
<div class="mb-3">
<label for="price" class="form-label">Price <span class="text-danger">*</span></label>
<input type="number" step="0.01" class="form-control @error('price') is-invalid @enderror"
id="price" name="price" value="{{ old('price', $product->price) }}" placeholder="0.00">
@error('price')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
</div>
<div class="col-md-4">
<div class="mb-3">
<label for="stock" class="form-label">Stock <span class="text-danger">*</span></label>
<input type="number" class="form-control @error('stock') is-invalid @enderror"
id="stock" name="stock" value="{{ old('stock', $product->stock) }}" placeholder="0">
@error('stock')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
</div>
<div class="col-md-4">
<div class="mb-3">
<label for="category" class="form-label">Category <span class="text-danger">*</span></label>
<select class="form-select @error('category') is-invalid @enderror" id="category" name="category">
<option value="">Pilih Kategori</option>
<option value="Electronics" {{ old('category', $product->category) == 'Electronics' ? 'selected' : '' }}>Electronics</option>
<option value="Clothing" {{ old('category', $product->category) == 'Clothing' ? 'selected' : '' }}>Clothing</option>
<option value="Books" {{ old('category', $product->category) == 'Books' ? 'selected' : '' }}>Books</option>
<option value="Home & Garden" {{ old('category', $product->category) == 'Home & Garden' ? 'selected' : '' }}>Home & Garden</option>
<option value="Sports" {{ old('category', $product->category) == 'Sports' ? 'selected' : '' }}>Sports</option>
<option value="Other" {{ old('category', $product->category) == 'Other' ? 'selected' : '' }}>Other</option>
</select>
@error('category')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
</div>
</div>
<div class="row">
<div class="col-md-6">
<div class="mb-3">
<label for="image" class="form-label">Product Image</label>
@if($product->image)
<div class="mb-2">
<img src="{{ asset('storage/products/' . $product->image) }}"
alt="Current Image" class="product-image">
<small class="text-muted d-block">Gambar saat ini</small>
</div>
@endif
<input type="file" class="form-control @error('image') is-invalid @enderror"
id="image" name="image" accept="image/*">
<small class="text-muted">Format: JPEG, PNG, JPG, GIF. Max: 2MB. Kosongkan jika tidak ingin mengubah gambar.</small>
@error('image')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
</div>
<div class="col-md-6">
<div class="mb-3">
<label for="status" class="form-label">Status <span class="text-danger">*</span></label>
<select class="form-select @error('status') is-invalid @enderror" id="status" name="status">
<option value="">Pilih Status</option>
<option value="active" {{ old('status', $product->status) == 'active' ? 'selected' : '' }}>Active</option>
<option value="inactive" {{ old('status', $product->status) == 'inactive' ? 'selected' : '' }}>Inactive</option>
</select>
@error('status')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
</div>
</div>
<div class="d-flex justify-content-between">
<a href="{{ route('products.index') }}" class="btn btn-secondary">
<i class="fas fa-arrow-left"></i> Kembali
</a>
<button type="submit" class="btn btn-primary">
<i class="fas fa-save"></i> Update
</button>
</div>
</form>
</div>
</div>
</div>
</div>
@endsection
Testing Aplikasi
1. Menjalankan Server
php artisan serve
2. Test Fungsi CRUD
-
Create: Akses
/products/create
untuk menambah product -
Read: Akses
/products
untuk melihat daftar - Update: Klik tombol edit pada product yang ada
- Delete: Klik tombol hapus pada product yang ada
3. Test Upload Image
- Upload gambar saat create product
- Edit product dan ganti gambar
- Pastikan gambar tampil dengan benar
4. Test Filter dan Search
- Coba search berdasarkan nama, kode, atau kategori
- Filter berdasarkan kategori
- Filter berdasarkan status
Seeder untuk Data Dummy
Membuat Seeder
php artisan make:seeder ProductSeeder
Edit ProductSeeder
<?php
namespace Database\Seeders;
use Illuminate\Database\Seeder;
use App\Models\Product;
class ProductSeeder extends Seeder
{
public function run()
{
$products = [
[
'code' => 'LAPTOP001',
'name' => 'Laptop Gaming ASUS ROG',
'description' => 'Laptop gaming dengan spesifikasi tinggi untuk gaming dan multimedia',
'price' => 15000000,
'stock' => 10,
'category' => 'Electronics',
'status' => 'active'
],
[
'code' => 'SHIRT001',
'name' => 'Kemeja Casual Pria',
'description' => 'Kemeja casual berkualitas tinggi untuk pria',
'price' => 250000,
'stock' => 25,
'category' => 'Clothing',
'status' => 'active'
],
[
'code' => 'BOOK001',
'name' => 'Buku Programming Laravel',
'description' => 'Panduan lengkap belajar Laravel untuk pemula',
'price' => 150000,
'stock' => 50,
'category' => 'Books',
'status' => 'active'
]
];
foreach ($products as $product) {
Product::create($product);
}
}
}
Menjalankan Seeder
php artisan db:seed --class=ProductSeeder
Kesimpulan
Selamat! Anda telah berhasil membuat aplikasi CRUD Product menggunakan Laravel 9 dan MySQL. Aplikasi ini mencakup:
Fitur yang telah dibuat:
- ✅ CRUD Product lengkap (Create, Read, Update, Delete)
- ✅ Upload dan manajemen gambar product
- ✅ Search dan filter berdasarkan nama, kode, kategori
- ✅ Filter berdasarkan status (active/inactive)
- ✅ Validasi form yang komprehensif
- ✅ Interface responsive dengan Bootstrap 5
- ✅ Pagination untuk daftar product
- ✅ Format harga yang user-friendly
- ✅ Management stock dengan indikator visual
Pengembangan selanjutnya:
- Multi-image upload untuk product
- Categories management terpisah
- Export data ke Excel/PDF
- Barcode generator untuk product
- Stock movement tracking
- Product variants (size, color, etc.)
- Reviews dan ratings system
Tips Penggunaan:
- Pastikan storage link sudah dibuat:
php artisan storage:link
- Gunakan seeder untuk data dummy testing
- Backup database secara berkala
- Implementasi soft delete untuk data penting
- Gunakan queue untuk operasi yang berat seperti image processing
Tutorial ini memberikan dasar yang solid untuk pengembangan sistem manajemen product yang lebih kompleks!
Top comments (0)