Laravel 9 ReactJS CRUD App
laramatic.com

Build Laravel 9 ReactJS CRUD App Tutorial And Examples

In this post Build Laravel 9 ReactJS CRUD App Tutorial And Examples we have used React js, Laravel 9 RESTful APIs to handle HTTP requests with Axios and have created a basic application. You can practice it on your own by changing the code. 

ReactJS

ReactJS is an open source JavaScript library that we can use to build a beautiful UI. Meta (FaceBook) and their active community keeps it updated. We can use Reactjs to create single page web apps. Web artisans consider ReactJS as the most progressive and accomplished JavaScrip framework to build quality front-ends of mobile and web apps. 

Useful Features of ReactJS to Build Front-end Environment 

  • JSX (JavaScript Syntax Extension)
  • Virtual DOM
  • One-way data binding
  • Impactful 
  • Extensions
  • Conditionals
  • Component ready 
  • Minimalistic 

CRUD App Using Laravel 9 Reactjs MySQL Database and RESTful APIs for Request Handling 

Laravel after the release of Laravel 9 especially has become the top PHP framework for building web apps. As 80% of web runs on PHP has advanced features with tons of free learning resources. Its conceptuality is minimalistic and very basic with a rich ecosystem. We can use Laravel to build webpack based tools with a rich web dev environment and create a single page app with basic knowledge. It can also be used back end to empower the frontend. There is so much potential in it that we can build awesome single page apps.In this tutorial we will create Laravel API and use it for our React frontend. It is a CRUD application which allows us to Create, Retrieve, Update and Delete whose frontend is created with Reactjs framework while Laravel is used as its backend.  

ReactJS allows us to build interactive user interfaces by using a variety of components with it. As our CRUD app will clear most of the dust which is also a ReactJS CRUD app. We are using RESTful APIs to execute database operations such as managing app data. While we are using Laravel 9 and MySQL for backend and database respectively. Our RESTful API will lighten up our app and the requests executed through APIs are handled differently and that difference makes the job very light. You will see our app’s frontend built in React and the RESTful APIs created in Laravel 9. 

Start Basic CRUD App with Laravel 9 and React.js

The application we are going to build is a simple game interface for trivia quizzes. It allows you to register the players, generate new questions from a free API and mark the answers of the players as either right or wrong.

CRUD Reactjs Laravel 9 App 

We are going to build will be a 4 columns 4 rows students with names, scores, photos and actions that will let us edit/save their details. 

Steps to build a basic CRUD application using Laravel 9 and React.js:

#1 Create A New Laravel App Project

Use this command to create a new Laravel project 

composer create-project --prefer-dist laravel/laravel:^9.2 laravel-react-crud-app

If you have Laravel global composer dependency installed use this in your terminal:

laravel new laravel-react-crud-app

#2 Configure Database 

Once the installation is over open project root directory, open the .env file, and set database as:

DB_CONNECTION=mysql 

DB_HOST=127.0.0.1 

DB_PORT=3306 

DB_DATABASE=<DATABASE NAME>

DB_USERNAME=<DATABASE USERNAME>

DB_PASSWORD=<DATABASE PASSWORD>

#3 Create product Model, Migration and Controller

Run this command as we have to create model for our Product, Controller and Migration

php artisan make:model Product -mcr

You should know that -mcr is the argument that will create Model, Migration and Controller for us. 

Next we are to open the migration file of our product from the database/migration and replace the code there in the up () function:

public function up()

{

    Schema::create('products', function (Blueprint $table) {

        $table->bigIncrements('id');

        $table->string('title');

        $table->text('description');

        $table->text('image');

        $table->timestamps();

    });

}

Using this command to migrate:

php artisan migrate

We need to update the code in the Product.php model. We will open the Category.php model in our app/Models and do that. See here

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;

use Illuminate\Database\Eloquent\Model;

class Product extends Model {

   use HasFactory;

   protected $fillable = ['title', 'description', 'image'];

}

?>

