How to Create Laravel & React FullStack Application 2023 – Part 1: The Ultimate Tutorial for Beginners

React-Laravel-Full-stack-Application Part 1

How to Create Laravel & React FullStack Application 2023 – Part 1
How to Create Laravel & React FullStack Application 2023 – Part 1

Alright, Tech Geeks. I’m back with an all-new course on modern programming, this time covering the fundamentals of building a web app from beginning to end with Laravel and ReactJs.
I plan on utilizing Laravel for the RESTful API development on the backend and ReactJS for the front end.
We still have a lot to educate ourselves on. Here’s what you’ll discover if you read on:

Table of Contents

React Side
  • How to Work with the Context API
  • How to Use React Hooks.
  • How to Work React Router.
  • How to use protected routes.
  • How to work with multiple layouts.
  • How to connect to the APIs.
  • How to handle the authentication.
  • etc
Laravel Side
  • How to create Login and Registration API Endpoints
  • How to create basic CRUD endpoints.
  • How to create form request data.
  • How to create the resource.
  • etc.

Let’s Get Started !!

I will explain the procedure for developing our project before we begin. For a full comprehension of the undertaking.

  1. Create the Laravel Project
  2. Configure the Database and Seeders
  3. Create the React Project
  4. Clean the ReactJs application (Optional)
  5. Laravel API Development
  6. ReactJS Application Development
  7. Interaction
  8. Finish and Testing
plan

Before Start, Let’s Discuss What is React?

React Is a

React is a front-end JavaScript library that is both open-source and free to use for the creation of component-based user interfaces. Meta and an ecosystem of independent programmers and businesses provide ongoing support. With the help of frameworks like Next.js, developers can use React to create single-page, mobile, or server-rendered apps

1. Create a Laravel Application

Building a Laravel app is a priority. Here, I’m going to assume that you already have Laravel set up and running on your machine. If you don’t already have these, you can get Xampp and Composer and then return here. You can get by with a Mac or PC as long as you have PHP and the Laravel framework. OK, let’s keep going.                                    

My project is an Laravel-react-full-stack-application, and I’m naming it now. Name it whatever you like.

composer create-project laravel/laravel Laravel-react-full-stack-application

2. Configure the Database

