Building a CRUD Application using Laravel 10, Inertia, and React-Start from Basic

Laravel 10, Inertia, and React

Sharing is caring!

1,067 Views -

In the ever-evolving landscape of web development, frameworks that streamline the process of building robust applications are highly sought after. Laravel, known for its elegance and simplicity, has been a go-to choice for PHP developers. When combined with Inertia.js and React, developers can create dynamic and responsive Single Page Applications (SPAs) efficiently. In this article, we’ll guide you through the process of building a CRUD (Create, Read, Update, Delete) application using Laravel 10, Inertia, and React, complete with code examples.

Understanding the Tech Stack

Laravel 10: Laravel is a powerful PHP framework that provides an expressive syntax, built-in tools, and conventions to speed up development. Laravel 10, the latest version at the time of writing, introduces new features and improvements.

Inertia.js: Inertia is a library that allows you to build single-page applications using classic server-driven routing while leveraging the benefits of modern SPAs. It eliminates the need to build APIs for the front end and simplifies the communication between the backend and frontend.

React: React is a widely-used JavaScript library for building user interfaces. It excels in creating reusable components and efficiently updating the user interface as data changes.

Creating a CRUD Application

For the purpose of this tutorial, we’ll create a simple CRUD application for managing tasks.

Setting Up the Project

composer create-project laravel/laravel laravel-inertia-react

Installing Inertia

1-Server Side

composer require inertiajs/inertia-laravel

2-Client Side

npm install @inertiajs/react

Then update your main javascript file – app.jsx

import { createInertiaApp } from '@inertiajs/react'
import { createRoot } from 'react-dom/client'

createInertiaApp({
  resolve: name => {
    const pages = import.meta.glob('./Pages/**/*.jsx', { eager: true })
    return pages[`./Pages/${name}.jsx`]
  },
  setup({ el, App, props }) {
    createRoot(el).render(<App {...props} />)
  },
})

Create a Product model with migration

php artisan make:model Product -m

Create a Product Controller with request and resource

php artisan make:controller ProductController --model=Product --requests --resource

ProductController.php

<?php

namespace App\Http\Controllers;

use App\Models\Product;
use App\Http\Requests\StoreProductRequest;
use App\Http\Requests\UpdateProductRequest;
use Illuminate\Support\Facades\Validator;
use Inertia\Inertia;

class ProductController extends Controller
{
    /**
     * Display a listing of the resource.
     */
    public function index()
    {
        $products = Product::all();
        return Inertia::render('Product/index', ['products' => $products]);
    }

    /**
     * Show the form for creating a new resource.
     */
    public function create()
    {
        return Inertia::render('Product/Create');
    }

    /**
     * Store a newly created resource in storage.
     */
    public function store(StoreProductRequest $request)
    {
        Validator::make($request->all(), [
            'name' => ['required'],
            'description' => ['required'],
        ])->validate();
   
        Product::create($request->all());
    
        return redirect()->route('product.index');
    }

    /**
     * Display the specified resource.
     */
    public function show(Product $product)
    {
        return Inertia::render('Product/Show', [
            'product' => $product
        ]);
    }

    /**
     * Show the form for editing the specified resource.
     */
    public function edit(Product $product)
    {
        return Inertia::render('Product/Edit', [
            'product' => $product
        ]);
    }

    /**
     * Update the specified resource in storage.
     */
    public function update(UpdateProductRequest $request, Product $product)
    {
        Product::find($product->id)->update($request->all());
        return redirect()->route('product.index');
    }

    /**
     * Remove the specified resource from storage.
     */
    public function destroy(Product $product)
    {
        Product::findOrFail($product->id)->delete();
        return redirect()->route('product.index');
    }
}

Add to route/web.php

use App\Http\Controllers\ProductController;

Route::resource('product', ProductController::class)

Create Product directory to resources/js

Add file Create.jsx with code.

import React from 'react';
import AuthenticatedLayout from '@/Layouts/AuthenticatedLayout';
import { Head, useForm, Link } from '@inertiajs/react';

export default function Dashboard(props) {
    const { data, setData, errors, post } = useForm({
        name: "",
        description: "",
    });

    function handleSubmit(e) {
        e.preventDefault();
        post(route("product.store"));
    }

    return (
        <AuthenticatedLayout auth={props.auth} user={props.auth.user} errors={props.errors} header={<h2 className="font-semibold text-xl text-gray-800 leading-tight">Create Product</h2>}>
            <Head title="Posts" />
            <div className="py-12">
                <div className="max-w-7xl mx-auto sm:px-6 lg:px-8">
                    <div className="bg-white overflow-hidden shadow-sm sm:rounded-lg">
                        <div className="p-6 bg-white border-b border-gray-200">
                            <div className="flex items-center justify-between mb-6">
                                <Link className="px-6 py-2 text-white bg-blue-500 rounded-md focus:outline-none" href={ route("product.index") }>Back</Link>
                            </div>
                            <form name="createForm" onSubmit={handleSubmit}>
                                <div className="flex flex-col">
                                    <div className="mb-4">
                                        <label className="">Name</label>
                                        <input type="text" className="w-full px-4 py-2" label="Name" name="name" value={data.name} onChange={(e) => setData("name", e.target.value) }/>
                                        <span className="text-red-600">
                                            {errors.name}
                                        </span>
                                    </div>
                                    <div className="mb-0">
                                        <label className="">Description</label>
                                        <textarea type="text" className="w-full rounded" label="description" name="description" errors={errors.description} value={data.description} onChange={(e) => setData("description", e.target.value) }/>
                                        <span className="text-red-600">
                                            {errors.description}
                                        </span>
                                    </div>
                                </div>
                                <div className="mt-4">
                                    <button type="submit" className="px-6 py-2 font-bold text-white bg-green-500 rounded">Save</button>
                                </div>
                            </form>
                        </div>
                    </div>
                </div>
            </div>
        </AuthenticatedLayout>
    );
}