The next we have to declare product functions we are to open Open ProductController.php and add code in the index, store, show, update, and delete see the syntax here do so:

<?php

namespace App\Http\Controllers;

use App\Models\Product;
use Illuminate\Http\Request;

use Illuminate\Support\Str;
use Illuminate\Support\Facades\Storage;
use Carbon\Carbon;

class ProductController extends Controller
{
    /**
     * Display a listing of the resource.
     *
     * @return \Illuminate\Http\Response
     */
    public function index()
    {
        return Product::select('id','title','description','image')->get();
    }

    /**
     * Store a newly created resource in storage.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\Response
     */
    public function store(Request $request)
    {
        $request->validate([
            'title'=>'required',
            'description'=>'required',
            'image'=>'required|image'
        ]);

        try{
            $imageName = Str::random().'.'.$request->image->getClientOriginalExtension();
            Storage::disk('public')->putFileAs('product/image', $request->image,$imageName);
            Product::create($request->post()+['image'=>$imageName]);

            return response()->json([
                'message'=>'Product Created Successfully!!'
            ]);
        }catch(\Exception $e){
            \Log::error($e->getMessage());
            return response()->json([
                'message'=>'Something goes wrong while creating a product!!'
            ],500);
        }
    }

    /**
     * Display the specified resource.
     *
     * @param  \App\Models\Product  $product
     * @return \Illuminate\Http\Response
     */
    public function show(Product $product)
    {
        return response()->json([
            'product'=>$product
        ]);
    }

    /**
     * Update resources in storage which are specified.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \App\Models\Product  $product
     * @return \Illuminate\Http\Response
     */
    public function update(Request $request, Product $product)
    {
        $request->validate([
            'title'=>'required',
            'description'=>'required',
            'image'=>'nullable'
        ]);

        try{

            $product->fill($request->post())->update();

            if($request->hasFile('image')){

                // remove old image
                if($product->image){
                    $exists = Storage::disk('public')->exists("product/image/{$product->image}");
                    if($exists){
                        Storage::disk('public')->delete("product/image/{$product->image}");
                    }
                }

                $imageName = Str::random().'.'.$request->image->getClientOriginalExtension();
                Storage::disk('public')->putFileAs('product/image', $request->image,$imageName);
                $product->image = $imageName;
                $product->save();
            }

            return response()->json([
                'message'=>'Product Updated Successfully!!'
            ]);

        }catch(\Exception $e){
            \Log::error($e->getMessage());
            return response()->json([
                'message'=>'Something goes wrong while updating a product!!'
            ],500);
        }
    }

    /**
     * Remove the specified resource from storage.
     *
     * @param  \App\Models\Product  $product
     * @return \Illuminate\Http\Response
     */
    public function destroy(Product $product)
    {
        try {

            if($product->image){
                $exists = Storage::disk('public')->exists("product/image/{$product->image}");
                if($exists){
                    Storage::disk('public')->delete("product/image/{$product->image}");
                }
            }

            $product->delete();

            return response()->json([
                'message'=>'Product Deleted Successfully!!'
            ]);
            
        } catch (\Exception $e) {
            \Log::error($e->getMessage());
            return response()->json([
                'message'=>'Something goes wrong while deleting a product!!'
            ]);
        }
    }
}

#4 Adding API Routes 

Now we have to specify and update routes which we can do in the api.php file by opening routes folder and then updating the routes in api.php file. See the syntax here: 

use App\Http\Controllers\ProductController;

Route::resource('products',ProductController::class);

If you upload images in the public disk you need to access them by using this command before starting the app ignore it otherwise: 

php artisan storage:link

You’d know that public disk uploaded files can be accessed by the public. These files are stored in storage/app/public by default in the local drive. We need to create a specific link if we want them to be accessed on the app. The link would be from public/storage to storage/app/public

We can start our app by using command php artisan serve and the API will become usable with the Postman using REST APIs. 

#5 React CRUD Frontend of Application 

Now we are building frontend of the app using React which is a JavaScript library used to build Facebook.