In this case, you’re free to utilize whichever database you like. However, MySQL will serve as the database for this tutorial. To begin, open the .env file in your program. The DB_DATABASE and DB_PASSWORD values will be displayed after that. In this case, I’m using the name “laravelFullStack” for the database and not providing a password. (Since I don’t use a MySQL password on my development machine. If you’re going to utilize it, this is the place to do it.

3. Create the React Project

I expect to use Vite for that purpose.
Vite is a new front-end build tool that streamlines the process of optimising files for distribution to production and staging servers. In this case, the Node Package Manager can be of assistance. To do so, we must first download and install NodeJs. The ‘npm create vite‘ command is being used here. 

npm create vite

The next step is entering the project’s name. As for me, I’ll just type in “react” for the name. Put in whatever name you choose. Next, you’ll be prompted to choose the structure. It’s as easy as choosing JavaScript and the React Framework.
Note: This React project must be created within the Laravel project. Please find attached a directory tree for your convenience.

Project Folder Structure

4. Clean the ReactJs application

We started the React App with a few unnecessary files. Those should be eliminated in advance of construction.
This step is completely optional. This is because I value cleanliness and organization in my code, so I’m taking the initiative to ensure it stays that way.

So Let’s get started.

  • Before that, Install dependencies for the react application using this command – npm install
  • Navigate to src->app.jsx and remove all the codes by excluding <div className=”App”></div>
  • Delete the App.css file
  • Clear the index.css file
  • Delete src -> assets -> react.svg
  • Now you will see a cleaner design in the application

Now I’m going to install a requirement called react-router-dom. What is react-router-dom? It is a npm package that lets you use dynamic routes in a web app, which we will use in our app in the future. We can run the react-vite app on the development server with the ‘npm run dev’ command

npm install react-router-dom
npm run dev

5.ReactJS Application Development(frontend)

Create router.jsx inside the src folder. To navigate inside of the application we need to define each and every route inside of the application. So We need to have views to navigate.

Therefore, within the src folder, establish a directory named views, and then build a Login.JSX, Signup.jsx, Users.jsx, NotFound.jsx, and Dashboard.jsx can all be found within the view directory.

Login.JSX, Signup.JSX, Users.JSX, and NotFound.JSX, in addition to Dashboard.JSX, are all examples of functional components. If you are using vscode and the extension has been configured, you can utilize rafce and just click the tab key. If you are not using vscode, you will need to manually create the functional component template.
The router.jsx file should then be updated as shown below.

Following the completion of the update, you need also to update the main.jsx file. It is necessary to import the file named router.jsx there.

Router.jsx

import { createBrowserRouter, Navigate } from "react-router-dom";
import Dashboard from "./Dashboard.jsx";
import Login from "./views/Login";
import NotFound from "./views/NotFound";
import Signup from "./views/Signup";
import Users from "./views/Users";

const router = createBrowserRouter([
  {
    path: '/',
    element: <Navigate to="/users" />
  },
  {
    path: '/dashboard',
    element: <Dashboard />
  },
  {
    path: '/users',
    element: <Users />
  },
  {
    path: '/login',
    element: <Login />
  },
  {
    path: '/signup',
    element: <Signup />
  },
  {
    path: "*",
    element: <NotFound />
  }
])
export default router;

Main.jsx

import React from 'react'
import ReactDOM from 'react-dom/client'
import Dashboard from './Dashboard.jsx'
import './index.css'
import {RouterProvider} from "react-router-dom";
import router from "./router.jsx";

ReactDOM.createRoot(document.getElementById("root")).render(
  <React.StrictMode>
      <RouterProvider router={router} />
  </React.StrictMode>
);

After this, we are going to move on to an exciting new part of react.js. In preparation for our work with Layouts, a directory titled components should be created inside the src folder. In that location, you are able to construct the functional component as well as produce two jsx files, which are referred to as GuestLayout.jsx and DefautLayout.jsx.

DefaultLayout.jsx

import { Navigate, Outlet } from 'react-router-dom';
import { useStateContext } from '../contexts/ContextProvider';

const DefaultLayout = () => {
    const { user, token } = useStateContext()

    if (!token) {
        return <Navigate to="/login" />
    }
    return (
        <div>
            <Outlet />
        </div>
    )
}

export default DefaultLayout

GuestLayout.jsx

import { Navigate, Outlet } from 'react-router-dom';
import { useStateContext } from '../contexts/ContextProvider';

const GuestLayout = () => {
    const {token} = useStateContext();

    if (token) {
        return <Navigate to="/" />
    }
    return (
        <div>
            <Outlet />
        </div>
    )
}

export default GuestLayout

Then go to the DefaultLayout and GuestLayout and we can import outlet to there. Using <outlet>  we can render the child component.

So, we created two layouts successfully. now we need to update our router,jsx file like below.

import {createBrowserRouter, Navigate} from "react-router-dom";
import Dashboard from "./Dashboard.jsx";
import DefaultLayout from "./components/DefaultLayout";
import GuestLayout from "./components/GuestLayout";
import Login from "./views/Login";
import NotFound from "./views/NotFound";
import Signup from "./views/Signup";
import Users from "./views/Users";
import UserForm from "./views/UserForm";

const router = createBrowserRouter([
  {
    path: '/',
    element: <DefaultLayout/>,
    children: [
      {
        path: '/',
        element: <Navigate to="/users"/>
      },
      {
        path: '/dashboard',
        element: <Dashboard/>
      },
      {
        path: '/users',
        element: <Users/>
      },
    ]
  },
  {
    path: '/',
    element: <GuestLayout/>,
    children: [
      {
        path: '/login',
        element: <Login/>
      },
      {
        path: '/signup',
        element: <Signup/>
      }
    ]
  },
  {
    path: "*",
    element: <NotFound/>
  }
])

export default router;

Then create again a folder called contexts inside the src folder. Inside it create a new .jsx file called ContextProvider.jsx

ContextProvider.jsx

import {createContext, useContext, useState} from "react";

const StateContext = createContext({
  currentUser: null,
  token: null,
  notification: null,
  setUser: () => {},
  setToken: () => {},
  setNotification: () => {}
})

export const ContextProvider = ({children}) => {
  const [user, setUser] = useState({});
  const [token, _setToken] = useState(localStorage.getItem('ACCESS_TOKEN'));
  const [notification, _setNotification] = useState('');

  const setToken = (token) => {
    _setToken(token)
    if (token) {
      localStorage.setItem('ACCESS_TOKEN', token);
    } else {
      localStorage.removeItem('ACCESS_TOKEN');
    }
  }

  const setNotification = message => {
    _setNotification(message);

    setTimeout(() => {
      _setNotification('')
    }, 5000)
  }

  return (
    <StateContext.Provider value={{
      user,
      setUser,
      token,
      setToken,
      notification,
      setNotification
    }}>
      {children}
    </StateContext.Provider>
  );
}

export const useStateContext = () => useContext(StateContext);

To use the created contextProvider we need to import it to the main.jsx file. here is the updated code.

import React from 'react'
import ReactDOM from 'react-dom/client'
import Dashboard from './Dashboard.jsx'
import './index.css'
import {RouterProvider} from "react-router-dom";
import router from "./router.jsx";
import {ContextProvider} from './context/ContextProvider.jsx'

ReactDOM.createRoot(document.getElementById("root")).render(
  <React.StrictMode>
    <ContextProvider>
      <RouterProvider router={router} />
    </ContextProvider>
  </React.StrictMode>
);

Simply cut and paste the CSS file into your own website to replicate the look of mine. Changes can be made according to your whim if you desire a specific different design.

@import url('https://fonts.googleapis.com/css2?family=Open+Sans:ital,wght@0,300;0,400;0,500;0,600;0,700;0,800;1,300;1,400;1,500;1,600;1,700;1,800&display=swap');

* {
    box-sizing: border-box;
}
html, body, #root, #defaultLayout, #guestLayout {
    min-height: 100vh;
}

