Building modern web applications often requires creating Create, Read, Update, and Delete (CRUD) functionalities. This article provides a simple and practical example of building a Laravel Vue.js CRUD example
application, combining the power of Laravel's backend with Vue.js's reactive frontend. Whether you are a beginner or an experienced developer, this step-by-step guide will help you understand the core concepts and implementation details.
Why Choose Laravel and Vue.js for Your CRUD Application?
Laravel, a PHP framework known for its elegant syntax and robust features, is an excellent choice for handling backend operations. Its features like Eloquent ORM, routing, and security make it a great foundation for any web application. Vue.js, a progressive JavaScript framework, simplifies frontend development with its component-based architecture and reactive data binding. Combining these two technologies can result in a scalable, maintainable, and efficient application.
Setting Up Your Laravel Project for the Vue.js CRUD Example
First, you need to set up a new Laravel project. Open your terminal and run the following command:
composer create-project --prefer-dist laravel/laravel laravel-vue-crud
cd laravel-vue-crud
Next, configure your database connection in the .env
file. Update the DB_DATABASE
, DB_USERNAME
, and DB_PASSWORD
variables to match your database credentials.
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=your_database_name
DB_USERNAME=your_database_username
DB_PASSWORD=your_database_password
After configuring the database, run the migrations to create the necessary tables. For this Laravel Vue.js CRUD example
, we'll create a simple products
table.
php artisan migrate
Creating the Product Model and Migration
To interact with the products
table, create a model and migration using Artisan commands:
php artisan make:model Product -m
Open the generated migration file (database/migrations/*_create_products_table.php
) and define the table schema:
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateProductsTable extends Migration
{
public function up()
{
Schema::create('products', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->text('description')->nullable();
$table->decimal('price', 8, 2);
$table->timestamps();
});
}
public function down()
{
Schema::dropIfExists('products');
}
}
Run the migration again to apply the changes to the database:
php artisan migrate
Now, open the Product
model (app/Models/Product.php
) and define the fillable attributes:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Product extends Model
{
use HasFactory;
protected $fillable = [
'name',
'description',
'price',
];
}
Building the Laravel API for CRUD Operations
Next, create a controller to handle the CRUD operations. Use the following Artisan command:
php artisan make:controller ProductController --resource
Open the ProductController
(app/Http/Controllers/ProductController.php
) and implement the CRUD methods:
<?php
namespace App\Http\Controllers;
use App\Models\Product;
use Illuminate\Http\Request;
class ProductController extends Controller
{
public function index()
{
$products = Product::all();
return response()->json($products);
}
public function store(Request $request)
{
$request->validate([
'name' => 'required',
'price' => 'required|numeric',
]);
$product = Product::create($request->all());
return response()->json($product, 201);
}
public function show(Product $product)
{
return response()->json($product);
}
public function update(Request $request, Product $product)
{
$request->validate([
'name' => 'required',
'price' => 'required|numeric',
]);
$product->update($request->all());
return response()->json($product, 200);
}
public function destroy(Product $product)
{
$product->delete();
return response()->json(null, 204);
}
}
Define the API routes in routes/api.php
:
<?php
use App\Http\Controllers\ProductController;
use Illuminate\Support\Facades\Route;
Route::resource('products', ProductController::class);
Setting Up Vue.js Frontend
Install Vue.js using npm or yarn. If you don't have Node.js and npm installed, download them from the official Node.js website.
npm install vue@next
Or, using yarn:
yarn add vue@next
Create a resources/js/app.js
file and import Vue.js:
import { createApp } from 'vue';
const app = createApp({});
// Register components here
app.mount('#app');
Create a main component, for example, resources/js/components/ProductList.vue
:
<template>
<div>
<h2>Products</h2>
<ul>
<li v-for="product in products" :key="product.id">
{{ product.name }} - ${{ product.price }}
</li>
</ul>
</div>
</template>
<script>
import { ref, onMounted } from 'vue';
import axios from 'axios';
export default {
setup() {
const products = ref([]);
onMounted(async () => {
const response = await axios.get('/api/products');
products.value = response.data;
});
return {
products
};
}
}
</script>
Register the component in resources/js/app.js
:
import { createApp } from 'vue';
import ProductList from './components/ProductList.vue';
const app = createApp({});
app.component('product-list', ProductList);
app.mount('#app');
Integrating Vue.js with Laravel Views
Create a blade template where the Vue.js application will be mounted. Create resources/views/welcome.blade.php
:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Laravel Vue.js CRUD Example</title>
<link rel="stylesheet" href="{{ asset('css/app.css') }}">
</head>
<body>
<div id="app">
<product-list></product-list>
</div>
<script src="{{ asset('js/app.js') }}"></script>
</body>
</html>
Update your webpack.mix.js
file to compile your assets:
const mix = require('laravel-mix');
mix.js('resources/js/app.js', 'public/js')
.vue()
.sass('resources/sass/app.scss', 'public/css');
Run the compilation command:
npm run dev
Or, using yarn:
yarn dev
Displaying Data: Reading Products
In the ProductList.vue
component, you've already seen how to fetch and display data. Use axios
to make a GET request to your API endpoint and display the products in a list. Enhance the component to include more details like description and price.
<template>
<div>
<h2>Products</h2>
<ul>
<li v-for="product in products" :key="product.id">
{{ product.name }} - ${{ product.description }} - ${{ product.price }}
</li>
</ul>
</div>
</template>
<script>
import { ref, onMounted } from 'vue';
import axios from 'axios';
export default {
setup() {
const products = ref([]);
onMounted(async () => {
const response = await axios.get('/api/products');
products.value = response.data;
});
return {
products
};
}
}
</script>
Creating New Products: Implementing the Create Operation
Add a form to your ProductList.vue
component to create new products. Use Vue.js's data binding to bind the form inputs to data properties and make a POST request to your API endpoint.
<template>
<div>
<h2>Products</h2>
<ul>
<li v-for="product in products" :key="product.id">
{{ product.name }} - ${{ product.description }} - ${{ product.price }}
</li>
</ul>
<h2>Create New Product</h2>
<form @submit.prevent="createProduct">
<input type="text" v-model="newProduct.name" placeholder="Name" required>
<textarea v-model="newProduct.description" placeholder="Description"></textarea>
<input type="number" v-model="newProduct.price" placeholder="Price" required>
<button type="submit">Create</button>
</form>
</div>
</template>
<script>
import { ref, onMounted } from 'vue';
import axios from 'axios';
export default {
setup() {
const products = ref([]);
const newProduct = ref({
name: '',
description: '',
price: ''
});
onMounted(async () => {
await fetchProducts();
});
const fetchProducts = async () => {
const response = await axios.get('/api/products');
products.value = response.data;
};
const createProduct = async () => {
await axios.post('/api/products', newProduct.value);
newProduct.value = {
name: '',
description: '',
price: ''
};
await fetchProducts(); // Refresh the product list
};
return {
products,
newProduct,
createProduct
};
}
}
</script>
Updating Existing Products: Implementing the Update Operation
Add functionality to edit existing products. This involves adding an edit button for each product, displaying a form populated with the product's data, and making a PUT request to your API endpoint.
<template>
<div>
<h2>Products</h2>
<ul>
<li v-for="product in products" :key="product.id">
{{ product.name }} - {{ product.description }} - ${{ product.price }}
<button @click="editProduct(product)">Edit</button>
</li>
</ul>
<h2>Create New Product</h2>
<form @submit.prevent="createProduct">
<input type="text" v-model="newProduct.name" placeholder="Name" required>
<textarea v-model="newProduct.description" placeholder="Description"></textarea>
<input type="number" v-model="newProduct.price" placeholder="Price" required>
<button type="submit">Create</button>
</form>
<div v-if="editingProduct">
<h2>Edit Product</h2>
<form @submit.prevent="updateProduct">
<input type="text" v-model="editingProduct.name" placeholder="Name" required>
<textarea v-model="editingProduct.description" placeholder="Description"></textarea>
<input type="number" v-model="editingProduct.price" placeholder="Price" required>
<button type="submit">Update</button>
</form>
</div>
</div>
</template>
<script>
import { ref, onMounted } from 'vue';
import axios from 'axios';
export default {
setup() {
const products = ref([]);
const newProduct = ref({
name: '',
description: '',
price: ''
});
const editingProduct = ref(null);
onMounted(async () => {
await fetchProducts();
});
const fetchProducts = async () => {
const response = await axios.get('/api/products');
products.value = response.data;
};
const createProduct = async () => {
await axios.post('/api/products', newProduct.value);
newProduct.value = {
name: '',
description: '',
price: ''
};
await fetchProducts(); // Refresh the product list
};
const editProduct = (product) => {
editingProduct.value = { ...product }; // Create a copy to avoid direct mutation
};
const updateProduct = async () => {
if (editingProduct.value) {
await axios.put(`/api/products/${editingProduct.value.id}`, editingProduct.value);
editingProduct.value = null;
await fetchProducts(); // Refresh the product list
}
};
return {
products,
newProduct,
createProduct,
editingProduct,
editProduct,
updateProduct
};
}
}
</script>
Deleting Products: Implementing the Delete Operation
Implement the delete functionality by adding a delete button for each product and making a DELETE request to your API endpoint.
<template>
<div>
<h2>Products</h2>
<ul>
<li v-for="product in products" :key="product.id">
{{ product.name }} - {{ product.description }} - ${{ product.price }}
<button @click="editProduct(product)">Edit</button>
<button @click="deleteProduct(product)">Delete</button>
</li>
</ul>
<h2>Create New Product</h2>
<form @submit.prevent="createProduct">
<input type="text" v-model="newProduct.name" placeholder="Name" required>
<textarea v-model="newProduct.description" placeholder="Description"></textarea>
<input type="number" v-model="newProduct.price" placeholder="Price" required>
<button type="submit">Create</button>
</form>
<div v-if="editingProduct">
<h2>Edit Product</h2>
<form @submit.prevent="updateProduct">
<input type="text" v-model="editingProduct.name" placeholder="Name" required>
<textarea v-model="editingProduct.description" placeholder="Description"></textarea>
<input type="number" v-model="editingProduct.price" placeholder="Price" required>
<button type="submit">Update</button>
</form>
</div>
</div>
</template>
<script>
import { ref, onMounted } from 'vue';
import axios from 'axios';
export default {
setup() {
const products = ref([]);
const newProduct = ref({
name: '',
description: '',
price: ''
});
const editingProduct = ref(null);
onMounted(async () => {
await fetchProducts();
});
const fetchProducts = async () => {
const response = await axios.get('/api/products');
products.value = response.data;
};
const createProduct = async () => {
await axios.post('/api/products', newProduct.value);
newProduct.value = {
name: '',
description: '',
price: ''
};
await fetchProducts(); // Refresh the product list
};
const editProduct = (product) => {
editingProduct.value = { ...product }; // Create a copy to avoid direct mutation
};
const updateProduct = async () => {
if (editingProduct.value) {
await axios.put(`/api/products/${editingProduct.value.id}`, editingProduct.value);
editingProduct.value = null;
await fetchProducts(); // Refresh the product list
}
};
const deleteProduct = async (product) => {
if (confirm(`Are you sure you want to delete ${product.name}?`)) {
await axios.delete(`/api/products/${product.id}`);
await fetchProducts(); // Refresh the product list
}
};
return {
products,
newProduct,
createProduct,
editingProduct,
editProduct,
updateProduct,
deleteProduct
};
}
}
</script>
Enhancing User Experience and Application Security
To provide a better user experience, consider adding features like pagination, search, and sorting. Additionally, implement proper validation and authentication to secure your application.
Best Practices for a Laravel Vue.js CRUD Example
Follow best practices such as using environment variables for configuration, implementing proper error handling, and writing clean, maintainable code. Regular code reviews and testing are also crucial.
Conclusion: Your Laravel Vue.js CRUD Application
By following this guide, you've created a Laravel Vue.js CRUD example
application that demonstrates the fundamental CRUD operations. You can extend this example by adding more features and complexity to meet your specific requirements. Combining Laravel and Vue.js provides a powerful and efficient way to build modern web applications. Remember to always prioritize security, maintainability, and user experience in your development process.