Di era digital saat ini, kebutuhan akan sistem kasir berbasis web (Point of Sale / POS) semakin meningkat, terutama bagi pelaku UMKM yang ingin mengelola transaksi secara cepat, akurat, dan real-time. Pada artikel ini, kita akan membahas secara lengkap cara membuat sistem kasir modern menggunakan Laravel 12 sebagai backend dan Vue.js sebagai frontend.
Laravel 12 menawarkan performa tinggi, struktur kode yang rapi, serta dukungan REST API yang powerful, sementara Vue.js memberikan pengalaman antarmuka yang interaktif dan responsif. Kombinasi keduanya sangat cocok untuk membangun aplikasi kasir yang scalable, ringan, dan mudah dikembangkan.
Tutorial ini akan membahas mulai dari setup project, desain database, pembuatan API transaksi, fitur keranjang belanja (cart system), manajemen produk, hingga proses checkout dan laporan penjualan. Dengan mengikuti panduan ini, Anda dapat membangun aplikasi kasir berbasis web yang siap digunakan untuk toko, café, minimarket, maupun bisnis retail lainnya.
STEP 1 — Setup Project Laravel + Vue
Install Laravel
composer create-project laravel/laravel smartpos<br>cd smartposInstall Breeze + Vue
Schema::create('categories', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->timestamps();
});Sekarang sudah ada:
- Login
- Register
- Vue 3
- Vite
- Sanctum ready
STEP 2 — Struktur Database POS
Kita buat minimal dulu:
- users
- categories
- products
- transactions
- transaction_items
Migration: Categories
php artisan make:model Category -mSchema::create('products', function (Blueprint $table) {
$table->id();
$table->foreignId('category_id')->constrained()->cascadeOnDelete();
$table->string('name');
$table->string('barcode')->nullable();
$table->decimal('cost_price', 12, 2)->default(0);
$table->decimal('sell_price', 12, 2);
$table->integer('stock')->default(0);
$table->timestamps();
});Migration: Products
php artisan make:model Product -mSchema::create('transactions', function (Blueprint $table) {
$table->id();
$table->string('invoice_number')->unique();
$table->decimal('total', 12, 2);
$table->decimal('paid', 12, 2);
$table->decimal('change', 12, 2);
$table->string('payment_method');
$table->foreignId('user_id')->constrained();
$table->timestamps();
});Migration: Transactions
php artisan make:model Transaction -mSchema::create('transactions', function (Blueprint $table) {
$table->id();
$table->string('invoice_number')->unique();
$table->decimal('total', 12, 2);
$table->decimal('paid', 12, 2);
$table->decimal('change', 12, 2);
$table->string('payment_method');
$table->foreignId('user_id')->constrained();
$table->timestamps();
});Migration: Transaction Items
php artisan make:model TransactionItem -mSchema::create('transaction_items', function (Blueprint $table) {
$table->id();
$table->foreignId('transaction_id')->constrained()->cascadeOnDelete();
$table->foreignId('product_id')->constrained();
$table->integer('qty');
$table->decimal('price', 12, 2);
$table->decimal('subtotal', 12, 2);
$table->timestamps();
});Jalankan Migration
php artisan migrateSTEP 3 — Relasi Model
Product.php
public function category()
{
return $this->belongsTo(Category::class);
}Transaction.php
public function items()
{
return $this->hasMany(TransactionItem::class);
}
public function user()
{
return $this->belongsTo(User::class);
}STEP 4 — API Endpoint
Edit routes/api.php
Route::middleware('auth:sanctum')->group(function () {
Route::apiResource('products', ProductController::class);
Route::post('transactions', [TransactionController::class, 'store']);
});STEP 5 — Logic Simpan Transaksi
Buat controller:
php artisan make:controller TransactionControllerContoh method store:
public function store(Request $request)
{
DB::beginTransaction();
try {
$transaction = Transaction::create([
'invoice_number' => 'INV-'.time(),
'total' => $request->total,
'paid' => $request->paid,
'change' => $request->paid - $request->total,
'payment_method' => $request->payment_method,
'user_id' => auth()->id(),
]);
foreach ($request->items as $item) {
TransactionItem::create([
'transaction_id' => $transaction->id,
'product_id' => $item['id'],
'qty' => $item['qty'],
'price' => $item['sell_price'],
'subtotal' => $item['sell_price'] * $item['qty'],
]);
Product::where('id', $item['id'])
->decrement('stock', $item['qty']);
}
DB::commit();
return response()->json(['success' => true]);
} catch (\Exception $e) {
DB::rollBack();
return response()->json(['error' => $e->getMessage()], 500);
}
}STEP 6 — Vue Cashier Page
Buat:
resources/js/Pages/Cashier.vueCore logic:
import { ref, computed } from 'vue'
import axios from 'axios'
const products = ref([])
const cart = ref([])
const total = computed(() =>
cart.value.reduce((sum, item) => sum + item.sell_price * item.qty, 0)
)
function addToCart(product) {
const existing = cart.value.find(p => p.id === product.id)
if (existing) {
existing.qty++
} else {
cart.value.push({ ...product, qty: 1 })
}
}
async function checkout() {
await axios.post('/api/transactions', {
items: cart.value,
total: total.value,
paid: total.value,
payment_method: 'cash'
})
cart.value = []
};Sampai Sini Kamu Sudah Punya:
✅ Login system
✅ CRUD produk
✅ Transaksi
✅ Pengurangan stok
✅ Total otomatis