Now we are to create our react app by using command create-react-app and expenses-manager. Then we need to cd into the folder to install Axios, the HTTP client we will use for sending XMLHttpRequests, react-bootstrap, bootstrap and also sweetalert2 for the alert boxes for our lovely tiny app. Use this: 

npm install -g create-react-app 

create-react-app crud-react 

cd crud-react 

npm install axios react-bootstrap bootstrap 

npm install react-router-dom sweetalert2 --save

Once all these are done, we will go to src/app.js and import the following bootstrap core file to the top of the code:

import 'bootstrap/dist/css/bootstrap.css';

Now we’d create our app’s components that we will use in it. We have to go to src folder and create
a new components folder there. Now in that new components folder we need to create one more folder named product. Next we need to add the files we have mentioned below:

create.component.js
edit.component.js
list.component.js

We need to add this code shown here in our create.component.js file, check the code here. If you have followed
until now copy this code:

import React, { useState } from "react";
import Form from 'react-bootstrap/Form'
import Button from 'react-bootstrap/Button'
import Row from 'react-bootstrap/Row';
import Col from 'react-bootstrap/Col';
import axios from 'axios'
import Swal from 'sweetalert2';
import { useNavigate } from 'react-router-dom'

export default function CreateProduct() {
  const navigate = useNavigate();

  const [title, setTitle] = useState("")
  const [description, setDescription] = useState("")
  const [image, setImage] = useState()
  const [validationError,setValidationError] = useState({})

  const changeHandler = (event) => {
		setImage(event.target.files[0]);
	};

  const createProduct = async (e) => {
    e.preventDefault();

    const formData = new FormData()

    formData.append('title', title)
    formData.append('description', description)
    formData.append('image', image)

    await axios.post(`http://localhost:8000/api/products`, formData).then(({data})=>{
      Swal.fire({
        icon:"success",
        text:data.message
      })
      navigate("/")
    }).catch(({response})=>{
      if(response.status===422){
        setValidationError(response.data.errors)
      }else{
        Swal.fire({
          text:response.data.message,
          icon:"error"
        })
      }
    })
  }

  return (
    <div className="container">
      <div className="row justify-content-center">
        <div className="col-12 col-sm-12 col-md-6">
          <div className="card">
            <div className="card-body">
              <h4 className="card-title">Create Product</h4>
              <hr />
              <div className="form-wrapper">
                {
                  Object.keys(validationError).length > 0 && (
                    <div className="row">
                      <div className="col-12">
                        <div className="alert alert-danger">
                          <ul className="mb-0">
                            {
                              Object.entries(validationError).map(([key, value])=>(
                                <li key={key}>{value}</li>   
                              ))
                            }
                          </ul>
                        </div>
                      </div>
                    </div>
                  )
                }
                <Form onSubmit={createProduct}>
                  <Row> 
                      <Col>
                        <Form.Group controlId="Name">
                            <Form.Label>Title</Form.Label>
                            <Form.Control type="text" value={title} onChange={(event)=>{
                              setTitle(event.target.value)
                            }}/>
                        </Form.Group>
                      </Col>  
                  </Row>
                  <Row className="my-3">
                      <Col>
                        <Form.Group controlId="Description">
                            <Form.Label>Description</Form.Label>
                            <Form.Control as="textarea" rows={3} value={description} onChange={(event)=>{
                              setDescription(event.target.value)
                            }}/>
                        </Form.Group>
                      </Col>
                  </Row>
                  <Row>
                    <Col>
                      <Form.Group controlId="Image" className="mb-3">
                        <Form.Label>Image</Form.Label>
                        <Form.Control type="file" onChange={changeHandler} />
                      </Form.Group>
                    </Col>
                  </Row>
                  <Button variant="primary" className="mt-2" size="lg" block="block" type="submit">
                    Save
                  </Button>
                </Form>
              </div>
            </div>
          </div>
        </div>
      </div>
    </div>
  )
}

Now we need to add the following in edit.component.js file.