Add file index.jsx to the same Product directory

import AuthenticatedLayout from '@/Layouts/AuthenticatedLayout';
import { Head, usePage, Link, router  } from '@inertiajs/react';

export default function Dashboard(props) {
    const { products } = usePage().props 
    function destroy(e) {
        if (confirm("Are you sure you want to delete this product?")) {
            router.delete(route("product.destroy", e.currentTarget.id));
        }
    }
    return (
            <AuthenticatedLayout auth={props.auth} user={props.auth.user} errors={props.errors} header={<h2 className="font-semibold text-xl text-gray-800 leading-tight">Products</h2>}>       
                <Head title="Posts" />
                <div className="py-12">
                    <div className="max-w-7xl mx-auto sm:px-6 lg:px-8">
                        <div className="bg-white overflow-hidden shadow-sm sm:rounded-lg">
                            <div className="p-6 bg-white border-b border-gray-200">
                                <div className="flex items-center justify-between mb-6">
                                    <Link className="px-6 py-2 text-white bg-green-500 rounded-md focus:outline-none" href={ route("product.create") }>Create Product</Link>
                                </div>
                                <table className="table-fixed w-full">
                                    <thead>
                                        <tr className="bg-gray-100">
                                            <th className="px-4 py-2 w-20">No.</th>
                                            <th className="px-4 py-2">Title</th>
                                            <th className="px-4 py-2">Body</th>
                                            <th className="px-4 py-2">Action</th>
                                        </tr>
                                    </thead>
                                    <tbody>
                                        {products && products.map( (item) => (
                                            <tr key={item.id}>
                                                <td className="border px-4 py-2">{ item.id }</td>
                                                <td className="border px-4 py-2">{ item.name }</td>
                                                <td className="border px-4 py-2">{ item.description }</td>
                                                <td className="border px-4 py-2">
                                                    <Link tabIndex="1" className="px-4 py-2 text-sm text-white bg-blue-500 rounded" href={route("product.edit", item.id)} >Edit</Link>
                                                    <button onClick={destroy} id={item.id} tabIndex="-1" type="button" className="mx-1 px-4 py-2 text-sm text-white bg-red-500 rounded" >Delete</button>
                                                </td>
                                            </tr>
                                        ))}
                                        {products.length === 0 && (
                                            <tr>
                                                <td className="px-6 py-4 border-t"colSpan="4">No contacts found.</td>
                                            </tr>
                                        )}
                                    </tbody>
                                </table>
                            </div>
                        </div>
                    </div>
                </div>
            </AuthenticatedLayout>
        );
    }

Add Edit.jsx to product update

import AuthenticatedLayout from '@/Layouts/AuthenticatedLayout';
import DeleteUserForm from './Partials/DeleteUserForm';
import UpdatePasswordForm from './Partials/UpdatePasswordForm';
import UpdateProfileInformationForm from './Partials/UpdateProfileInformationForm';
import { Head } from '@inertiajs/react';

export default function Edit({ auth, mustVerifyEmail, status }) {
    return (
        <AuthenticatedLayout
            user={auth.user}
            header={<h2 className="font-semibold text-xl text-gray-800 leading-tight">Profile</h2>}
        >
            <Head title="Profile" />

            <div className="py-12">
                <div className="max-w-7xl mx-auto sm:px-6 lg:px-8 space-y-6">
                    <div className="p-4 sm:p-8 bg-white shadow sm:rounded-lg">
                        <UpdateProfileInformationForm
                            mustVerifyEmail={mustVerifyEmail}
                            status={status}
                            className="max-w-xl"
                        />
                    </div>

                    <div className="p-4 sm:p-8 bg-white shadow sm:rounded-lg">
                        <UpdatePasswordForm className="max-w-xl" />
                    </div>

                    <div className="p-4 sm:p-8 bg-white shadow sm:rounded-lg">
                        <DeleteUserForm className="max-w-xl" />
                    </div>
                </div>
            </div>
        </AuthenticatedLayout>
    );
}

Run migrations

php artisan migrate

To Run and build dependencies run this command.

npm install

npm run dev

To Run the application.

php artisan serve

Conclusion

In this article, we’ve explored how to build a CRUD application using Laravel 10, Inertia, and React. By leveraging Laravel’s backend capabilities, Inertia’s server-driven approach, and React’s powerful frontend library, developers can create feature-rich applications while enjoying efficient development practices.

Remember, this tutorial serves as a starting point. As you become more familiar with these technologies, you can explore advanced topics such as authentication, validation, and state management to further enhance your application. Happy coding!

Check out the code at GitHub.

Reference:

Inertia.js

Laravel 10

5 1 vote
Article Rating
Subscribe
Notify of
guest
0 Comments
Inline Feedbacks
View all comments