You may have seen tutorials that help you build a simple React.js app that use some third-party API or a Node.js server as a backend. You could also use Laravel for this purpose and integrate it with React.
As a backend framework, Laravel actually offers a tool to help you do this, called Inertia. Here’s what the docs say about it:
It bridges the gap between your Laravel application and your modern Vue or React frontend, allowing you to build full-fledged, modern frontends using Vue or React while leveraging Laravel routes and controllers for routing, data hydration, and authentication — all within a single code repository.
But what if you don’t want to use such a tool? And instead, you just want to use React.js as a frontend library and have a simple Laravel-powered backend?
Well, in this article, you will learn how to use React.js with Laravel as a backend by building a draggable tasklist app.
By the end of this article, you will have a single-page app for managing tasks, which will look like this:
In this article, we’ll create a dynamic page that will have a list of tasks, each of which will belong to a specific project. This way, the user will be able to select a project, and only the tasks of the selected project will be shown on the page. The user can also create a new task for the current project, as well as edit, delete and reorder tasks by dragging and dropping them.
Table of Contents:
Before following along, it would be helpful to have a basic understanding of React.js, Laravel, and familiarity with fundamental web development concepts.
You’ll need the following tools for the app that we’ll build in this article:
- PHP 8.1 or above (run
php -vto check the version)
- Composer (run
composerto check that it exists)
- Node.js 18 or above (run
node -vto check the version)
- MySQL 5.7 or above (run
mysql --versionto check if it exists, or follow the docs)
Additional (optional) tools that you can use:
- Postman – a program with a UI for testing the API routes
- curl – a CLI command for testing the API routes
We’ll start by building out the backend, and then move to the frontend.
The Backend: How to Install Laravel
First, if you don’t have it already, you’ll need to install the Laravel framework on your local machine.
One way to install Laravel is by using a popular dependency manager for PHP called Composer. Here’s the command to do so:
composer create-project laravel/laravel tasklist
This will install the latest stable version of Laravel in your local machine (currently it’s version 10).
The tasklist in the command is the app’s root folder name, which you can set to whatever you want.
At this point, you can
cd into the project’s folder and run the backend app without needing to have a virtual server set up:
cd tasklist/ && php artisan serve
artisan in the above command is a CLI tool included in Laravel. It exists at the root of your Laravel application as the
artisan script file, which provides a number of helpful commands that can assist you while you build your application.
We’ll use it in this article often.
http://127.0.0.1:8000 in your browser to see the default page. It should look like this:
How to Create Models and Migrations
Now, let’s create Project and Task models, as well as migrations for them.
Models are the way your app entities should be defined, and migrations are like schema definitions for storing the records of those entities in the database.
You can create model and migration files manually as well as generate them using the
php artisan make:model Project -m
php artisan make:model Task -m
-m argument will automatically generate a migration file using the provided model name.
Keep the command execution sequence as it is, so the Project’s migration later can run before the Task’s migration.
This is important, because the
tasks tables should have a one-to-many relationship (1-N): each task will refer to a single project, or, in other words, each project can have multiple tasks.
$fillable fields and the
task() relationship method as below:
By default, the
$timestamps public property has a
true value, which is coming from the parent
Model class. This means that the
updated_at columns in your database table will be maintained automatically by Eloquent (the ORM included in Laravel).
But you can customize it by changing its value to
false. We don’t need to have
updated_at fields in the
projects table, so we’ll set the
project() relationship method, and
created accessor. An accessor in Laravel is like a function between the database and your code, that can access the already fetched database record and modify it.
Above, in the
Task model, there is an accessor called
created. For having an accessor, we have the
created field in the
$appends array, and also a public function
get<WordsInCamelCase>Attribute() function there is logic that will run to modify the already fetched database record.
In our case the
getCreatedAttribute() function will return a human-friendly and readable time difference between the current time and the given time. For example, “3 mins ago” or “4 months ago”.
Now that the models are ready, let’s set up the migrations.
First, set up a migration for the
Then set up a migration for the
tasks table has a foreign key
project_id, which is a reference to the
projects table. So it’s a good practice to update the
down() method too, to be sure that the
project_id foreign will be dropped before dropping the actual
There is also a
priority field, which will be a non-nullable natural number for ordering the tasks. And optionally, you can add a soft deletion feature to the
How to Create Seeders
Now we need to add dummy data to the
tasks tables. To seed some data in the database, you can use Laravel seeders. This allows you to create dummy data to use in your database.
If you want to read more about how this works, you can check out the docs here.
Laravel provides a way to generate those files by using
make:seeder artisan command:
php artisan make:seeder ProjectsSeeder
php artisan make:seeder TasksSeeder
So with the above commands, you’ll have
database/seeders/TasksSeeder.php files created.
At first, you’ll need to set up the
ProjectsSeeder to add a few projects to the
projects table. Then you can set up the
TasksSeeder to add tasks to the
As I mentioned at the beginning, each task will belong to a specific project. From a relational database perspective, this means that each entry in the
tasks table will link to a specific entry in the
projects table. Here’s the importance of having a
project_id foreign key in the
tasks table to be able to relate each task to a specific project as well as retrieve the specific project’s tasks.
You can imagine the database structure by looking at the following visuals:
Using the example below, you can generate 3 projects:
Next, set up
TasksSeeder. You’ll run all the seeder files after setting them up, and they will run one by one. That being said, at this point your
ProjectsSeeder is ready to create a few projects.
By imagining it, the next step will be generating the tasks, each of them will have a reference to one of the already existing projects by its
Using the example below, you can generate 10 projects:
The above code just grabs all the project IDs, then randomly chooses a project for each task. In the end, it inserts all the tasks into the
As you may have noticed, we’re inserting
$tasks into the
tasks table using the
insert() static function, which allows us to insert all the items into the database table with a single query.
But it has a downside as well: it doesn’t manage
updated_at fields. That’s why there’s a need to set up those fields manually by assigning them the same
Now, when you have all the seeders ready, you need to register them into the
How to Connect to the MySQL Database
Before running migrations and seeds, create a MySQL database and set up the appropriate credentials in the
.env file. If there is not a
.env, then create it and paste the
.env.example file’s content into it.
After setting up the database credentials, you’ll have these kinds of environment variables:
After setting up environment variables, optimize the cache:
php artisan optimize
Now you’ll be able to create
tasks tables in the MySQL database, setup their structure, and add initial records with a single command:
php artisan migrate:fresh --seed
In the above command, the
migrate:fresh argument will drop all tables from the database. Then it will execute the
migrate command, which will run your migrations to create
tasks tables appropriately.
--seed argument, it will run
TasksSeeder after the migrations. That being said, it will empty your database for you, and will create all the tables and fill all the necessary dummy data.
After running the command, you’ll have these kinds of database records:
Now let’s create a controller and a service classes to manage all the task features, such as listing, creating, updating, deleting, and reordering the tasks.
At first, use the below command to generate a controller.
php artisan make:controller TaskController
In order not to place all the code in the controller, you can keep only the main logic in it, and move the other logic implementations to another class file.
Those classes are generally called services, and using service implementations in a controller method is called service injection (it comes from the term dependency injection).
One of the main advantages of using services is that it helps you create a maintainable codebase.
You can inject your service class into the controller’s construction method as an argument, so after each controller execution (when a controller’s
__construct() method runs) you can initialize an object of service. This means that you can access your service’s functions right in your controller.
Now, let’s create two separate service classes, which will be used in the
Manually create a
app/Services/ProjectService.php service class, which will be responsible for the project-related logic.
The second service class will be the
app/Services/TaskService.php, which will be responsible for doing task manipulations:
In the above
TaskService class, you’ll use the following functions in the
- list: fetches tasks for a given project ID, including the related project, and orders them by priority.
- getById: retrieves a specific task by its ID, including the related project.
- store: stores a new task, calculating the priority based on existing tasks for the same project.
- update: updates an existing task by its ID.
- delete: deletes a task by its ID and adjusts the priorities of remaining tasks in the same project.
- reorder: changes the priorities of tasks within a project, (handles soft delete as well with
deleted_at IS NULL).
Web and API Routes in Laravel
Now you can add routes to test the methods you’ve already written. In this project, we have a stateless app on the frontend which requests API routes for getting JSON data, so it will follow RESTful principles (GET, POST, PUT, DELETE methods). Only the initial HTML page will be retrieved as a whole web page.
So now, set up a route in
routes/web.php for the initial single-page:
Set up API routes in
routes/api.php like this:
We have all the API routes in the
routes/api.php instead of the
routes/web.php because in the web.php file, all the routes by default are CSRF protected. So, in a stateless app, usually you won’t need that – that’s why api.php was invented in Laravel.
As you can see, there is a “task” prefix for all API routes. It’s optional to have a prefix, but it’s just a good practice. And for the specific API routes, there are regex validations for accepting only natural numbers as project IDs.
Don’t forget to refresh route caches after the above changes. It’s important to remember that Laravel (version 10 in this case) reads routes from the cached
bootstrap/cache/routes-v7.php file, and they won’t be updated automatically right after your changes. It just generates one if it hasn’t cached yet.
Use the below command to refresh Laravel caches as well as the route caches:
php artisan optimize
Validation Requests in Laravel
Before writing controller methods, you’ll need to add some validation request files. You can do that manually or by just using the
php artisan make:request Task/CreateTaskRequest
php artisan make:request Task/ListTasksRequest
php artisan make:request Task/ReorderTasksRequest
php artisan make:request Task/UpdateTaskRequest
After creating them, you’ll need to set up validation rules for each request.
Validation rules in Laravel are a way to describe how to expect to get incoming HTTP data. If the data matches the rules, then it passes the validation – otherwise, Laravel will return a failure.
Laravel provides a set of rules you can use to check incoming data. A field of the incoming request can have multiple rules.
One way to write validation rules for a single field is concatenating those rules by a “|” character.
Below are the validation rules for creating a new task:
Below is the validation rule for listing project tasks:
Below are the validation rules for tasks reordering:
Below are the validation rules for updating a task:
Don’t forget to return
true in the
authorize() method in all validation classes:
This function is usually designed to determine if the user is authorized to make the request. As we don’t use authentication as well as authorization stuff in the app, it should return
true for all the cases.
How to Write a Controller that Uses Services
As the last step in the backend part, it’s time to write controller methods for each API route, which will use service functions.
As you can see in the
TaskServiceis injected into the constructor method as an argument. In the constructor body, an instance of the
TaskServiceclass is created, and the
$taskServiceproperty is initialized. So in the custom methods, you’ll be able to access that
$taskServiceand its functions.
indexmethod is for returning the HTML.
- All the other custom methods (
reorder) are using the
TaskServicefunctions through the already initialized
$taskServiceproperty. So, all the logic implementation goes to the service, and this way, you just call a service function and return the response.
How to Test the API Routes
At this point, you can test the API routes by requesting them via Postman or any similar tool. Just run (or rerun) the backend:
php artisan serve
Here’s the published Postman collection with all the requests.
Instead of using Postman, you can use a command line tool such as curl right from your terminal.
Below are all the sample commands that you can run to test out the API routes:
- Create a new task for a specific project:
curl --location '127.0.0.1:8000/api/tasks?project_id=1' \
--header 'Content-Type: application/json' \
curl --location 'http://127.0.0.1:8000/api/tasks?project_id=1'
curl --location 'http://127.0.0.1:8000/api/tasks/1'
curl --location --request PUT 'http://127.0.0.1:8000/api/tasks/11' \
--header 'Content-Type: application/json' \
"title": "Title edited",
"description": "Description edited"
curl --location --request PUT 'http://127.0.0.1:8000/api/tasks' \
--header 'Content-Type: application/json' \
curl --location --request DELETE 'http://127.0.0.1:8000/api/tasks/11'
In the below screenshot, there is an example of getting project tasks by its ID using the curl command (at the bottom right):
The Frontend: How to Install the Packages
Now it’s time to switch to the frontend. We’ll use TypeScript for React.js. After completing this part, you’ll be able to integrate React.js (with Vite) in your Laravel app.
First, make sure you have Node.js version 18 or above by using this command:
Install these necessary npm packages:
npm i react-dom dotenv react-beautiful-dnd react-responsive-modal react-toastify @vitejs/plugin-react
react-domis a library from the React team for rendering React components in the DOM (Document Object Model)
dotenvis for loading environment variables from the
.envfile into the process environment
react-beautiful-dndis a library from Atlassian for creating drag-and-drop interfaces with animations
react-responsive-modalis for creating simple and responsive modal dialogs
react-toastifyis for displaying notifications or toasts
@vitejs/plugin-reactis a plugin for the Vite build tool that enables seamless integration of React with fast development and optimized production builds
Install the development dependencies with this command:
npm i -D @types/react-dom @types/react-beautiful-dnd
@types/react-domis TypeScript type definitions for the
@types/react-beautiful-dndis TypeScript type definitions for the
How to Configure Vite.js
As Laravel v10 already has
vite.config.js, you’ll want to set up any React-related stuff there. Or if you still don’t have this file, create one like this:
As you can see in the Vite configuration file, there is a reference to the
resources/react/app.tsx, which will be the entry point for Laravel to use React resources.
For the initial HTML page, create a
resources/views/tasks/index.blade.php blade file, so all the frontend assets will be injected there in the
div with ID
As you can see in the blade file, there is a
$projects variable passed from the backend. It’s the whole project data that will be used to filter tasks in the frontend.
React.js – Initial Integration
In this article, we’ll just have a basic React.js app working with Laravel.
At first, it’s a good idea to delete unnecessary resources, like default
resources/react/app.tsx file like this:
resources/react folder will be the root directory for all the upcoming React stuff.
index.css with some temporary content:
Also create a
Main.tsx with some temporary content:
To check the result in the browser, make sure you have backend running and build the assets via the
npm run build
Or, if you want to watch React file changes and automatically build assets, you can keep this command running:
npm run dev
npm run commands above refer to
vite, which builds the assets.
You can see this by checking the
package.json file, “scripts” field:
"build": "vite build"
Now you can open http://localhost:8000 to see the initial rendered view:
How to Add CSS
Now, once you’ve set up Vite and have React integrated into your Laravel app, you can work on the React part.
We won’t spend too much time on styles, so you can paste this CSS into your
Later you’ll attach the
index.css file in your main component.
A Service for the API Requests
As you did in backend, here in the frontend you also can move all the logic implementations into a different file, so your code will be more readable and maintainable. We can name that file
utils.ts, as there will be utilities in it we need.
Before that, just create
axiosConfig.ts for the global Axios configuration, which you’ll use in
Using the above setup, you can be sure that all the HTTP requests will have the
For example, if you use
axiosConfig.get('/example'), it will send a GET request to the
/api/example. This is an optional configuration, but it’s a recommended way to have non-repetitive code.
As you’ll have a few use cases for sending HTTP requests to the server, you can have separate utilities file for those operations:
- Create a new task for a project
- Update a task
- List project’s tasks
- Delete a task
- Reorder project’s tasks
So below is the
In the above file, you’ll find the following functions:
- getErrorMessage: Returns the error message if the input is an instance of Error – otherwise, converts it to a string.
- getTasks: Retrieves tasks for a given project ID using Axios. Displays an error toast if the project ID is missing or if the API request is unsuccessful.
- reorderTasks: Sends a PUT request to reorder tasks within a project based on start and end positions. Displays a success or error toast based on the API response.
- editTask: Sends a PUT request to update task information. Validates that the task has an ID and a title before making the request. Displays a success or error toast based on the API response.
- deleteTask: Sends a DELETE request to delete a task by its ID. Displays a success or error toast based on the API response.
- createTask: Sends a POST request to create a new task for a given project ID. Validates that the project ID is present, and the task has a title before making the request. Displays a success or error toast based on the API response.
Now, since you have utilities ready, in the
resources/react/components folder, you can create the components you need to use in your
SelectProject.tsx, which will be responsible for choosing the current project:
SelectProject component renders a dropdown menu allowing the user to select a project. When a project is selected, it updates the state with the selected project ID, fetches tasks for that project using the
getTasks utility function, and updates the state with the retrieved tasks, providing dynamic interaction with project selection and task loading.
TaskList.tsx, which will be responsible for rendering the project’s tasks and for their drag and drop manipulations:
TaskList component utilizes the
react-beautiful-dnd library to implement a draggable task list. It renders a list of tasks, allowing users to drag and drop tasks to reorder them, with drag-and-drop functionality triggering a function (
handleDragEnd) that updates the task order both visually and in the backend using the
reorderTasks utility function.
Task.tsx, which will be responsible for a single Task from a list:
Task component represents a single task item. It displays task details including the project name, creation time, and title, and provides buttons to trigger actions such as editing and deleting the task, with corresponding handlers (
AddTaskForm.tsx, which will be responsible for the task form for adding tasks to the current selected project:
AddTaskForm component provides a form for adding new tasks. It includes input fields for the task title and description, with buttons to clear the form or submit the task creation, and it utilizes the
createTask utility function to handle the task creation process, triggering a reload of tasks upon success.
ModalEdit.tsx, which will be responsible for the modal popup for editing and submitting changes to a task:
ModalEdit component displays a modal for editing task details. It includes input fields for modifying the task title and description, and buttons to close the modal or save the changes, using the
editTask utility function to handle the task editing process and triggering a reload of tasks upon successful editing.
ModalDelete.tsx, which will be responsible for submitting a task deletion:
ModalDelete component displays a modal for confirming the deletion of a task. It provides options to either cancel the deletion or proceed with deleting the task, utilizing the
deleteTask utility function and triggering a reload of tasks upon successful deletion.
And lastly, set up the
Main.tsx by using the above-defined components.
Main component is a central component that managing project and task-related functionalities. It includes modals for editing and deleting tasks, a task list with dynamic updates, a project selection dropdown, and a form for adding new tasks, leveraging state management and utility functions for smooth user interaction.
At this point, all the components are ready to interact with each other. So you can build the frontend assets and run the server:
npm run build && php artisan serve
http://127.0.0.1:8000 you’ll get this kind of result:
Now you can easily integrate React.js into your Laravel app without using any additional Laravel tools (like Inertia). And as a result, you can continue to maintain your Laravel app to build more scalable APIs with its authentication and other stuff.
So, this can be just an example app for your next full-stack Laravel and React.js project.
With this article, you built a single-page, full-stack Tasklist app using React.js (with TypeScript) with Vite.js as frontend technologies, Laravel as a backend framework, and
react-beautiful-dnd package for having draggable items.
Now you know how to manually integrate React.js in your Laravel app and maintain it.
You can find the complete code of the project here on my GitHub⭐, where I actively publicize much of my work about various modern technologies.
For more information, you can visit my website: boolfalse.com
Feel free to share this article. 😇