import React, { useEffect, useState } from "react";
import Form from 'react-bootstrap/Form'
import Button from 'react-bootstrap/Button';
import Row from 'react-bootstrap/Row';
import Col from 'react-bootstrap/Col';
import { useNavigate, useParams } from 'react-router-dom'
import axios from 'axios';
import Swal from 'sweetalert2';

export default function EditUser() {
  const navigate = useNavigate();

  const { id } = useParams()

  const [title, setTitle] = useState("")
  const [description, setDescription] = useState("")
  const [image, setImage] = useState(null)
  const [validationError,setValidationError] = useState({})

  useEffect(()=>{
    fetchProduct()
  },[])

  const fetchProduct = async () => {
    await axios.get(`http://localhost:8000/api/products/${id}`).then(({data})=>{
      const { title, description } = data.product
      setTitle(title)
      setDescription(description)
    }).catch(({response:{data}})=>{
      Swal.fire({
        text:data.message,
        icon:"error"
      })
    })
  }

  const changeHandler = (event) => {
		setImage(event.target.files[0]);
	};

  const updateProduct = async (e) => {
    e.preventDefault();

    const formData = new FormData()
    formData.append('_method', 'PATCH');
    formData.append('title', title)
    formData.append('description', description)
    if(image!==null){
      formData.append('image', image)
    }

    await axios.post(`http://localhost:8000/api/products/${id}`, formData).then(({data})=>{
      Swal.fire({
        icon:"success",
        text:data.message
      })
      navigate("/")
    }).catch(({response})=>{
      if(response.status===422){
        setValidationError(response.data.errors)
      }else{
        Swal.fire({
          text:response.data.message,
          icon:"error"
        })
      }
    })
  }

  return (
    <div className="container">
      <div className="row justify-content-center">
        <div className="col-12 col-sm-12 col-md-6">
          <div className="card">
            <div className="card-body">
              <h4 className="card-title">Update Product</h4>
              <hr />
              <div className="form-wrapper">
                {
                  Object.keys(validationError).length > 0 && (
                    <div className="row">
                      <div className="col-12">
                        <div className="alert alert-danger">
                          <ul className="mb-0">
                            {
                              Object.entries(validationError).map(([key, value])=>(
                                <li key={key}>{value}</li>   
                              ))
                            }
                          </ul>
                        </div>
                      </div>
                    </div>
                  )
                }
                <Form onSubmit={updateProduct}>
                  <Row> 
                      <Col>
                        <Form.Group controlId="Name">
                            <Form.Label>Title</Form.Label>
                            <Form.Control type="text" value={title} onChange={(event)=>{
                              setTitle(event.target.value)
                            }}/>
                        </Form.Group>
                      </Col>  
                  </Row>
                  <Row className="my-3">
                      <Col>
                        <Form.Group controlId="Description">
                            <Form.Label>Description</Form.Label>
                            <Form.Control as="textarea" rows={3} value={description} onChange={(event)=>{
                              setDescription(event.target.value)
                            }}/>
                        </Form.Group>
                      </Col>
                  </Row>
                  <Row>
                    <Col>
                      <Form.Group controlId="Image" className="mb-3">
                        <Form.Label>Image</Form.Label>
                        <Form.Control type="file" onChange={changeHandler} />
                      </Form.Group>
                    </Col>
                  </Row>
                  <Button variant="primary" className="mt-2" size="lg" block="block" type="submit">
                    Update
                  </Button>
                </Form>
              </div>
            </div>
          </div>
        </div>
      </div>
    </div>
  )
}

After edit.component.js file edit, we need to go to list.component.js and add the following code in that:

import React, { useEffect, useState } from 'react';
import { Link } from 'react-router-dom';
import Button from 'react-bootstrap/Button'
import axios from 'axios';
import Swal from 'sweetalert2'