h1, h2, h3, h4, h5, h6, p {
    margin: 0;
}

body {
    font-family: 'Open Sans', sans-serif;
    margin: 0;
    padding: 0;
    font-size: 14px;
    color: #212121;
    background-color: #f6f6f6;
}

input {
    outline: 0;
    background: #ffffff;
    width: 100%;
    border: 2px solid #e6e6e6;
    margin: 0 0 15px;
    padding: 15px;
    box-sizing: border-box;
    font-size: 14px;
    transition: all 0.3s;
}

input:focus {
    border-color: #5b08a7;
}

.btn,
.btn-add,
.btn-edit,
.btn-delete {
    font-family: "Roboto", sans-serif;
    outline: 0;
    background: #5b08a7;
    border: 0;
    text-decoration: none;
    padding: 15px;
    color: #FFFFFF;
    font-size: 16px;
    -webkit-transition: all 0.3 ease;
    transition: all 0.3 ease;
    cursor: pointer;
}

.btn-block {
    width: 100%;
}

.btn-add,
.btn-edit,
.btn-delete{
    padding: 0.5rem 0.75rem;
    font-size: 14px;
    border-radius: 4px;
}
.btn-add {
    background-color: #00a762;
}
.btn-delete {
    background-color: #b72424;
}

.btn-logout {
    text-decoration: none;
    padding: 0.75rem 1.5rem;
    color: #212121;
    transition: all 0.3s;
    border-radius: 6px;
}
.btn-logout:hover {
    background-color: rgba(0, 0, 0, 0.1);
}

.btn:hover,
.btn:active,
.btn:focus {
    background: #5b08a7;
}

.text-center {
    text-align: center;
}

table {
    width: 100%;
    border-spacing: 0;
    border-collapse: collapse;
}

table > thead > tr > th {
    text-align: left;
    padding: 0.5rem 0.5rem;
    background-color: #efefef;
}

table > tbody > tr > td {
    padding: 0.5rem 0.5rem;
    border-bottom: 1px solid #efefef;
    white-space: nowrap;
}

.card {
    background-color: #FFF;
    border-radius: 0.5rem;
    box-shadow: 1px 1px 3px rgba(0, 0, 0, 0.1);
    padding: 1.25rem 1.5rem;
    margin-bottom: 1rem;
    margin-top: 0.5rem;
}

.alert {
    padding: 1rem;
    background-color: #ff4040;
    color: white;
    border-radius: 0.5rem;
    margin-bottom: 1rem;
}

.notification {
    position: fixed;
    right: 1rem;
    bottom: 1rem;
    z-index: 100;
    padding: 1rem 1.5rem;
    background-color: #00a762;
    color: white;
    border-radius: 0.5rem;
}

/* Login/Signup forms*/

.login-signup-form {
    height: 100vh;
    display: flex;
    justify-content: center;
    align-items: center;
}

.login-signup-form .form {
    width: 360px;
    position: relative;
    z-index: 1;
    background: #FFFFFF;
    max-width: 360px;
    padding: 34px;
    box-shadow: 0 0 2px 0 rgba(0, 0, 0, 0.1);
}

.login-signup-form .title {
    font-size: 20px;
    margin-bottom: 1rem;
    text-align: center;
}

.login-signup-form .form .message {
    margin: 15px 0 0;
    color: #b3b3b3;
    font-size: 16px;
    text-align: center;
}

.login-signup-form .form .message a {
    color: #5b08a7;
    text-decoration: none;
}

/* Login/Signup form */


#defaultLayout {
    display: flex;
}

#defaultLayout aside {
    width: 240px;
    background-color: #5b08a7;
    padding: 1rem
}

