Welcome folks today in this blog post we will be building a rest crud api in laravel 8 using mysql and consume it using react.js & axios in bootstrap 4. All the full source code of the application is shown below.
Get Started
In order to get started you need to initialize a new laravel project inside the command line. For this you need to install the laravel cli on command line using composer library.
Installing Laravel CLI Using Composer
Now we will be installing laravel cli using composer as shown below
composer global require laravel/installer
After installing it we can now use the laravel command to create a laravel project as shown below
laravel new crudapp
cd new crudapp
Directory Structure of Laravel 8 App
As you can see we are showing the directory structure of laravel in which we are showing the files and folders as shown below
Configuring MySQL Database in Laravel 8
Now we will be configuring the mysql database inside the laravel 8 project. For this you need to edit the .env
file of the project as shown below
As you can see we are adding the details of mysql database and localhost and also port number. The default username and password for mysql database is root and empty password. And also host is localhost and port number is 3306.
1 2 3 4 5 6 |
DB_CONNECTION=mysql DB_HOST=127.0.0.1 DB_PORT=3306 DB_DATABASE=crud_react_laravel DB_USERNAME=root DB_PASSWORD= |
Installing Xammp Control Panel
Now for the mysql database you need to install the xammp control panel and then install it on your machine. You can download it here.
After installation you need to start the Apache server and the mysql database as well as shown below
So now you need to create the mysql database inside the phpmyadmin section of xammp control panel as shown below
Creating Migrations,Model & Controller
Now guys we will creating the migrations, model and controller at the same time using the below command as shown below
php artisan make:model Product -mcr
As you can see in the above command we are using the artisan command to create the model, migrations and also controller as well. So the name of the model is Product.
And now we need to copy paste the below code inside the migrations table of Products as shown below
1 2 3 4 5 6 7 8 9 10 |
public function up() { Schema::create('products', function (Blueprint $table) { $table->bigIncrements('id'); $table->string('title'); $table->text('description'); $table->text('image'); $table->timestamps(); }); } |
As you can see we are copying pasting the code inside the up() function. Inside the above code we are adding the five fields. And first of all we are creating the products table and inside that we are adding the title,description and image of the product as well. And lastly we are also having the timestamps.
Migrating the Database
Now if we migrate the database by using the migrations we have defined earlier on. To migrate the database you need to execute the below command as shown below
php artisan migrate
As soon as you execute the above command the tables will be created automatically as shown below. This also includes the Products table as we defined. In the products table we have the same schema and columns that we have defined earlier on.
Now guys we will adding the source code inside the products models file which is present inside the app folder as shown below
1 2 3 4 5 6 7 8 9 10 11 12 |
<?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']; } |
As you can see we are adding the three columns that we defined inside the products table in this file as shown above in the code
Adding the Controller to Project
Now we will be adding the controller code for this project. Controllers are nothing but it contains the code which will directly perform the crud operations and directly interact with the mysql database as shown below in the functions
As you can see we will editing the ProductController.php file which is present inside the controllers folder which is present inside the http folder and it is present inside the app folder as shown above
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 |
<?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 the specified resource in storage. * * @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!!' ]); } } } |
As you can see we have defined all the five methods of the CRUD operation. The methods are as follows
- Get All Products: In this request we get all the records using the GET Request to the /api/products route
- Add New Product: In this request we will creating New Product Using the POST Request to the /api/products route
- Get Specific Product: In this request we will getting the specific product information using GET Request to the /api/product/${id} here we are passing the id of the product to get the information in the url itself
- Updating the Product: In this request we will be updating a specific product using it’s id using the POST Request to the /api/product/${id} here we are passing the id of the product to update the information of the product
- Delete the Product: In this request we will be deleting a specific product using it’s id using the DELETE Request to the /api/products/${id} here we are passing the id of the product to be deleted
And also for storing the images of the product we will be storing all the images inside the public folder of the project as shown below
And after completion of all the CRUD operations we are sending the JSON response back to the client telling the status of the request whether it was successful or not
Adding Controller File in Route
Now guys we will be adding the controller file that we defined earlier on to the actual routes file. Where we will add a route and then map the controller file to that request. So whenever user enters that url your controller file will serve up the contents. For this you need to modify the routes folder of your project and inside it you need to modify the api.php
file as shown below
As you can see we are adding a resource or route called as products and then as a second argument we are passing the controller to handle this route.
Granting Permission to Upload Images in Public Folder
Now guys before running the laravel project you need to grant the permission to upload image inside the public folder. This can be done using the below command as shown below
php artisan storage:link
Running the Laravel Project
Now guys we can run the backend api of Laravel using the below command
php artisan serve
This will start the application at port 8000.
Testing the API Routes in Postman
Now first of all we will test the API using a free tool called Postman. First of all we will check to get all the products using the GET Request to the route /api/products as shown below
As you can see it is returning array of products. Each product having four properties
Creating the User in Postman
Now we will be using the POST Request to create a new Product record inside the mysql database as shown below
As you can see we are passing the formData values and then we are getting a JSON response back to the client which is product created successfully. And for passing the image we are using the file attribute inside the formData option as shown above
Updating the User in Postman
Now we will be using the POST Request again to modify an existing Product record inside the mysql database using it’s id as shown below. Inside the form-data we are passing an additional field called _method which is equal to PATCH which is required for updation.
Deleting the User in Postman
Now guys lastly we will be removing the product from the database. For this you need to write the below request
As you can see we are deleting the specific user using it’s id. And the we are deleting the user and sending the json response back to the client.
Making the React.js Frontend
Now we will we consuming this backend api that we have created earlier on. For this we need to create the React.js project using the below command
mkdir frontend
cd frontend
npx create-react-app frontend
cd frontend
Installing Dependencies
Now we will be installing dependencies for this react.js project which are as follows
- Bootstrap
- Axios
- React Router Dom
- SweetAlert2
npm i react-bootstrap bootstrap axios
npm i react-router-dom sweetalert2
Now we need to include the bootstrap library inside the App.js
file as shown below
1 2 3 4 5 6 7 8 9 10 11 12 |
import 'bootstrap/dist/css/bootstrap.css'; import './App.css'; function App() { return ( <div className="App"> </div> ); } export default App; |
As you can see we are importing the bootstrap 4 cdn at the very top of the file as shown above.
Now we will be adding some source code inside the App.js
file as shown below
src/App.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
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"; <span class="hljs-keyword">import</span> EditProduct <span class="hljs-keyword">from</span> <span class="hljs-string">"./components/edit.component"</span>; <span class="hljs-keyword">import</span> CreateProduct <span class="hljs-keyword">from</span> <span class="hljs-string">"./components/create.component"</span>; import { BrowserRouter as Router , Routes, Route, Link } from "react-router-dom"; import List from "./components/list.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; |
As you can see in this code we are adding the dependencies of bootstrap and including all the widgets that we will be using for styling the component. And also we are importing the react router dom library and importing the Routes and route. And we have the Routes inside that we have the single route which will redirect the user to the home route. Here we will be rendering all the products. And also we have the routes for Edit Product and also Create Product to actually update and create the product. And for the components we will be rendering the CreateProduct component for the create request. And we will be rendering the UpdateProduct component for the update request.
Rendering All the Products
Now we will be rendering all the products coming from the API. For doing that you need to first of all create a components folder inside the root directory as shown below
components/list.component.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 |
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> ) } |
As you can see in the above code we are first of all fetching all the products by making a simple GET request to the route http://localhost:8000/api/products. We are making this request inside the useEffect hook. It automatically occurs whenever you loads the component. After that we get the response and after that we are setting the products using the hook function setProducts() function.
And after that inside the jsx we are first of all computing the length of the array and then we are using the map method to loop all the elements of the products array and then we are rendering all the product properties which are title, description and the image of the product. And also we have two buttons edit and delete as shown below
As you can see we are rendering all the products inside the html5 table. And we are rendering title, description and image. And also we have the edit and delete buttons.
As you can see if you click the delete button we are showing the sweetalert2 modal to ask user whether they want to delete the product or not. In the delete function we are receiving the id parameter and in this function we are using the filter function to remove the product using it’s id.
Creating the Products
Now we will be writing code for creating the actual product using the html5 form in which we will have the option for the user to choose the title,description and the image of the product that can be uploaded in the html5 form. So when they click the add Product button then that new product is inserted to table successfully. And we also see sweetalert2 modal after the successful insertion.
src/components/create.component.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 |
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> ) } |
As you can see we have the actual bootstrap 4 form in which we have three input fields which is for title,description and image file input. And also we have the save button to save the product. And when we submit the form we are executing the createProduct() function. Inside that function we are preventing the auto submission of the form and then we are initializing the form data object and inserting values inside this form data object. After that we are making a POST Request to the http://localhost:8000/api/routes and here we are passing the actual raw json data. And when the record is successfully created we are seeing the modal message.
As you can see in the above code we also have the validation for the input fields. We are showing custom error messages when user leaves the fields empty or don’t write anything or write wrong values. We are catching all the errors using the catch statement and initializing all the errors inside the SetErrors() hook method.
After successful insertion we are redirecting or navigating the user back to the home age using the navigate() method
Updating the User Using it’s ID
Now we will be updating the user using it’s id. So now we need to create the edit.component.ts
file inside the root directory as shown below
src/components/edit.component.ts
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 |
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 EditProduct() { 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> ) } |
First of all inside this update file we are first of all extracting the values from the product which is title and description by first of all making the POST Request to the GET route having some id passed. After you do this title and description we are making two way data binded. And this function we are setting the value of title and description which we need to .
And then guys as you all know inside this request we are passing the id parameter in the address bar only. And also we are using the form data to append all the information such as updated title, description and the image uploaded by the user.