export default function List() {

    const [products, setProducts] = useState([])

    useEffect(()=>{
        fetchProducts() 
    },[])

    const fetchProducts = async () => {
        await axios.get(`http://localhost:8000/api/products`).then(({data})=>{
            setProducts(data)
        })
    }

    const deleteProduct = async (id) => {
        const isConfirm = await Swal.fire({
            title: 'Are you sure?',
            text: "You won't be able to revert this!",
            icon: 'warning',
            showCancelButton: true,
            confirmButtonColor: '#3085d6',
            cancelButtonColor: '#d33',
            confirmButtonText: 'Yes, delete it!'
          }).then((result) => {
            return result.isConfirmed
          });

          if(!isConfirm){
            return;
          }

          await axios.delete(`http://localhost:8000/api/products/${id}`).then(({data})=>{
            Swal.fire({
                icon:"success",
                text:data.message
            })
            fetchProducts()
          }).catch(({response:{data}})=>{
            Swal.fire({
                text:data.message,
                icon:"error"
            })
          })
    }

    return (
      <div className="container">
          <div className="row">
            <div className='col-12'>
                <Link className='btn btn-primary mb-2 float-end' to={"/product/create"}>
                    Create Product
                </Link>
            </div>
            <div className="col-12">
                <div className="card card-body">
                    <div className="table-responsive">
                        <table className="table table-bordered mb-0 text-center">
                            <thead>
                                <tr>
                                    <th>Title</th>
                                    <th>Description</th>
                                    <th>Image</th>
                                    <th>Actions</th>
                                </tr>
                            </thead>
                            <tbody>
                                {
                                    products.length > 0 && (
                                        products.map((row, key)=>(
                                            <tr key={key}>
                                                <td>{row.title}</td>
                                                <td>{row.description}</td>
                                                <td>
                                                    <img width="50px" src={`http://localhost:8000/storage/product/image/${row.image}`} />
                                                </td>
                                                <td>
                                                    <Link to={`/product/edit/${row.id}`} className='btn btn-success me-2'>
                                                        Edit
                                                    </Link>
                                                    <Button variant="danger" onClick={()=>deleteProduct(row.id)}>
                                                        Delete
                                                    </Button>
                                                </td>
                                            </tr>
                                        ))
                                    )
                                }
                            </tbody>
                        </table>
                    </div>
                </div>
            </div>
          </div>
      </div>
    )
}

Let’s Add React Router to Our App Now

Now we need to use the React Router which is used for React routing as a standard libray. It will help us keep our user interface
at par and properly synced with the URL. Its APIs are packed with powerful features of lazy code loading, location change and much more that you will read below
while testing the app. At this point we are adding this react router to our app now by modifying app.js file in src directory. See the code here for better understanding:

import * as React from "react";
import Navbar from "react-bootstrap/Navbar";
import Container from "react-bootstrap/Container";
import Row from "react-bootstrap/Row";
import Col from "react-bootstrap/Col";
import "bootstrap/dist/css/bootstrap.css";

import { BrowserRouter as Router , Routes, Route, Link } from "react-router-dom";

import EditProduct from "./components/product/edit.component";
import ProductList from "./components/product/list.component";
import CreateProduct from "./components/product/create.component";

function App() {
  return (<Router>
    <Navbar bg="primary">
      <Container>
        <Link to={"/"} className="navbar-brand text-white">
          Basic Crud App
        </Link>
      </Container>
    </Navbar>

    <Container className="mt-5">
      <Row>
        <Col md={12}>
          <Routes>
            <Route path="/product/create" element={<CreateProduct />} />
            <Route path="/product/edit/:id" element={<EditProduct />} />
            <Route exact path='/' element={<ProductList />} />
          </Routes>
        </Col>
      </Row>
    </Container>
  </Router>);
}

export default App;

Now we have our React Laravel app ready to roll. We have done every thing that was needed for that. The final thing
we need to do is to test our React CRUD Applicaion by running it with npm run start.

This is how you can create your own laravel react js CRUD application in a short time. This will clear your concepts how to use RESTful APIs for handling requests and also use of Axios. Let us know in the comments for queries or errors you may encounter while doing it.