Building modern web applications requires a powerful backend and a reactive frontend. This Laravel & Vue.js 3 product create tutorial - API based explains how to build a real-world product creation system using Laravel REST API and Vue.js 3.
This tutorial follows a clean API-based architecture, making it ideal for scalable enterprise applications.
php artisan make:migration create_products_table
// database/migrations/xxxx_xx_xx_create_products_table.php Schema::create('products', function (Blueprint $table) { $table->id(); $table->string('name'); $table->string('sku')->unique(); $table->decimal('price', 10, 2); $table->integer('stock')->default(0); $table->text('description')->nullable(); $table->string('image')->nullable(); $table->timestamps(); });
php artisan migrate
php artisan make:model Product
// app/Models/Product.php
class Product extends Model
{
protected $fillable = [
'name',
'sku',
'price',
'stock',
'description',
'image'
];
}
php artisan make:controller Api/ProductController
// app/Http/Controllers/Api/ProductController.php
use App\Models\Product;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;
class ProductController extends Controller
{
public function store(Request $request)
{
$validated = $request->validate([
'name' => 'required|string|max:255',
'sku' => 'required|unique:products,sku',
'price' => 'required|numeric',
'stock' => 'required|integer',
'description' => 'nullable|string',
'image' => 'nullable|image|mimes:jpg,png,jpeg|max:2048'
]);
if ($request->hasFile('image')) {
$validated['image'] = $request->file('image')
->store('products', 'public');
}
$product = Product::create($validated);
return response()->json([
'message' => 'Product created successfully',
'data' => $product
], 201);
}
}
// routes/api.php
use App\Http\Controllers\Api\ProductController;
Route::post('/products', [ProductController::class, 'store']);
php artisan storage:link
npm install axios
// src/services/api.js import axios from 'axios' export default axios.create({ baseURL: 'http://localhost:8000/api', headers: { 'Accept': 'application/json' } })
<!-- src/views/ProductCreate.vue -->
<template>
<div class="max-w-xl mx-auto p-6">
<h2 class="text-xl font-bold mb-4">Create Product</h2>
<form @submit.prevent="submit">
<input v-model="form.name" placeholder="Product Name" class="input" />
<input v-model="form.sku" placeholder="SKU" class="input" />
<input type="number" v-model="form.price" placeholder="Price" class="input" />
<input type="number" v-model="form.stock" placeholder="Stock" class="input" />
<textarea v-model="form.description" placeholder="Description" class="input"></textarea>
<input type="file" @change="handleFile" />
<button class="btn">Save Product</button>
</form>
<p v-if="error" class="text-red-500 mt-2">{{ error }}</p>
</div>
</template>
<script setup>
import { reactive, ref } from 'vue'
import api from '@/services/api'
const error = ref(null)
const form = reactive({
name: '',
sku: '',
price: '',
stock: '',
description: '',
image: null
})
const handleFile = (e) => {
form.image = e.target.files[0]
}
const submit = async () => {
error.value = null
const data = new FormData()
Object.keys(form).forEach(key => {
data.append(key, form[key])
})
try {
await api.post('/products', data, {
headers: { 'Content-Type': 'multipart/form-data' }
})
alert('Product Created Successfully')
} catch (err) {
error.value = err.response?.data?.message || 'Something went wrong'
}
}
</script>// src/router/index.js
{
path: '/products/create',
component: () => import('@/views/ProductCreate.vue')
}