Why should you choose Laravel with React.js? React.js is the most popular frontend framework, with a huge community base and many supported packages. Laravel, on the other hand, is the most popular MVC framework in the PHP ecosystem.
In this crud application, we will create a single-page application with all the frontend and backend files included. We will not use inertia; instead, we will use the Vite bundler.
However, you can create separate projects for both frameworks. In that scenario, Laravel would be your backend server for API response, and React.js would be your frontend, where client-side routing and components would be.
Here is the step-by-step guide:
Step 1: Setting up a new project
Create a new Laravel project using this command:
composer create-project --prefer-dist laravel/laravel larareact
Navigate to the folder.
cd larareact
Install the frontend dependencies using this command:
npm install
Install the additional npm packages below to work with React.
npm install --save-dev @vitejs/plugin-react bootstrap bootstrap-icons react react-bootstrap react-router-dom react-dom
We will use Bootstrap 5 CSS Framework.
After everything is installed, we need to configure the vite.config.js file:
import { defineConfig } from 'vite'; import laravel from 'laravel-vite-plugin'; import react from '@vitejs/plugin-react'; export default defineConfig({ plugins: [ laravel({ input: ['resources/js/app.jsx'], refresh: true, }), react(), ], });
Step 2: Configure Database
Go to the root of the project and edit the .env file to add the mysql database configuration:
DB_CONNECTION=mysql DB_HOST=127.0.0.1 DB_PORT=8889 DB_DATABASE=larareact DB_USERNAME=root DB_PASSWORD=root
You can replace your hostname, port, database, username, and password.
Step 3: Create a Model and Migration
We will create a migration file to add a schema and then migrate it into the database table.
To create a migration and model file the “-m” flag using the below command:
php artisan make:model Item -m
The above command created a migration file called [timestamp]_create_items_table.php:
public function up(): void { Schema::create('items', function (Blueprint $table) { $table->id(); $table->string('name'); $table->integer('price'); $table->timestamps(); }); }
Go to the cmd or terminal and migrate using the below command:
php artisan migrate
Now, add the below code inside the App\Models\Item.php file:
<?php namespace App\Models; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; class Item extends Model { use HasFactory; protected $fillable = ['name', 'price']; }
To prevent the MassAssignmentException, we need to add protected $fillable property.
Step 4: Create a Controller file
To create a resource controller in Laravel, use the below command:
php artisan make:controller ItemController --resource
Add the below code inside the App\Http\Controllers\ItemController.php file.
<?php namespace App\Http\Controllers; use Illuminate\Http\Request; use App\Models\Item; class ItemController extends Controller { /** * Display a listing of the resource. */ public function index() { $items = Item::all(); return response()->json($items); } /** * Store a newly created resource in storage. */ public function store(Request $request) { $item = new Item([ 'name' => $request->get('name'), 'price' => $request->get('price') ]); $item->save(); return response()->json('Successfully added'); } /** * Show the form for editing the specified resource. */ public function edit(string $id) { $item = Item::find($id); return response()->json($item); } /** * Update the specified resource in storage. */ public function update(Request $request, string $id) { $item = Item::find($id); $item->name = $request->get('name'); $item->price = $request->get('price'); $item->save(); return response()->json('Successfully Updated'); } /** * Remove the specified resource from storage. */ public function destroy(string $id) { $item = Item::find($id); $item->delete(); return response()->json('Successfully Deleted'); } }
In this Controller file, we added five functions:
- index(): It will return all the items from the database.
- store(): It will store a single item in the database.
- edit(): It will fetch the existing item from the database.
- update(): It will update the item to the database.
- delete(): It will remove the item from the database.
Step 5: Register the routes
Open the routes >> web.php file and add the below code:
<?php use App\Http\Controllers\ItemController; use Illuminate\Support\Facades\Route; Route::get('/', function () { return view('welcome'); }); Route::resource('items', ItemController::class);
Save the file, go to the root of the laravel project, and start the development server using this command:
php artisan serve
Step 6: Modify the view blade file
Modify the the resources >> views >> welcome.blade.php file like this:
<!doctype html> <html lang="{{ app()->getLocale() }}"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>Laravel React CRUD Application</title> @viteReactRefresh @vite(['resources/js/app.jsx']) </head> <body> <div id="root"></div> <script> window.Laravel = <?php echo json_encode([ 'csrfToken' => csrf_token(), ]); ?> </script> </body> </html>
In this code, we used the @viteReactRefresh and @vite directives to properly include the React view in our Laravel project.
Also, modify the resources >> js >> bootstrap.js file:
import 'bootstrap/dist/css/bootstrap.min.css'; import 'bootstrap-icons/font/bootstrap-icons.css'; import bootstrap from 'bootstrap/dist/js/bootstrap'; window.bootstrap = bootstrap; import axios from 'axios'; window.axios = axios;
Step 7: Create a Navigation component
Create a folder called components in the resources >> js folder, and in that folder, create a jsx file called “Navigation.jsx”. Add the following code to it:
import { NavLink } from "react-router-dom"; export default function Navigation() { return ( <nav className="navbar navbar-expand-lg navbar-dark bg-dark"> <div className="container"> <NavLink className="navbar-brand" to="/">Laravel React CRUD</NavLink> <button className="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation"> <span className="navbar-toggler-icon"></span> </button> </div> </nav> ) }
We will import this component to the main file and include it in all the pages we create in the future.
Step 8: Create three components for listing items, add items, and update items
Inside the resources >> js >> components folder, create three new files:
- Create.jsx
- Update.jsx
- Index.jsx
Add the below code inside the Create.jsx file:
import React, { useState } from "react"; import { useNavigate } from "react-router-dom"; function Create() { const [item, setItem] = useState({ name: '', price: '' }); const [successMessage, setSuccessMessage] = useState(''); const navigate = useNavigate(); const addItem = async (e) => { e.preventDefault(); const uri = 'http://localhost:8000/items'; try { await axios.post(uri, item); setSuccessMessage('Item added successfully!'); setTimeout(() => { setSuccessMessage(''); navigate('/'); }, 1000); } catch (error) { console.error('Error adding item:', error); } }; const handleInputChange = (e) => { const { name, value } = e.target; setItem(prevItem => ({ ...prevItem, [name]: value })); }; return ( <div className="container mt-5"> <div className="card shadow-lg"> <div className="card-header bg-gradient-primary text-white"> <h1 className="card-title">Create An Item</h1> </div> <div className="card-body"> {successMessage && ( <div className="alert alert-success"> {successMessage} </div> )} <form onSubmit={addItem}> <div className="row g-3"> <div className="col-md-6"> <label htmlFor="itemName" className="form-label">Item Name:</label> <input type="text" className="form-control" id="itemName" name="name" value={item.name} onChange={handleInputChange} placeholder="Enter item name" required /> </div> <div className="col-md-6"> <label htmlFor="itemPrice" className="form-label">Item Price:</label> <input type="text" className="form-control" id="itemPrice" name="price" value={item.price} onChange={handleInputChange} placeholder="Enter item price" required /> </div> </div> <div className="mt-4 text-center"> <button type="submit" className="btn btn-primary px-4"> <i className="bi bi-plus-lg"></i> Add Item </button> </div> </form> <div className="mt-4 d-flex justify-content-start"> <button onClick={() => navigate('/')} className="btn btn-outline-secondary"> <i className="bi bi-arrow-left-circle"></i> Return to Items List </button> </div> </div> </div> </div> ); } export default Create;
In this code, we created a bootstrap form. When the form data is submitted, we send a POST request with the data to the Laravel server.
Add the below code inside the Update.jsx file:
import React, { useEffect, useState } from "react"; import { useParams, useNavigate } from "react-router-dom"; function Update() { const [item, setItem] = useState({ name: '', price: '' }); const [successMessage, setSuccessMessage] = useState(''); const params = useParams(); const navigate = useNavigate(); useEffect(() => { const getItem = async () => { try { const uri = `http://localhost:8000/items/${params.id}/edit`; const response = await axios.get(uri); setItem(response.data); } catch (error) { console.error("Failed to fetch item:", error); } }; getItem(); }, [params.id]); const updateItem = async (e) => { e.preventDefault(); const uri = `http://localhost:8000/items/${params.id}`; try { await axios.patch(uri, item); setSuccessMessage('Item Updated Successfully!'); setTimeout(() => { setSuccessMessage(''); navigate('/'); // Replace '/' with your actual route name for the item list }, 1000); } catch (error) { console.error("Failed to update item:", error); } }; const handleInputChange = (e) => { const { name, value } = e.target; setItem(prevItem => ({ ...prevItem, [name]: value })); }; return ( <div className="container mt-5"> <div className="card shadow-lg"> <div className="card-header bg-gradient-primary text-white"> <h1 className="card-title text-center">Edit an Item</h1> </div> <div className="card-body"> {successMessage && ( <div className="alert alert-success"> {successMessage} </div> )} <form onSubmit={updateItem}> <div className="row g-3"> <div className="col-md-6"> <label htmlFor="itemName" className="form-label">Item Name:</label> <input type="text" id="itemName" name="name" className="form-control" value={item.name} onChange={handleInputChange} placeholder="Enter item name" required /> </div> <div className="col-md-6"> <label htmlFor="itemPrice" className="form-label">Item Price:</label> <input type="text" id="itemPrice" name="price" className="form-control" value={item.price} onChange={handleInputChange} placeholder="Enter item price" required /> </div> </div> <div className="text-center mt-4"> <button type="submit" className="btn btn-primary px-4">Update Item</button> </div> </form> <div className="mt-4 d-flex justify-content-start"> <button onClick={() => navigate('/')} className="btn btn-outline-secondary"> <i className="bi bi-arrow-left-circle"></i> Return to Items List </button> </div> </div> </div> </div> ); } export default Update;
In this code, we fetched the existing data from the database, put it inside the textbox, and when we were done editing the data, it sent a Patch request to the Laravel server to update the data in the database.
Add the below code inside the Index.jsx file:
import React, { useEffect, useState } from "react"; import { useNavigate, Link } from "react-router-dom"; import Modal from 'react-bootstrap/Modal'; import Button from 'react-bootstrap/Button'; function Index() { const [items, setItems] = useState([]); const [successMessage, setSuccessMessage] = useState(''); const [showDeleteModal, setShowDeleteModal] = useState(false); const [itemIdToDelete, setItemIdToDelete] = useState(null); const navigate = useNavigate(); useEffect(() => { const fetchItems = async () => { try { const uri = 'http://localhost:8000/items'; const response = await axios.get(uri); setItems(response.data); } catch (error) { console.error('Error fetching items:', error); } }; fetchItems(); }, []); const openDeleteConfirmation = (id) => { setItemIdToDelete(id); setShowDeleteModal(true); }; const closeModal = () => { setShowDeleteModal(false); }; const confirmDelete = async () => { if (itemIdToDelete !== null) { try { const uri = `http://localhost:8000/items/${itemIdToDelete}`; await axios.delete(uri); setItems(currentItems => currentItems.filter((item) => item.id !== itemIdToDelete)); setSuccessMessage('Item deleted successfully!'); setTimeout(() => { setSuccessMessage(''); navigate('/'); // Replace '/' with your actual route name for the item list }, 1000); closeModal(); } catch (error) { console.error('Error deleting item:', error); } } }; return ( <div className="container mt-5"> <h1 className="mb-4">Items</h1> {successMessage && ( <div className="alert alert-success"> {successMessage} </div> )} <div className="d-flex justify-content-end mb-4"> <Link to="/create" className="btn btn-success"><i className="bi bi-plus-lg"></i> Create New Item</Link> </div> <div className="table-responsive"> <table className="table table-hover table-bordered"> <thead className="table-dark"> <tr> <th scope="col">ID</th> <th scope="col">Item Name</th> <th scope="col">Item Price</th> <th scope="col" style={{ width: "20%" }}>Actions</th> </tr> </thead> <tbody> {items.map((item) => ( <tr key={item.id}> <td>{item.id}</td> <td>{item.name}</td> <td>{item.price}</td> <td> <Link to={`/edit/${item.id}`} className="btn btn-sm btn-outline-primary me-2"> <i className="bi bi-pencil-square me-1"></i> Edit </Link> <Button variant="outline-danger" size="sm" onClick={() => openDeleteConfirmation(item.id)}> <i className="bi bi-trash me-1"></i> Delete </Button> </td> </tr> ))} </tbody> </table> </div> {/* Delete Confirmation Modal */} <Modal show={showDeleteModal} onHide={closeModal}> <Modal.Header closeButton> <Modal.Title>Confirm Delete</Modal.Title> </Modal.Header> <Modal.Body>Are you sure you want to delete this item?</Modal.Body> <Modal.Footer> <Button variant="secondary" onClick={closeModal}>Cancel</Button> <Button variant="danger" onClick={confirmDelete}>Delete</Button> </Modal.Footer> </Modal> </div> ); } export default Index;
In this long file, we are fetching all the items and displaying them in the list format with the Edit and Delete buttons.
If you click on the Edit button, it will display the Update.jsx view.
A dialog box with a confirmation message will open if you click the Delete button. If you confirm, the data will be removed from the database.
Step 9: Configure the frontend routing
Create a file called MainApp.jsx inside resources >> js folder and add the code below.
import { Routes, Route } from "react-router-dom"; import Navigation from "./components/Navigation"; import Create from "./components/Create"; import Update from "./components/Update"; import Index from "./components/Index"; function App() { return ( <div className="container"> <Navigation /> <Routes> <Route path="/" element={<Index />} /> <Route path="/create" element={<Create />} /> <Route path="/edit/:id" element={<Update />} /> </Routes> </div> ) } export default App;
In this code, we declare and define our application’s routes.
We assigned a “/” URL for the listing page.
To create an item, use the “/create” URL.
Create an app.jsx file inside the resources >> js folder and import the MainApp module inside the app.jsx file.
import './bootstrap' import '../css/app.css' import React from 'react' import ReactDOM from 'react-dom/client' import App from './MainApp'; import { BrowserRouter } from "react-router-dom" ReactDOM.createRoot(document.getElementById('root')).render( <React.StrictMode> <BrowserRouter> <App /> </BrowserRouter> </React.StrictMode>, )
This is the main file that we included in the welcome.blade.php file. This means we have now registered the React.js views to the Laravel application.
My resources >> css >> app.css file looks like this:
.bg-gradient-primary { background: linear-gradient(45deg, #007bff, #6610f2); }
Save the file, go to the root of the project, and run the vite development server using this command:
npm run dev
Step 10: Run the project
Make sure that you are running two development servers:
- Laravel development server (php artisan serve)
- Vite development server (npm run dev)
To create an item, you need to visit this URL: http://localhost:8000/create
The create page looks like this:
Let’s create six items. When you create an item every time, you will be redirected to the listing page (http://localhost:8000/).
After creating 6 items, our listing page looks like this:
If you click on the Edit button, you will be redirected to the update page like this:
If you update the item price, it will get updated in the database, and you will see the success message and be redirected to the listing page.
If you try to delete an item, you will be prompted for confirmation like this:
If you press the Delete button, it will remove the item from the database.
That’s it!
Here is the complete code on Github.