#defaultLayout aside > a {
    display: block;
    padding: 0.75rem 1rem;
    border-radius: 6px;
    color: white;
    text-decoration: none;
    transition: all 0.2s;
}

#defaultLayout aside > a:hover {
    background-color: rgba(0, 0, 0, 0.2);
}

#defaultLayout .content {
    flex: 1;
}

#defaultLayout header {
    height: 80px;
    padding: 2rem 3rem;
    background-color: white;
    box-shadow: 1px 1px 2px rgba(0, 0, 0, 0.2);
    display: flex;
    justify-content: space-between;
    align-items: center;
}

#defaultLayout main {
    padding: 2rem;
}

.animated {
    -webkit-animation-duration: 0.3s;
    animation-duration: 0.3s;
    -webkit-animation-fill-mode: both;
    animation-fill-mode: both;
}

.fadeInDown {
    -webkit-animation-name: fadeInDown;
    animation-name: fadeInDown;
}

@keyframes fadeInDown {
    0% {
        opacity: 0;
        transform: translateY(-20px);
    }
    100% {
        opacity: 1;
        transform: translateY(0);
    }
}

Now we going to implement the login and Signup form.

Signup.jsx

import {Link} from "react-router-dom";
import {createRef, useState} from "react";
import axiosClient from "../axios-client.js";
import {useStateContext} from "../context/ContextProvider.jsx";

export default function Signup() {
  const nameRef = createRef()
  const emailRef = createRef()
  const passwordRef = createRef()
  const passwordConfirmationRef = createRef()
  const {setUser, setToken} = useStateContext()
  const [errors, setErrors] = useState(null)

  const onSubmit = ev => {
    ev.preventDefault()

    const payload = {
      name: nameRef.current.value,
      email: emailRef.current.value,
      password: passwordRef.current.value,
      password_confirmation: passwordConfirmationRef.current.value,
    }
    axiosClient.post('/signup', payload)
      .then(({data}) => {
        setUser(data.user)
        setToken(data.token);
      })
      .catch(err => {
        const response = err.response;
        if (response && response.status === 422) {
          setErrors(response.data.errors)
        }
      })
  }

  return (
    <div className="login-signup-form animated fadeInDown">
      <div className="form">
        <form onSubmit={onSubmit}>
          <h1 className="title">Signup for Free</h1>
          {errors &&
            <div className="alert">
              {Object.keys(errors).map(key => (
                <p key={key}>{errors[key][0]}</p>
              ))}
            </div>
          }
          <input ref={nameRef} type="text" placeholder="Full Name"/>
          <input ref={emailRef} type="email" placeholder="Email Address"/>
          <input ref={passwordRef} type="password" placeholder="Password"/>
          <input ref={passwordConfirmationRef} type="password" placeholder="Repeat Password"/>
          <button className="btn btn-block">Signup</button>
          <p className="message">Already registered? <Link to="/login">Sign In</Link></p>
        </form>
      </div>
    </div>
  )
}

Login.jsx

import {Link} from "react-router-dom";
import axiosClient from "../axios-client.js";
import {createRef} from "react";
import {useStateContext} from "../context/ContextProvider.jsx";
import { useState } from "react";

export default function Login() {
  const emailRef = createRef()
  const passwordRef = createRef()
  const { setUser, setToken } = useStateContext()
  const [message, setMessage] = useState(null)

  const onSubmit = ev => {
    ev.preventDefault()

    const payload = {
      email: emailRef.current.value,
      password: passwordRef.current.value,
    }
    axiosClient.post('/login', payload)
      .then(({data}) => {
        setUser(data.user)
        setToken(data.token);
      })
      .catch((err) => {
        const response = err.response;
        if (response && response.status === 422) {
          setMessage(response.data.message)
        }
      })
  }

  return (
    <div className="login-signup-form animated fadeInDown">
      <div className="form">
        <form onSubmit={onSubmit}>
          <h1 className="title">Login into your account</h1>

          {message &&
            <div className="alert">
              <p>{message}</p>
            </div>
          }

          <input ref={emailRef} type="email" placeholder="Email"/>
          <input ref={passwordRef} type="password" placeholder="Password"/>
          <button className="btn btn-block">Login</button>
          <p className="message">Not registered? <Link to="/signup">Create an account</Link></p>
        </form>
      </div>
    </div>
  )
}

We are going to start using axios now. What exactly is the term “axios“? In a word. Axios is what we turn to when we need to send a request from the front end to the back end. Which is an extremely well-liked client for HTTP requests. In order to accomplish that, we can run the command npm install axios.

npm install axios

Following that, we will need to develop axios-client.js in addition to the Laravel API. Because this is taking too much time, we will continue it in the next one.

Laravel & React Full Stack Application Part 2