<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/"
    xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/" version="2.0">
    <channel>
        
        <title>
            <![CDATA[ San B - freeCodeCamp.org ]]>
        </title>
        <description>
            <![CDATA[ Browse thousands of programming tutorials written by experts. Learn Web Development, Data Science, DevOps, Security, and get developer career advice. ]]>
        </description>
        <link>https://www.freecodecamp.org/news/</link>
        <image>
            <url>https://cdn.freecodecamp.org/universal/favicons/favicon.png</url>
            <title>
                <![CDATA[ San B - freeCodeCamp.org ]]>
            </title>
            <link>https://www.freecodecamp.org/news/</link>
        </image>
        <generator>Eleventy</generator>
        <lastBuildDate>Mon, 18 May 2026 22:34:28 +0000</lastBuildDate>
        <atom:link href="https://www.freecodecamp.org/news/author/boolfalse/rss.xml" rel="self" type="application/rss+xml" />
        <ttl>60</ttl>
        
            <item>
                <title>
                    <![CDATA[ How to Set Up a Custom Email with Cloudflare and Mailgun ]]>
                </title>
                <description>
                    <![CDATA[ As a software engineer, you may consider having a professional email account along with your own website, like "info@example.com". But this may cost a certain amount that you'll not be willing to pay. But do you know you can do it for free? There is ... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-set-up-custom-email/</link>
                <guid isPermaLink="false">66ba2af24d935175898a7069</guid>
                
                    <category>
                        <![CDATA[ cloudflare ]]>
                    </category>
                
                    <category>
                        <![CDATA[ email ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ San B ]]>
                </dc:creator>
                <pubDate>Mon, 15 Apr 2024 13:49:03 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2024/04/boolfalse-gmail-manage-custom-email.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>As a software engineer, you may consider having a professional email account along with your own website, like "<em>info@example.com</em>". But this may cost a certain amount that you'll not be willing to pay.</p>
<p>But do you know you can do it for free? There is actually a way to do it, and besides the fact that having the professional email account is free, it will help you be more efficient, reliable and secure in your daily work.</p>
<p>In this article, you'll learn how to create and set up your own email address using Cloudflare and Mailgun to manage emails in Gmail. This means that you can send and receive emails directly in your Gmail inbox.</p>
<p>I've done this already for personal use and have taken screenshots of the entire process that you'll see in this article. So I'll share all the necessary steps you need to follow to set up your own email.</p>
<p>Let's figure out what you need to have before you start, what you are going to do, and how it will work.</p>
<h2 id="heading-what-you-need-to-have-before-you-start">What you need to have before you start</h2>
<p>I assume that you already have a domain, let's call it "<em>yourdomain.com</em>". Specifically, you need to have accessibility to connect your domain with Cloudflare and setup DNS records there. A classic example of that is having a domain on some domain registrar (like GoDaddy, Namecheap), and adding your domain to Cloudflare by setting DNS records provided by Cloudflare on your domain registrar account.</p>
<p>Adding a domain to Cloudflare involves updating your domain's DNS nameservers to point to Cloudflare's nameservers. Once the domain is added, Cloudflare acts as an intermediary for web traffic, providing security features like DDoS protection, firewall, and SSL encryption, as well as performance enhancements through caching and content optimization.</p>
<p>If you haven't done that yet, here's the official <a target="_blank" href="https://www.youtube.com/watch?v=7hY3gp_-9EU">video on YouTube</a> on how to connect your domain to Cloudflare.</p>
<p>Additionally, Cloudflare manages DNS records for your domain, allowing you to control how traffic is routed and ensuring reliable delivery of services like email.<br>So, our work in this article will be focusing exactly on that: how to setup your domain on Cloudflare Email.</p>
<p><a target="_blank" href="https://blog.cloudflare.com/email-routing-leaves-beta/">Cloudflare Email</a> has been one of the services of Cloudflare since 2021, which can be used for free (as of now, at least).</p>
<p>The second assumption is that you have Gmail account, and you have access to its email settings. Simply, if you just have a regular "<em>youremail@gmail.com</em>" email, which isn't under the control of any administrator, then you have nothing to worry about. We'll explore and work on email settings later on.</p>
<h2 id="heading-what-you-are-going-to-do">What you are going to do</h2>
<p>In simple words, you're going to create a custom email like "<em>something@yourdomain.com</em>", which you can use to send and receive emails using Gmail's platform. So you will be receiving and reading emails sent to "<em>something@yourdomain.com</em>" in Gmail, as well as sending emails from that custom email using Gmail.</p>
<p>You'll use Cloudflare Email for the email routing, and Mailgun's SMTP server for sending emails.</p>
<h2 id="heading-how-it-will-work">How it will work</h2>
<p>When composing an email from Gmail with the sender set as "<em>something@yourdomain.com</em>", Gmail utilizes Mailgun's SMTP server through the provided credentials, transmitting the email. Mailgun then processes the message and forwards it to the recipient's email server, likely involving DNS lookups to find the recipient's server.</p>
<p>Emails sent to "<em>something@yourdomain.com</em>" are received by Cloudflare's email servers, configured via MX records in the domain's DNS settings. Cloudflare stores the received emails in the associated account, accessible through Gmail, which periodically connects to Cloudflare's servers (using IMAP or POP3 protocols) to retrieve new messages, enabling seamless access to incoming emails.</p>
<h2 id="heading-email-routing-on-cloudflare">Email Routing on Cloudflare</h2>
<blockquote>
<p>Cloudflare Email Routing is designed to simplify the way you create and manage email addresses, without needing to keep an eye on additional mailboxes. With Email Routing, you can create any number of custom email addresses to use in situations where you do not want to share your primary email address, such as when you subscribe to a new service or newsletter. Emails are then routed to your preferred email inbox, without you ever having to expose your primary email address. (<a target="_blank" href="https://developers.cloudflare.com/email-routing/">Cloudflare Docs</a>)</p>
</blockquote>
<p>Sign in to your Cloudflare account and navigate to the Dashboard.<br>Choose and click on the desired website. For me it's "<em>boolfalse.com</em>", as I want to create a custom email like "<em>email@boolfalse.com</em>".</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/04/01-dashboard.png" alt="Image" width="600" height="400" loading="lazy">
<em>Cloudflare: Websites</em></p>
<p>Navigate to <strong>Email Routing</strong> for the selected website.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/04/02-email-routing.png" alt="Image" width="600" height="400" loading="lazy">
<em>Cloudflare: Email Routing</em></p>
<p>If you don't have email routing configured, you may see something similar to the screenshot above. Click "Get started". You may be able to create your own address to receive emails and take action.</p>
<p>We'll skip this without creating our own address because we'll do it manually.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/04/03-skip-custom-address.png" alt="Image" width="600" height="400" loading="lazy">
<em>Cloudflare: Custom Email</em></p>
<p>By default, email routing is disabled, so you need to enable it. Click the link to navigate to the <strong>Email Routing</strong> page.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/04/04-enable-email-routing.png" alt="Image" width="600" height="400" loading="lazy">
<em>Cloudflare: Email Routing</em></p>
<p>Submit it by clicking "Enable Email Routing".</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/04/05-email-dns-records-enable-email-routing.png" alt="Image" width="600" height="400" loading="lazy">
<em>Cloudflare: Enable Email Routing</em></p>
<p>You need to have three MX and one TXT records:</p>
<ul>
<li>Type: <em><strong>MX</strong></em>; Name: <em><strong>@</strong></em>; Mail Server: <em><strong>route1.mx.cloudflare.net</strong></em>; TTL: <strong><em>Auto</em></strong>; Priority: <em><strong>69</strong></em></li>
<li>Type: <em><strong>MX</strong></em>; Name: <em><strong>@</strong></em>; Mail Server: <em><strong>route2.mx.cloudflare.net</strong></em>; TTL: <strong><em>Auto</em></strong>; Priority: <strong><em>99</em></strong></li>
<li>Type: <em><strong>MX</strong></em>; Name: <strong><em>@</em></strong>; Mail Server: <em><strong>route3.mx.cloudflare.net</strong></em>; TTL: <strong><em>Auto</em></strong>; Priority: <strong><em>40</em></strong></li>
<li>Type: <em><strong>TXT</strong></em>; Name: <em><strong>@</strong></em>; TTL: <strong><em>Auto</em></strong>; Content: <strong>_v=spf1 include:<em>spf.mx.cloudflare.net ~all</em></strong></li>
</ul>
<p>You can see them at the bottom of the <strong>Email Routing</strong> page.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/04/06-required-dns-records.png" alt="Image" width="600" height="400" loading="lazy">
<em>Cloudflare: DNS records for Email Routing</em></p>
<p>So, as already said, in the left menu, go to "DNS" -&gt; "Records" and add the following records there.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/04/06-dns-records-added-2.png" alt="Image" width="600" height="400" loading="lazy">
<em>Cloudflare: DNS records added</em></p>
<p>After creating these records, go to the <strong>Email Routing</strong> page again.</p>
<p>Here, you only need to have the records you just created. So if you have any other records, just delete them.</p>
<p>For example, I already had an unnecessary entry there that I should delete.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/04/07-unnecessary-dns-records.png" alt="Image" width="600" height="400" loading="lazy">
<em>Cloudflare: existing records for Email Routing</em></p>
<p>Submit to delete existing unnecessary records.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/04/08-delete-existing-dns-records.png" alt="Image" width="600" height="400" loading="lazy">
<em>Cloudflare: deleting unnecessary records</em></p>
<p>After removing unnecessary DNS records, you will see only the ones you need there.</p>
<p>You will now be able to enable email routing by clicking the "Add records and enable" button.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/04/09-enabling-email-routing.png" alt="Image" width="600" height="400" loading="lazy">
<em>Cloudflare: Enable Email Routing</em></p>
<p>After enabling it you should see something like this:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/04/10-email-routing-enabled.png" alt="Image" width="600" height="400" loading="lazy">
<em>Cloudflare: Email DNS records configured</em></p>
<h2 id="heading-how-to-create-a-custom-email-on-cloudflare">How to Create a Custom Email on Cloudflare</h2>
<p>Now go to the <strong>Routes</strong> tab and create an email by clicking the "Create address" button.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/04/11-email-routing-routes-tab.png" alt="Image" width="600" height="400" loading="lazy">
<em>Cloudflare: Email Routing (enabled)</em></p>
<p>In this example, we'll create "<em>email@boolfalse.com</em>" email address, by adding "<em>email</em>" as a custom address, and a destination email address, where I'll be able to receive emails.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/04/12-creating-email-address.png" alt="Image" width="600" height="400" loading="lazy">
<em>Cloudflare: Email Routing</em></p>
<p>You should see a notification about that.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/04/13-email-address-created.png" alt="Image" width="600" height="400" loading="lazy">
<em>Cloudflare: creating a custom email</em></p>
<p>You should also get an email for confirming this action.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/04/14-getting-confirmation-email.png" alt="Image" width="600" height="400" loading="lazy">
<em>Verifying the destination email</em></p>
<p>Go on and verify the email address.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/04/15-verify-email-address.png" alt="Image" width="600" height="400" loading="lazy">
<em>Verify email address</em></p>
<p>Once you've verified the email address, you may get this page:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/04/16-email-address-verified.png" alt="Image" width="600" height="400" loading="lazy">
<em>Cloudflare: custom email address is verified</em></p>
<p>You will probably get an email that you've verified your domain with Mailgun:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/04/36-mailgun-domain-verified-2.png" alt="Image" width="600" height="400" loading="lazy">
<em>Notification about custom email address verification</em></p>
<h2 id="heading-how-to-receive-emails-in-the-custom-email">How to Receive Emails in the Custom Email</h2>
<p>Now, your email address is activated, and you can see that here:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/04/17-email-address-activated.png" alt="Image" width="600" height="400" loading="lazy">
<em>Cloudflare: custom email address is active</em></p>
<p>At this point you can send emails to the custom email you just set up. In this case, it's "<em>email@boolfalse.com</em>".</p>
<p>Below is a test email sent from a different email.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/04/18-test-email-sending-1.png" alt="Image" width="600" height="400" loading="lazy">
<em>Testing email receiving</em></p>
<p>You'll receive a test email to the custom email.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/04/19-test-email-received.png" alt="Image" width="600" height="400" loading="lazy">
<em>Test email has been received</em></p>
<h2 id="heading-mailgun-adding-new-domain">Mailgun: Adding New Domain</h2>
<p>You can now successfully receive emails, but you can't send emails from that custom email yet.</p>
<p>So, it's time to switch to the mail service provider. In our case, it will be <a target="_blank" href="https://www.mailgun.com/">Mailgun</a>.<br>To do this, you just need to register and attach the card to your Mailgun account. After activating your account with the card attached, you can set up a domain for your email.</p>
<p>You don't have to worry about the card, because Mailgun does not charge for limited quantities. I think the amount it gives is quite suitable for a free package.<br>You can find the price packages in detail <a target="_blank" href="https://www.mailgun.com/pricing/">here</a>.</p>
<p>Go to <strong>Sending</strong> -&gt; <strong>Domains</strong> page, and click the "Add New Domain" button.</p>
<p>In our case it will be "<em>mg.boolfalse.com</em>", as Mailgun recommends that to be able to send emails from your root domain, that is: "<em>email@boolfalse.com</em>".</p>
<p>You should see that recommendation on the right in below image:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/04/24-mailgun-adding-domain.png" alt="Image" width="600" height="400" loading="lazy">
<em>Mailgun: create a new domain</em></p>
<p>You can also select the domain region and DCIM key length, but you can leave everything as default.<br>I will leave DCIM key length as 1024 and "US" as a domain region.</p>
<p>After creating the domain, you may be shown some tips on how to verify your domain.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/04/23-add-new-domain-2.png" alt="Image" width="600" height="400" loading="lazy">
<em>Mailgun: adding a new domain</em></p>
<p>Mailgun will give you two TXT records, two MX records and one CNAME record to add to your provider.</p>
<ul>
<li>Type: <em><strong>TXT</strong></em>; Name: _<strong>mailto._domainkey.mg.boolfalse.com</strong>_; TTL: <strong><em>Auto</em></strong>; Content: <strong><em></em></strong></li>
<li>Type: <em><strong>TXT</strong></em>; Name: <em><strong>mg.boolfalse.com</strong></em>; TTL: <strong><em>Auto</em></strong>; Content: <strong><em>v=spf1 include:mailgun.org ~all</em></strong></li>
<li>Type: <em><strong>MX</strong></em>; Name: <em><strong>mg.boolfalse.com</strong></em>; Mail Server: <em><strong>mxa.mailgun.org</strong></em>; TTL: <strong><em>Auto</em></strong>; Priority: <em><strong>10</strong></em></li>
<li>Type: <em><strong>MX</strong></em>; Name <strong><em>mg.boolfalse.com</em></strong>; Mail Server: <em><strong>mxb.mailgun.org</strong></em>; TTL: <strong><em>Auto</em></strong>; Priority: <strong><em>10</em></strong></li>
<li>Type: <strong><em>CNAME</em></strong>; Name: <strong><em>email</em></strong>; Target: <strong><em>mailgun.org</em></strong>; TTL: <strong><em>Auto</em></strong>; Proxy Status: <strong><em>On</em></strong></li>
</ul>
<p>In our case, we will add them to Cloudflare.</p>
<p>Below is the first TXT record:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/04/27-mailgun-dns-record-1-new.png" alt="Image" width="600" height="400" loading="lazy">
<em>Mailgun: first TXT record for a new domain</em></p>
<p>Below is the second TXT record:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/04/29-mailgun-dns-record-2-new.png" alt="Image" width="600" height="400" loading="lazy">
<em>Mailgun: second TXT record for a new domain</em></p>
<p>Below is the first MX record:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/04/30-mailgun-dns-record-3.png" alt="Image" width="600" height="400" loading="lazy">
<em>Mailgun: first MX record for a new domain</em></p>
<p>Below is the second MX record:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/04/31-mailgun-dns-record-4.png" alt="Image" width="600" height="400" loading="lazy">
<em>Mailgun: second MX record for a new domain</em></p>
<p>After you've added two TXT and two MX records, you can check and verify them by clicking the "Verify DNS Records" button.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/04/32-mailgun-checking-dns-records.png" alt="Image" width="600" height="400" loading="lazy">
<em>Mailgun: checking TXT and MX records for a new domain</em></p>
<p>Lastly, add CNAME record.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/04/33-mailgun-dns-record-5-2.png" alt="Image" width="600" height="400" loading="lazy">
<em>Mailgun: adding CNAME record for a new domain</em></p>
<p>You may see a warning icon on the left of the CNAME record. You don't need to worry about that. Here's what <a target="_blank" href="https://developers.cloudflare.com/ssl/edge-certificates/additional-options/total-tls/error-messages">official documentation</a> says about it:</p>
<blockquote>
<p>If you recently <a target="_blank" href="https://developers.cloudflare.com/fundamentals/setup/manage-domains/add-site/">added your domain</a> to Cloudflare - meaning that your zone is in a <a target="_blank" href="https://developers.cloudflare.com/dns/zone-setups/reference/domain-status/">pending state</a> - you can often ignore this warning.<br>Once most domains becomes <strong>Active</strong>, Cloudflare will automatically issue a Universal SSL certificate, which will provide SSL/TLS coverage and remove the warning message.</p>
</blockquote>
<p>After adding a CNAME record, you can check and verify it again by clicking the second "Verify DNS Records" button.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/04/34-mailgun-checking-dns-records.png" alt="Image" width="600" height="400" loading="lazy">
<em>Mailgun: checking CNAME record for a new domain</em></p>
<p>If you have added all 5 records on the Cloudflare successfully, after clicking the verifying button, Mailgun will automatically redirect you to the <strong>Overview</strong> page.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/04/36-mailgun-verified-1.png" alt="Image" width="600" height="400" loading="lazy">
<em>Mailgun: 2 TXT, 2 MX and 1 CNAME records added for a new domain</em></p>
<p>It means you're ready to add a Sending API key on Mailgun.</p>
<h2 id="heading-mailgun-sending-api-key-amp-smpt-user">Mailgun: Sending API key &amp; SMPT User</h2>
<p>Go to <strong>Sending</strong> -&gt; <strong>Domain Settings</strong> page. Choose the <strong>Sending API keys</strong> tab at the top. You probably won't see any API keys there. You just need to create a new Sending API key. </p>
<p>Click "Add sending key" from the top right corner, and in the pop-up, fill the name of the key you're about to create.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/04/37-mailgun-create-sending-api-key-1.png" alt="Image" width="600" height="400" loading="lazy">
<em>Mailgun: creating a Sending API key</em></p>
<p>After pressing "Create sending key", you'll get the secret API key that you need to copy and save somewhere safe. After saving the key, you can just close the pop-up.</p>
<p>You should see the created key listed:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/04/38-mailgun-sending-api-key-created.png" alt="Image" width="600" height="400" loading="lazy">
<em>Mailgun: Sending API key created</em></p>
<p>You also need to create a new SMTP user in the Mailgun dashbaord.<br>Go to <strong>Sending</strong> -&gt; <strong>Domain Settings</strong> page. Choose the <strong>SMTP credentials</strong> tab at the top and click the "Add new SMTP user" button on the top left corner. It will open up a pop-up. </p>
<p>Type user credentials there. In our case I'll create a user with the name "email". It will be like a login for your email on Gmail.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/04/41-mailgun-create-smtp-user.png" alt="Image" width="600" height="400" loading="lazy">
<em>Mailgun: creating SMTP user</em></p>
<p>Once you create an SMTP user in Mailgun, you'll see it listed and a password for that user will be generated automatically. To get this password, copy it by clicking the "Copy" button in the pop-up notification in the lower right corner.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/04/42-mailgun-smtp-user-created.png" alt="Image" width="600" height="400" loading="lazy">
<em>Mailgun: SMTP user created</em></p>
<p>Keep this in a safe place for future use. You will need this login and password to authenticate on Gmail.</p>
<p>You are now ready to set up email configurations with your email provider. In our case, we will do this in Gmail.</p>
<p>Open your Gmail account in your desktop browser and go to Settings by clicking the settings icon in the top right corner and click the "See all settings" button.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/04/39-gmail-settings-page.png" alt="Image" width="600" height="400" loading="lazy">
<em>Mailgun: new domain is verified</em></p>
<h2 id="heading-gmail-authentication-with-mailgun-smtp-server">Gmail Authentication with Mailgun SMTP Server</h2>
<p>In the Gmail settings page choose the <strong>Accounts and Import</strong> tab and click on the "Add another email address" from the "Send mail as" section:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/04/40-gmail-add-another-email-2.png" alt="Image" width="600" height="400" loading="lazy">
<em>Gmail: Settings</em></p>
<p>It will open a pop-up for the authentication. Use the login and the password you just got by creating an SMTP user on Mailgun. Make sure to fill out the credentials correctly.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/04/43-gmail-add-smtp-user.png" alt="Image" width="600" height="400" loading="lazy">
<em>Gmail: authenticate a new user using a created SMTP server on Mailgun</em></p>
<p>Submit the form by clicking the "Add Account" button. It'll probably ask you to save the username/password in your browser. It's up to you.</p>
<p>And the last important thing here: it'll ask you to verify adding an account.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/04/44-gmail-verify-account.png" alt="Image" width="600" height="400" loading="lazy">
<em>Gmail: authentication confirmation for a new user</em></p>
<p>For the verification, the confirmation email will be sent to your primary email.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/04/45-gmail-confirmation-code.png" alt="Image" width="600" height="400" loading="lazy">
<em>Gmail: authentication verification email</em></p>
<p>You can either use the confirmation code to verify it using the pop-up window or simply follow the link provided in the confirmation email.</p>
<p>In this case, we'll click on a link which will open the page, where you'll be asked to confirm. Click on "Confirm" and simply close the previously opened pop-up window without worrying.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/04/47-gmail-adding-user-confirmed.png" alt="Image" width="600" height="400" loading="lazy">
<em>Gmail: verifying the authentication</em></p>
<p>Now you're ready to send and receive emails from the custom email you just created.</p>
<p>For sending an email from the custom email, you just need to choose that email as a sender email:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/04/49-gmail-send-emails-from-custom-email.png" alt="Image" width="600" height="400" loading="lazy">
<em>Gmail: sending emails</em></p>
<p><strong>That's it!</strong></p>
<p>An additional thing that may be useful to you is that you can set the custom email address you just created as the default address for sending emails from Gmail.</p>
<p>You can set this on the settings page in the "Send mail as" section:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/04/48-gmail-another-email-default.png" alt="Image" width="600" height="400" loading="lazy">
<em>Gmail: Settings (default sender)</em></p>
<p>I hope this guide will be a good resource for setting up your custom email.</p>
<h2 id="heading-conclusion"><strong>Conclusion</strong></h2>
<p>In this article, you learned how to set up your own email to manage emails in Gmail using Cloudflare Email and Mailgun.</p>
<p>In conclusion, it is worth noting that the choice of tools is not mandatory, other tools can be used instead, but the basic idea and logic will be similar.</p>
<p>You can check out my website at: <a target="_blank" href="https://boolfalse.com/"><strong>boolfalse.com</strong></a></p>
<p>Feel free to share this article. 😇</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Build a Real-Time Chat App with Laravel Reverb ]]>
                </title>
                <description>
                    <![CDATA[ In March of 2024, Laravel 11 was released. And with it arrived a new tool in the Laravel ecosystem: Laravel Reverb. Reverb is a separate open-source package that's a first-party WebSocket server for Laravel applications. It helps facilitate real-time... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/laravel-reverb-realtime-chat-app/</link>
                <guid isPermaLink="false">66ba2af546ffa10237d2bc86</guid>
                
                    <category>
                        <![CDATA[ Laravel ]]>
                    </category>
                
                    <category>
                        <![CDATA[ PHP ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ San B ]]>
                </dc:creator>
                <pubDate>Wed, 27 Mar 2024 18:13:43 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2024/03/laravel-reverb-react-chat-boolfalse.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>In March of 2024, <a target="_blank" href="https://blog.laravel.com/laravel-11-now-available">Laravel 11 was released</a>. And with it arrived a new tool in the Laravel ecosystem: <a target="_blank" href="https://reverb.laravel.com/">Laravel Reverb</a>.</p>
<p>Reverb is a separate open-source package that's a first-party WebSocket server for Laravel applications. It helps facilitate real-time communication between client and server.</p>
<p>Before this new package, Laravel had event broadcasting, but basically it didn't have a built-in way to set up a self-hosted WebSocket server. Fortunately, Reverb now gives us that option.</p>
<p>Laravel Reverb has a few key features: it's written in PHP, it's fast, and and it's scalable. It was developed in particular to be horizontally scalable. </p>
<p>Reverb basically allows you to run an application on a single server – but if the application starts to outgrow that server, you can add multiple additional servers. Then those servers can all communicate with each other to distribute the messages between themselves.</p>
<p>In this article you will learn how to build a real-time chat application using Laravel Reverb. This will let you easily implement WebSocket communications between your backend and frontend. </p>
<p>For a frontend technology, you can use anything you want – but in this case we'll use React.js with the Vite.js build tool.</p>
<p>By the end of this article, you'll have a full-stack, real-time app in your local machine, which will work like this:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/03/article-video-1.gif" alt="Image" width="600" height="400" loading="lazy">
<em>Demo of the app showing messaging between two logged in users</em></p>
<h2 id="heading-table-of-contents"><strong>Table of Contents</strong></h2>
<ul>
<li><a class="post-section-overview" href="#heading-prerequisites">Prerequisites</a></li>
<li><a class="post-section-overview" href="#heading-general-steps">General Steps</a></li>
<li><a class="post-section-overview" href="#heading-how-to-install-laravel">How to Install Laravel</a></li>
<li><a class="post-section-overview" href="#heading-how-to-create-the-model-and-migration">How to Create the Model and Migration</a></li>
<li><a class="post-section-overview" href="#heading-how-to-add-authentication">How to Add Authentication</a></li>
<li><a class="post-section-overview" href="#heading-how-to-set-up-routes">How to Set Up Routes</a></li>
<li><a class="post-section-overview" href="#heading-how-to-set-up-a-laravel-event">How to Set Up a Laravel Event</a></li>
<li><a class="post-section-overview" href="#heading-how-to-set-up-a-laravel-queue-job">How to Set Up a Laravel Queue Job</a></li>
<li><a class="post-section-overview" href="#heading-how-to-write-the-controller-methods">How to Write the Controller Methods</a></li>
<li><a class="post-section-overview" href="#heading-how-to-install-laravel-reverb">How to Install Laravel Reverb</a></li>
<li><a class="post-section-overview" href="#heading-how-to-setup-websocket-channels">How to Setup WebSocket Channels</a></li>
<li><a class="post-section-overview" href="#heading-how-to-customize-laravel-views">How to Customize Laravel Views</a></li>
<li><a class="post-section-overview" href="#lets-work-on-the-front-end">Let's Work on the Front End</a></li>
<li><a class="post-section-overview" href="#heading-running-the-application">Running the Application</a></li>
<li><a class="post-section-overview" href="#heading-useful-reverb-resources">Useful Reverb Resources</a></li>
<li><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></li>
</ul>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>You'll need the following tools for the app that we'll build in this article:</p>
<ul>
<li><strong>PHP</strong>: version 8.2 or above (run <code>php -v</code> to check the version)</li>
<li><strong>Composer</strong> (run <code>composer</code> to check that it exists)</li>
<li><strong>Node.js</strong>: version 20 or above (run <code>node -v</code> to check the version)</li>
<li><strong>MySQL</strong>: version 5.7 or above (run <code>mysql --version</code> to check if it exists, or follow the <a target="_blank" href="https://dev.mysql.com/doc/refman/5.7/en/linux-installation.html">docs</a> to install it)</li>
</ul>
<h2 id="heading-general-steps">General Steps</h2>
<p>The main steps in this article will be:</p>
<ul>
<li>Installing Laravel 11.</li>
<li>Adding authentication flow to it (authentication scaffolding). Laravel provides a basic starting point for this using Bootstrap with React / Vue.</li>
<li>Installing Reverb.</li>
<li>React.js components and event listening in the frontend.</li>
</ul>
<h2 id="heading-how-to-install-laravel">How to Install Laravel</h2>
<p>To start, install Laravel 11 by using the composer command:</p>
<pre><code class="lang-shell">composer create-project laravel/laravel:^11.0 laravel-reverb-react-chat &amp;&amp; cd laravel-reverb-react-chat/
</code></pre>
<p>At this point, you can check out the app by running the <code>serve</code> command:</p>
<pre><code class="lang-shell">php artisan serve
</code></pre>
<h2 id="heading-how-to-create-the-model-and-migration">How to Create the Model and Migration</h2>
<p>You can generate a model and a migration for the messages by using this single command:</p>
<pre><code class="lang-shell">php artisan make:model -m Message
</code></pre>
<p>Then you'll need to set up the Message's model with the following code:</p>
<pre><code class="lang-php"><span class="hljs-meta">&lt;?php</span>

<span class="hljs-keyword">namespace</span> <span class="hljs-title">App</span>\<span class="hljs-title">Models</span>;

<span class="hljs-keyword">use</span> <span class="hljs-title">Illuminate</span>\<span class="hljs-title">Database</span>\<span class="hljs-title">Eloquent</span>\<span class="hljs-title">Factories</span>\<span class="hljs-title">HasFactory</span>;
<span class="hljs-keyword">use</span> <span class="hljs-title">Illuminate</span>\<span class="hljs-title">Database</span>\<span class="hljs-title">Eloquent</span>\<span class="hljs-title">Model</span>;
<span class="hljs-keyword">use</span> <span class="hljs-title">Illuminate</span>\<span class="hljs-title">Database</span>\<span class="hljs-title">Eloquent</span>\<span class="hljs-title">Relations</span>\<span class="hljs-title">BelongsTo</span>;

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Message</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">Model</span>
</span>{
    <span class="hljs-keyword">use</span> <span class="hljs-title">HasFactory</span>;

    <span class="hljs-keyword">public</span> $table = <span class="hljs-string">'messages'</span>;
    <span class="hljs-keyword">protected</span> $fillable = [<span class="hljs-string">'id'</span>, <span class="hljs-string">'user_id'</span>, <span class="hljs-string">'text'</span>];

    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">user</span>(<span class="hljs-params"></span>): <span class="hljs-title">BelongsTo</span> </span>{
        <span class="hljs-keyword">return</span> <span class="hljs-keyword">$this</span>-&gt;belongsTo(User::class, <span class="hljs-string">'user_id'</span>);
    }

    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getTimeAttribute</span>(<span class="hljs-params"></span>): <span class="hljs-title">string</span> </span>{
        <span class="hljs-keyword">return</span> date(
            <span class="hljs-string">"d M Y, H:i:s"</span>,
            strtotime(<span class="hljs-keyword">$this</span>-&gt;attributes[<span class="hljs-string">'created_at'</span>])
        );
    }
}
</code></pre>
<p>As you can see, there's a <code>getTimeAttribute()</code> accessor that will format the message creation timestamp into a human-readable date and time format. It will show it on the top of each message in the chat box.</p>
<p>Next, set up the migration for the <code>messages</code> database table with this code:</p>
<pre><code class="lang-php"><span class="hljs-meta">&lt;?php</span>

<span class="hljs-keyword">use</span> <span class="hljs-title">Illuminate</span>\<span class="hljs-title">Database</span>\<span class="hljs-title">Migrations</span>\<span class="hljs-title">Migration</span>;
<span class="hljs-keyword">use</span> <span class="hljs-title">Illuminate</span>\<span class="hljs-title">Database</span>\<span class="hljs-title">Schema</span>\<span class="hljs-title">Blueprint</span>;
<span class="hljs-keyword">use</span> <span class="hljs-title">Illuminate</span>\<span class="hljs-title">Support</span>\<span class="hljs-title">Facades</span>\<span class="hljs-title">Schema</span>;

<span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">Migration</span>
</span>{
    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">up</span>(<span class="hljs-params"></span>): <span class="hljs-title">void</span> </span>{
        Schema::create(<span class="hljs-string">'messages'</span>, <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">Blueprint $table</span>) </span>{
            $table-&gt;id();
            $table-&gt;foreignId(<span class="hljs-string">'user_id'</span>)-&gt;constrained();
            $table-&gt;text(<span class="hljs-string">'text'</span>)-&gt;nullable();
            $table-&gt;timestamps();
        });
    }

    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">down</span>(<span class="hljs-params"></span>): <span class="hljs-title">void</span> </span>{
        Schema::dropIfExists(<span class="hljs-string">'messages'</span>);
    }
};
</code></pre>
<p>This migration creates a <code>messages</code> table in the database. The table contains columns for an auto-incrementing primary key (<code>id</code>), a foreign key (<code>user_id</code>) referencing the <code>id</code> column of the <code>users</code> table, a <code>text</code> column for storing the message content, and <code>timestamps</code> to automatically track the creation and modification times of each record. </p>
<p>The migration also includes a rollback method (<code>down()</code>) to drop the <code>messages</code> table if needed.</p>
<p>In this article, we'll use the MySQL database, but you can go with SQLite as the default one if you prefer. Just make sure to set up your database credentials in <code>.env</code> file correctly:</p>
<pre><code class="lang-env">DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=database_name
DB_USERNAME=username
DB_PASSWORD=password
</code></pre>
<p>After setting up the environment variables, optimize the cache:</p>
<pre><code class="lang-shell">php artisan optimize
</code></pre>
<p>Run migrations to recreate the database tables as well as to add the <code>messages</code> table:</p>
<pre><code class="lang-shell">php artisan migrate:fresh
</code></pre>
<h2 id="heading-how-to-add-authentication">How to Add Authentication</h2>
<p>Now, you can add authentication scaffolding to your app. You can use Laravel's UI package to import some asset files. First you'll need to install the appropriate package:</p>
<pre><code class="lang-shell">composer require laravel/ui
</code></pre>
<p>Then import the React-related assets into the application:</p>
<pre><code class="lang-shell">php artisan ui react --auth
</code></pre>
<p>It may ask to overwrite the <code>app/Http/Controllers/Controller.php</code>, and you can go ahead and allow it:</p>
<pre><code class="lang-shell">The [Controller.php] file already exists. Do you want to replace it? (yes/no) [no]
</code></pre>
<p>This will do all of the authentication scaffolding compiled and installed, including routes, controllers, views, vite configurations, and a simple React-specific sample.<br>At this point, you're just one step away from the app being ready to go.</p>
<p><strong>NOTE:</strong> Make sure you have <strong>Node.js</strong> (with <strong>npm</strong>) version 20 or above installed. You can check that by running the <code>node -v</code> command. Otherwise, just go ahead and install it using the <a target="_blank" href="https://nodejs.org/en/download">official page</a>.</p>
<pre><code class="lang-shell">npm install &amp;&amp; npm run build
</code></pre>
<p>The command above will install NPM packages and build frontend assets. Now you can start the Laravel application and check out your fully ready app sample:</p>
<pre><code class="lang-shell">php artisan optimize &amp;&amp; php artisan serve
</code></pre>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/03/article-image-1.png" alt="Image" width="600" height="400" loading="lazy">
<em>A screenshot of the Register page</em></p>
<p>It's also important to note that you can separately run the <code>dev</code> command instead of using <code>build</code> every time when you're making changes to frontend files:</p>
<pre><code class="lang-shell">npm run dev
</code></pre>
<p>See the details in the <code>package.json</code> file, in the <code>scripts</code> field.</p>
<h2 id="heading-how-to-set-up-routes">How to Set Up Routes</h2>
<p>In this real-time chat app, you'll need to have a few routes:</p>
<ul>
<li><code>home</code> for the home page (already should be added)</li>
<li><code>message</code> for adding a new message</li>
<li><code>messages</code> to get all the existing messages</li>
</ul>
<p>You'll have these kind of routes in the <code>web.php</code> file:</p>
<pre><code class="lang-php"><span class="hljs-meta">&lt;?php</span>

<span class="hljs-keyword">use</span> <span class="hljs-title">Illuminate</span>\<span class="hljs-title">Support</span>\<span class="hljs-title">Facades</span>\<span class="hljs-title">Auth</span>;
<span class="hljs-keyword">use</span> <span class="hljs-title">Illuminate</span>\<span class="hljs-title">Support</span>\<span class="hljs-title">Facades</span>\<span class="hljs-title">Route</span>;
<span class="hljs-keyword">use</span> <span class="hljs-title">App</span>\<span class="hljs-title">Http</span>\<span class="hljs-title">Controllers</span>\<span class="hljs-title">HomeController</span>;

Route::get(<span class="hljs-string">'/'</span>, <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params"></span>) </span>{ <span class="hljs-keyword">return</span> view(<span class="hljs-string">'welcome'</span>); });

Auth::routes();

Route::get(<span class="hljs-string">'/home'</span>, [HomeController::class, <span class="hljs-string">'index'</span>])
    -&gt;name(<span class="hljs-string">'home'</span>);
Route::get(<span class="hljs-string">'/messages'</span>, [HomeController::class, <span class="hljs-string">'messages'</span>])
    -&gt;name(<span class="hljs-string">'messages'</span>);
Route::post(<span class="hljs-string">'/message'</span>, [HomeController::class, <span class="hljs-string">'message'</span>])
    -&gt;name(<span class="hljs-string">'message'</span>);
</code></pre>
<p>After setting up those routes, let's use Laravel Events and Queue Jobs advantages.</p>
<h2 id="heading-how-to-set-up-a-laravel-event">How to Set Up a Laravel Event</h2>
<p>You need to create a <code>GotMessage</code> event for listening for a specific event:</p>
<pre><code class="lang-shell">php artisan make:event GotMessage
</code></pre>
<blockquote>
<p>Laravel's events provide a simple observer pattern implementation, allowing you to subscribe and listen for various events that occur within your application. Event classes are typically stored in the <code>app/Events</code> directory. (<a target="_blank" href="https://laravel.com/docs/11.x/events">Docs</a>)</p>
</blockquote>
<p>Set up a private WebSocket channel in the <code>broadcastOn</code> method for all the authenticated users to receive messages in real time. In this case, we will call it <code>"channel_for_everyone"</code>, but you can also make it dynamic, depending on the user, like <code>"App.Models.User.{$this-&gt;message['user_id']}"</code>.</p>
<pre><code class="lang-php"><span class="hljs-meta">&lt;?php</span>

<span class="hljs-keyword">namespace</span> <span class="hljs-title">App</span>\<span class="hljs-title">Events</span>;

<span class="hljs-keyword">use</span> <span class="hljs-title">Illuminate</span>\<span class="hljs-title">Broadcasting</span>\<span class="hljs-title">InteractsWithSockets</span>;
<span class="hljs-keyword">use</span> <span class="hljs-title">Illuminate</span>\<span class="hljs-title">Broadcasting</span>\<span class="hljs-title">PrivateChannel</span>;
<span class="hljs-keyword">use</span> <span class="hljs-title">Illuminate</span>\<span class="hljs-title">Contracts</span>\<span class="hljs-title">Broadcasting</span>\<span class="hljs-title">ShouldBroadcast</span>;
<span class="hljs-keyword">use</span> <span class="hljs-title">Illuminate</span>\<span class="hljs-title">Foundation</span>\<span class="hljs-title">Events</span>\<span class="hljs-title">Dispatchable</span>;
<span class="hljs-keyword">use</span> <span class="hljs-title">Illuminate</span>\<span class="hljs-title">Queue</span>\<span class="hljs-title">SerializesModels</span>;

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">GotMessage</span> <span class="hljs-keyword">implements</span> <span class="hljs-title">ShouldBroadcast</span>
</span>{
    <span class="hljs-keyword">use</span> <span class="hljs-title">Dispatchable</span>, <span class="hljs-title">InteractsWithSockets</span>, <span class="hljs-title">SerializesModels</span>;

    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">__construct</span>(<span class="hljs-params"><span class="hljs-keyword">public</span> <span class="hljs-keyword">array</span> $message</span>) </span>{
        <span class="hljs-comment">//</span>
    }

    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">broadcastOn</span>(<span class="hljs-params"></span>): <span class="hljs-title">array</span> </span>{
        <span class="hljs-comment">// $this-&gt;message is available here</span>
        <span class="hljs-keyword">return</span> [
            <span class="hljs-keyword">new</span> PrivateChannel(<span class="hljs-string">"channel_for_everyone"</span>),
        ];
    }
}
</code></pre>
<p>As you can see, there's a public <code>$massage</code> property as a constructor argument, so you can get message infromation in the front end.</p>
<p>We've already used the channel name in the channels file, and we'll use it in the front end as well for real-time message updates.</p>
<p>Don't forget to implement the <code>ShouldBroadcast</code> interface in the event's class.</p>
<h2 id="heading-how-to-set-up-a-laravel-queue-job">How to Set Up a Laravel Queue Job</h2>
<p>Now it's time to create the <code>SendMessage</code> job for sending messages:</p>
<pre><code class="lang-shell">php artisan make:job SendMessage
</code></pre>
<blockquote>
<p>Laravel allows you to easily create queued jobs that may be processed in the background. By moving time intensive tasks to a queue, your application can respond to web requests with blazing speed and provide a better user experience to your customers. (<a target="_blank" href="https://laravel.com/docs/11.x/queues">Docs</a>)</p>
</blockquote>
<pre><code class="lang-php"><span class="hljs-meta">&lt;?php</span>

<span class="hljs-keyword">namespace</span> <span class="hljs-title">App</span>\<span class="hljs-title">Jobs</span>;

<span class="hljs-keyword">use</span> <span class="hljs-title">App</span>\<span class="hljs-title">Events</span>\<span class="hljs-title">GotMessage</span>;
<span class="hljs-keyword">use</span> <span class="hljs-title">App</span>\<span class="hljs-title">Models</span>\<span class="hljs-title">Message</span>;
<span class="hljs-keyword">use</span> <span class="hljs-title">Illuminate</span>\<span class="hljs-title">Bus</span>\<span class="hljs-title">Queueable</span>;
<span class="hljs-keyword">use</span> <span class="hljs-title">Illuminate</span>\<span class="hljs-title">Contracts</span>\<span class="hljs-title">Queue</span>\<span class="hljs-title">ShouldQueue</span>;
<span class="hljs-keyword">use</span> <span class="hljs-title">Illuminate</span>\<span class="hljs-title">Foundation</span>\<span class="hljs-title">Bus</span>\<span class="hljs-title">Dispatchable</span>;
<span class="hljs-keyword">use</span> <span class="hljs-title">Illuminate</span>\<span class="hljs-title">Queue</span>\<span class="hljs-title">InteractsWithQueue</span>;
<span class="hljs-keyword">use</span> <span class="hljs-title">Illuminate</span>\<span class="hljs-title">Queue</span>\<span class="hljs-title">SerializesModels</span>;

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">SendMessage</span> <span class="hljs-keyword">implements</span> <span class="hljs-title">ShouldQueue</span>
</span>{
    <span class="hljs-keyword">use</span> <span class="hljs-title">Dispatchable</span>, <span class="hljs-title">InteractsWithQueue</span>, <span class="hljs-title">Queueable</span>, <span class="hljs-title">SerializesModels</span>;

    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">__construct</span>(<span class="hljs-params"><span class="hljs-keyword">public</span> Message $message</span>) </span>{
        <span class="hljs-comment">//</span>
    }

    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">handle</span>(<span class="hljs-params"></span>): <span class="hljs-title">void</span> </span>{
        GotMessage::dispatch([
            <span class="hljs-string">'id'</span> =&gt; <span class="hljs-keyword">$this</span>-&gt;message-&gt;id,
            <span class="hljs-string">'user_id'</span> =&gt; <span class="hljs-keyword">$this</span>-&gt;message-&gt;user_id,
            <span class="hljs-string">'text'</span> =&gt; <span class="hljs-keyword">$this</span>-&gt;message-&gt;text,
            <span class="hljs-string">'time'</span> =&gt; <span class="hljs-keyword">$this</span>-&gt;message-&gt;time,
        ]);
    }
}
</code></pre>
<p>The <code>SendMessage.php</code> queue job is responsible for dispatching the <code>GotMessage</code> event with information about a newly sent message. It receives a <code>Message</code> object upon construction, representing the message to be sent. </p>
<p>In its <code>handle()</code> method, it dispatches the <code>GotMessage</code> event with details such as the message ID, user ID, text, and timestamp. This job is designed to be queued for asynchronous processing, enabling efficient handling of message sending tasks in the background.</p>
<p>As you can see, there's a public <code>$massage</code> property as a constructor argument, which we'll use to attach a message information to the queue job.</p>
<h2 id="heading-how-to-write-the-controller-methods">How to Write the Controller Methods</h2>
<p>For the defined routes, here are the appropriate controller methods:</p>
<pre><code class="lang-php"><span class="hljs-meta">&lt;?php</span>

<span class="hljs-keyword">namespace</span> <span class="hljs-title">App</span>\<span class="hljs-title">Http</span>\<span class="hljs-title">Controllers</span>;

<span class="hljs-keyword">use</span> <span class="hljs-title">App</span>\<span class="hljs-title">Jobs</span>\<span class="hljs-title">SendMessage</span>;
<span class="hljs-keyword">use</span> <span class="hljs-title">App</span>\<span class="hljs-title">Models</span>\<span class="hljs-title">Message</span>;
<span class="hljs-keyword">use</span> <span class="hljs-title">App</span>\<span class="hljs-title">Models</span>\<span class="hljs-title">User</span>;
<span class="hljs-keyword">use</span> <span class="hljs-title">Illuminate</span>\<span class="hljs-title">Http</span>\<span class="hljs-title">JsonResponse</span>;
<span class="hljs-keyword">use</span> <span class="hljs-title">Illuminate</span>\<span class="hljs-title">Http</span>\<span class="hljs-title">Request</span>;

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">HomeController</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">Controller</span>
</span>{
    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">__construct</span>(<span class="hljs-params"></span>) </span>{
        <span class="hljs-keyword">$this</span>-&gt;middleware(<span class="hljs-string">'auth'</span>);
    }

    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">index</span>(<span class="hljs-params"></span>) </span>{
        $user = User::where(<span class="hljs-string">'id'</span>, auth()-&gt;id())-&gt;select([
            <span class="hljs-string">'id'</span>, <span class="hljs-string">'name'</span>, <span class="hljs-string">'email'</span>,
        ])-&gt;first();

        <span class="hljs-keyword">return</span> view(<span class="hljs-string">'home'</span>, [
            <span class="hljs-string">'user'</span> =&gt; $user,
        ]);
    }

    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">messages</span>(<span class="hljs-params"></span>): <span class="hljs-title">JsonResponse</span> </span>{
        $messages = Message::with(<span class="hljs-string">'user'</span>)-&gt;get()-&gt;append(<span class="hljs-string">'time'</span>);

        <span class="hljs-keyword">return</span> response()-&gt;json($messages);
    }

    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">message</span>(<span class="hljs-params">Request $request</span>): <span class="hljs-title">JsonResponse</span> </span>{
        $message = Message::create([
            <span class="hljs-string">'user_id'</span> =&gt; auth()-&gt;id(),
            <span class="hljs-string">'text'</span> =&gt; $request-&gt;get(<span class="hljs-string">'text'</span>),
        ]);
        SendMessage::dispatch($message);

        <span class="hljs-keyword">return</span> response()-&gt;json([
            <span class="hljs-string">'success'</span> =&gt; <span class="hljs-literal">true</span>,
            <span class="hljs-string">'message'</span> =&gt; <span class="hljs-string">"Message created and job dispatched."</span>,
        ]);
    }
}
</code></pre>
<ul>
<li>In the <code>home</code> method, we'll get the logged in user's data from the database using the <code>User</code> model and send it to the blade view.</li>
<li>In the <code>messages</code> method, we'll retrieve all the messages from the database using the <code>Message</code> model, attach the <code>user</code> relationship data to it, append the <code>time</code> field (accessor) to each item, and send all that to the view.</li>
<li>In the <code>message</code> method, a new message will be created in the database table by using the <code>Message</code> model, and the <code>SendMessage</code> queue job will be dispatched.</li>
</ul>
<h2 id="heading-how-to-install-laravel-reverb">How to Install Laravel Reverb</h2>
<p>Now we've come to the most important moment: it's time to install <a target="_blank" href="https://laravel.com/docs/11.x/reverb#installation">Reverb</a> in your Laravel app.</p>
<p>It's so easy. All the necessary packaging and configuration setup can be done using this single command:</p>
<pre><code class="lang-shell">php artisan install:broadcasting
</code></pre>
<p>It will ask you to install Laravel Reverb as well as install and build the Node dependencies required for broadcasting. Just press enter to continue.</p>
<p>After the command execution, make sure you've automatically added reverb-specific environment variables to the <code>.env</code> file, like:</p>
<pre><code class="lang-env">BROADCAST_CONNECTION=reverb

###

REVERB_APP_ID=795051
REVERB_APP_KEY=s3w3thzezulgp5g0e5bs
REVERB_APP_SECRET=gncsnk3rzpvczdakl6pz
REVERB_HOST="localhost"
REVERB_PORT=8080
REVERB_SCHEME=http

VITE_REVERB_APP_KEY="${REVERB_APP_KEY}"
VITE_REVERB_HOST="${REVERB_HOST}"
VITE_REVERB_PORT="${REVERB_PORT}"
VITE_REVERB_SCHEME="${REVERB_SCHEME}"
</code></pre>
<p>You'll also have two new configration files in the <code>config</code> directory:</p>
<ul>
<li><code>reverb.php</code></li>
<li><code>broadcasting.php</code></li>
</ul>
<h2 id="heading-how-to-setup-websocket-channels">How to Setup WebSocket Channels</h2>
<p>Lastly, you'll need to add a channel in the <code>channels.php</code> file. It should already be created after installing Reverb.</p>
<pre><code class="lang-php"><span class="hljs-meta">&lt;?php</span>

<span class="hljs-keyword">use</span> <span class="hljs-title">Illuminate</span>\<span class="hljs-title">Support</span>\<span class="hljs-title">Facades</span>\<span class="hljs-title">Broadcast</span>;

Broadcast::channel(<span class="hljs-string">'channel_for_everyone'</span>, <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">$user</span>) </span>{
    <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>;
});
</code></pre>
<p>You'll have only one channel. You can change the channel's name and make it dynamic – it's up to you. In the closure of the channel, we'll always return true, but you can modify it later to make some restrictions regarding the channel's subscription.</p>
<p>Optimize caches one more time:</p>
<pre><code class="lang-shell">php artisan optimize
</code></pre>
<h2 id="heading-how-to-customize-laravel-views">How to Customize Laravel Views</h2>
<p>Now your back end should be ready at this point, so you can switch to the front end.</p>
<p>Before working on the React stuff, you'll want to set up Laravel <code>*.blade.php</code> views. In the <code>home</code> blade view, make sure to have the root div with an ID of <code>main</code> to render all the React components there.</p>
<pre><code class="lang-php">@<span class="hljs-keyword">extends</span>(<span class="hljs-string">'layouts.app'</span>)

@section(<span class="hljs-string">'content'</span>)
    &lt;div <span class="hljs-class"><span class="hljs-keyword">class</span>="<span class="hljs-title">container</span>"&gt;
        &lt;<span class="hljs-title">div</span> <span class="hljs-title">id</span>="<span class="hljs-title">main</span>" <span class="hljs-title">data</span>-<span class="hljs-title">user</span>="</span>{{ json_encode($user) }}<span class="hljs-string">"&gt;&lt;/div&gt;
    &lt;/div&gt;
@endsection</span>
</code></pre>
<p>The div with ID of <code>main</code> gets a data property for holding the <code>$user</code> info sent from the controller's <code>home</code> method.</p>
<p>I won't put the whole <code>resources/views/welcome.blade.php</code> content here, but you can just make the following small changes to it:</p>
<ul>
<li>Replace <code>url('/dashboard')</code> with <code>url('/home')</code>;</li>
<li>Replace <code>Dashboard</code> with <code>Home</code>;</li>
<li>Remove <code>main</code> and <code>footer</code> sections.</li>
</ul>
<h2 id="heading-lets-work-on-the-front-end">Let's Work on the Front End</h2>
<p>In Reverb, event broadcasting is done by a server-side broadcasting driver that broadcasts your Laravel events so that the front end can receive them within the browser client. </p>
<p>In the front end, <a target="_blank" href="https://github.com/laravel/echo">Laravel Echo</a> does that job under the hood. Echo a JavaScript library that makes it painless to subscribe to channels and listen for events broadcast by your server-side broadcasting driver.</p>
<p>You can find the WebSocket configurations setup with Echo in the <code>rources/js/echo.js</code> file, but you don't need to do anything there for this project.</p>
<p>Let's create a few React components so that we have a refactored and more readable project.</p>
<p>Create a <code>Main.jsx</code> component in the new <code>components</code> folder:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> React <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>;
<span class="hljs-keyword">import</span> ReactDOM <span class="hljs-keyword">from</span> <span class="hljs-string">'react-dom/client'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">'../../css/app.css'</span>;
<span class="hljs-keyword">import</span> ChatBox <span class="hljs-keyword">from</span> <span class="hljs-string">"./ChatBox.jsx"</span>;

<span class="hljs-keyword">if</span> (<span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">'main'</span>)) {
    <span class="hljs-keyword">const</span> rootUrl = <span class="hljs-string">"http://127.0.0.1:8000"</span>;

    ReactDOM.createRoot(<span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">'main'</span>)).render(
        <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">React.StrictMode</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">ChatBox</span> <span class="hljs-attr">rootUrl</span>=<span class="hljs-string">{rootUrl}</span> /&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">React.StrictMode</span>&gt;</span></span>
    );
}
</code></pre>
<p>Here we'll check if there's an element with the id <code>'main'</code>. If it exists, it proceeds with rendering the React application.</p>
<p>As you can see, there's a <code>ChatBox</code> component. We'll learn more about it soon.</p>
<p>Remove the <code>resources/js/components/Example.jsx</code> file, and import the <code>Main.jsx</code> component in the <code>app.js</code>:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> <span class="hljs-string">'./bootstrap'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">'./components/Main.jsx'</span>;
</code></pre>
<p>Create <code>Message.jsx</code> and <code>MessageInput.jsx</code> files so you can use them in the <code>ChatBox</code> component.</p>
<p>The <code>Message</code> component will get <code>userId</code> and <code>message</code> arguments (fields) to show each message in the chat box.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> React <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;

<span class="hljs-keyword">const</span> Message = <span class="hljs-function">(<span class="hljs-params">{ userId, message }</span>) =&gt;</span> {
    <span class="hljs-keyword">return</span> (
        <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">{</span>`<span class="hljs-attr">row</span> ${
        <span class="hljs-attr">userId</span> === <span class="hljs-string">message.user_id</span> ? "<span class="hljs-attr">justify-content-end</span>" <span class="hljs-attr">:</span> ""
        }`}&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"col-md-6"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">small</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"text-muted"</span>&gt;</span>
                    <span class="hljs-tag">&lt;<span class="hljs-name">strong</span>&gt;</span>{message.user.name} | <span class="hljs-tag">&lt;/<span class="hljs-name">strong</span>&gt;</span>
                <span class="hljs-tag">&lt;/<span class="hljs-name">small</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">small</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"text-muted float-right"</span>&gt;</span>
                    {message.time}
                <span class="hljs-tag">&lt;/<span class="hljs-name">small</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">{</span>`<span class="hljs-attr">alert</span> <span class="hljs-attr">alert-</span>${
                <span class="hljs-attr">userId</span> === <span class="hljs-string">message.user_id</span> ? "<span class="hljs-attr">primary</span>" <span class="hljs-attr">:</span> "<span class="hljs-attr">secondary</span>"
                }`} <span class="hljs-attr">role</span>=<span class="hljs-string">"alert"</span>&gt;</span>
                    {message.text}
                <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
    );
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> Message;
</code></pre>
<p>The <code>Message.jsx</code> component renders individual messages within the chat interface. It receives the <code>userId</code> and <code>message</code> props. Based on whether the message sender matches the current user, it aligns the message to the appropriate side of the screen. </p>
<p>Each message includes the sender's name, timestamp, and the message content itself, styled differently based on whether the message is sent by the current user or another user.</p>
<p>The <code>MessageInput</code> component will care about creating a new message:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> React, { useState } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;

<span class="hljs-keyword">const</span> MessageInput = <span class="hljs-function">(<span class="hljs-params">{ rootUrl }</span>) =&gt;</span> {
    <span class="hljs-keyword">const</span> [message, setMessage] = useState(<span class="hljs-string">""</span>);

    <span class="hljs-keyword">const</span> messageRequest = <span class="hljs-keyword">async</span> (text) =&gt; {
        <span class="hljs-keyword">try</span> {
            <span class="hljs-keyword">await</span> axios.post(<span class="hljs-string">`<span class="hljs-subst">${rootUrl}</span>/message`</span>, {
                text,
            });
        } <span class="hljs-keyword">catch</span> (err) {
            <span class="hljs-built_in">console</span>.log(err.message);
        }
    };

    <span class="hljs-keyword">const</span> sendMessage = <span class="hljs-function">(<span class="hljs-params">e</span>) =&gt;</span> {
        e.preventDefault();
        <span class="hljs-keyword">if</span> (message.trim() === <span class="hljs-string">""</span>) {
            alert(<span class="hljs-string">"Please enter a message!"</span>);
            <span class="hljs-keyword">return</span>;
        }

        messageRequest(message);
        setMessage(<span class="hljs-string">""</span>);
    };

    <span class="hljs-keyword">return</span> (
        <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"input-group"</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">onChange</span>=<span class="hljs-string">{(e)</span> =&gt;</span> setMessage(e.target.value)}
                   autoComplete="off"
                   type="text"
                   className="form-control"
                   placeholder="Message..."
                   value={message}
            /&gt;
            <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"input-group-append"</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">onClick</span>=<span class="hljs-string">{(e)</span> =&gt;</span> sendMessage(e)}
                        className="btn btn-primary"
                        type="button"&gt;Send<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
    );
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> MessageInput;
</code></pre>
<p>The <code>MessageInput</code> component provides a form input field for users to type messages and send them in the chat interface. By clicking the button, it triggers a function to send the message to the server via an Axios POST request to the specified <code>rootUrl</code> that it got from the parent <code>ChatBox</code> component. It also handles validation to ensure that users cannot send empty messages. You can customize it later if you want. </p>
<p>Now create a <code>ChatBox.jsx</code> component to have the front end ready:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> React, { useEffect, useRef, useState } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;
<span class="hljs-keyword">import</span> Message <span class="hljs-keyword">from</span> <span class="hljs-string">"./Message.jsx"</span>;
<span class="hljs-keyword">import</span> MessageInput <span class="hljs-keyword">from</span> <span class="hljs-string">"./MessageInput.jsx"</span>;

<span class="hljs-keyword">const</span> ChatBox = <span class="hljs-function">(<span class="hljs-params">{ rootUrl }</span>) =&gt;</span> {
    <span class="hljs-keyword">const</span> userData = <span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">'main'</span>)
        .getAttribute(<span class="hljs-string">'data-user'</span>);

    <span class="hljs-keyword">const</span> user = <span class="hljs-built_in">JSON</span>.parse(userData);
    <span class="hljs-comment">// `App.Models.User.${user.id}`;</span>
    <span class="hljs-keyword">const</span> webSocketChannel = <span class="hljs-string">`channel_for_everyone`</span>;

    <span class="hljs-keyword">const</span> [messages, setMessages] = useState([]);
    <span class="hljs-keyword">const</span> scroll = useRef();

    <span class="hljs-keyword">const</span> scrollToBottom = <span class="hljs-function">() =&gt;</span> {
        scroll.current.scrollIntoView({ <span class="hljs-attr">behavior</span>: <span class="hljs-string">"smooth"</span> });
    };

    <span class="hljs-keyword">const</span> connectWebSocket = <span class="hljs-function">() =&gt;</span> {
        <span class="hljs-built_in">window</span>.Echo.private(webSocketChannel)
            .listen(<span class="hljs-string">'GotMessage'</span>, <span class="hljs-keyword">async</span> (e) =&gt; {
                <span class="hljs-comment">// e.message</span>
                <span class="hljs-keyword">await</span> getMessages();
            });
    }

    <span class="hljs-keyword">const</span> getMessages = <span class="hljs-keyword">async</span> () =&gt; {
        <span class="hljs-keyword">try</span> {
            <span class="hljs-keyword">const</span> m = <span class="hljs-keyword">await</span> axios.get(<span class="hljs-string">`<span class="hljs-subst">${rootUrl}</span>/messages`</span>);
            setMessages(m.data);
            <span class="hljs-built_in">setTimeout</span>(scrollToBottom, <span class="hljs-number">0</span>);
        } <span class="hljs-keyword">catch</span> (err) {
            <span class="hljs-built_in">console</span>.log(err.message);
        }
    };

    useEffect(<span class="hljs-function">() =&gt;</span> {
        getMessages();
        connectWebSocket();

        <span class="hljs-keyword">return</span> <span class="hljs-function">() =&gt;</span> {
            <span class="hljs-built_in">window</span>.Echo.leave(webSocketChannel);
        }
    }, []);

    <span class="hljs-keyword">return</span> (
        <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"row justify-content-center"</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"col-md-8"</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"card"</span>&gt;</span>
                    <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"card-header"</span>&gt;</span>Chat Box<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
                    <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"card-body"</span>
                         <span class="hljs-attr">style</span>=<span class="hljs-string">{{height:</span> "<span class="hljs-attr">500px</span>", <span class="hljs-attr">overflowY:</span> "<span class="hljs-attr">auto</span>"}}&gt;</span>
                        {
                            messages?.map((message) =&gt; (
                                <span class="hljs-tag">&lt;<span class="hljs-name">Message</span> <span class="hljs-attr">key</span>=<span class="hljs-string">{message.id}</span>
                                         <span class="hljs-attr">userId</span>=<span class="hljs-string">{user.id}</span>
                                         <span class="hljs-attr">message</span>=<span class="hljs-string">{message}</span>
                                /&gt;</span>
                            ))
                        }
                        <span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">ref</span>=<span class="hljs-string">{scroll}</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span>
                    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
                    <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"card-footer"</span>&gt;</span>
                        <span class="hljs-tag">&lt;<span class="hljs-name">MessageInput</span> <span class="hljs-attr">rootUrl</span>=<span class="hljs-string">{rootUrl}</span> /&gt;</span>
                    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
                <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
    );
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> ChatBox;
</code></pre>
<p>The <code>ChatBox</code> component manages a chat interface within the application. It fetches and displays messages from a server using WebSocket and HTTP requests.</p>
<p>The component renders a list of messages, a message input field, and automatically scrolls to the bottom when new messages arrive.</p>
<p>It defines a WebSocket channel for real-time message updates. You need to set up that channel by using the same name as it was written in the <code>routes/hannels.php</code> and in the <code>app/Events/GotMessage.php</code> queue job.</p>
<p>Also, the <code>leave()</code> function is called within the <code>useEffect</code> cleanup function to unsubscribe from the WebSocket channel when the component unmounts. This prevents memory leaks and unnecessary network connections by stopping the component from listening to updates on the WebSocket channel after it's no longer needed.</p>
<h2 id="heading-running-the-application">Running the Application</h2>
<p>Now, everything's ready and it's time to check out the app. Follow these instructions:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/03/article-image-2.png" alt="Image" width="600" height="400" loading="lazy">
<em>A screenshot from the terminal with all the necessary commands</em></p>
<ul>
<li>Build frontend assets (this is not a "forever" running command):<br><code>npm run build</code></li>
<li>Start listening to the Laravel events:<br><code>php artisan queue:listen</code></li>
<li>Start the WebSocket server:<br><code>php artisan reverb:start</code></li>
<li>Start the server (you may use an alternative for your app like a local running server):<br><code>php artisan serve</code></li>
</ul>
<p>After all the necessary commands are running, you can check out the app by visiting the default URL: <code>http://127.0.0.1:8000</code>.</p>
<p>For testing, you can register two different users, have those users log in, send messages from each of them, and see the chat box.</p>
<h2 id="heading-useful-reverb-resources">Useful Reverb Resources</h2>
<p>Now that we've reached the end of this article, it's worth listing some useful resources about Reverb:</p>
<ul>
<li><a target="_blank" href="https://laravel.com/docs/11.x/broadcasting">Laravel Broadcasting</a> (official documentation)</li>
<li><a target="_blank" href="https://www.youtube.com/watch?v=0g7HqfsCX4Y">Taylor Otwel - Laravel Update</a> (talk on Laracon EU 2024)</li>
<li><a target="_blank" href="https://twitter.com/_joedixon">Joe Dixon on X</a> (creator of Reverb)</li>
<li><a target="_blank" href="https://laracasts.com/series/lukes-larabits/episodes/17">Laracast episode</a> (practical example with Reverb)</li>
</ul>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Now you know how to build real-time applications with Laravel Reverb in the new version of Laravel. With this, you can implement WebSocket communications in your full-stack app and avoid using any additional 3rd-party services (like Pusher and Socket.io).</p>
<p>If you want to have a clear idea of how to integrate React.js into your Laravel app without using any additional Laravel tools (like Inertia), you can read through my previous <a target="_blank" href="https://www.freecodecamp.org/news/use-react-with-laravel/">freeCodeCamp article</a>, where you can build a single-page, full-stack Tasklist app.</p>
<p>The complete code for this article is here on my <a target="_blank" href="https://github.com/boolfalse/laravel-reverb-react-chat"><strong>GitHub</strong></a>⭐, where I actively publicize much of my work about various modern technologies.</p>
<p>For more information, you can visit my website: <a target="_blank" href="https://boolfalse.com/"><strong>boolfalse.com</strong></a></p>
<p>Feel free to share this article. 😇</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Use React.js with Laravel to Build a Draggable Tasklist App ]]>
                </title>
                <description>
                    <![CDATA[ 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 of... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/use-react-with-laravel/</link>
                <guid isPermaLink="false">66ba2afae059e7f2fa5d3276</guid>
                
                    <category>
                        <![CDATA[ Laravel ]]>
                    </category>
                
                    <category>
                        <![CDATA[ React ]]>
                    </category>
                
                    <category>
                        <![CDATA[ vite ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ San B ]]>
                </dc:creator>
                <pubDate>Fri, 26 Jan 2024 00:09:49 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2024/01/freecodecamp-boolfalse-laravel-react-vite-draggable-tasklist.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>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.</p>
<p>As a backend framework, Laravel actually offers a tool to help you do this, called <a target="_blank" href="https://laravel.com/docs/10.x/frontend#inertia">Inertia</a>. Here's what the docs say about it:</p>
<blockquote>
<p>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.</p>
</blockquote>
<p>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?</p>
<p>Well, in this article, you will learn how to use React.js with Laravel as a backend by building a draggable tasklist app.</p>
<p>For this full-stack single-page app, you'll use <a target="_blank" href="https://vitejs.dev/">Vite.js</a> as your frontend build tool and the <a target="_blank" href="https://www.npmjs.com/package/react-beautiful-dnd">react-beautiful-dnd</a> package for draggable items.</p>
<p>By the end of this article, you will have a single-page app for managing tasks, which will look like this:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/01/tasklist.png" alt="Image" width="600" height="400" loading="lazy">
<em>Captured from a local working project</em></p>
<p>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.</p>
<h2 id="heading-table-of-contents">Table of Contents:</h2>
<ul>
<li><a class="post-section-overview" href="#heading-prerequisites">Prerequisites</a></li>
<li><a class="post-section-overview" href="#heading-the-backend-how-to-install-laravel">The Backend: How to Install Laravel</a></li>
<li><a class="post-section-overview" href="#heading-how-to-create-models-and-migrations">How to Create Models and Migrations</a></li>
<li><a class="post-section-overview" href="#heading-how-to-create-seeders">How to Create Seeders</a></li>
<li><a class="post-section-overview" href="#heading-how-to-connect-to-the-mysql-database">How to Connect to the MySQL Database</a></li>
<li><a class="post-section-overview" href="#heading-service-injection">Service Injection</a></li>
<li><a class="post-section-overview" href="#heading-web-and-api-routes-in-laravel">Web and API Routes in Laravel</a></li>
<li><a class="post-section-overview" href="#heading-validation-requests-in-laravel">Validation Requests in Laravel</a></li>
<li><a class="post-section-overview" href="#heading-how-to-write-a-controller-that-uses-services">How to Write a Controller that Uses Services</a></li>
<li><a class="post-section-overview" href="#heading-how-to-test-the-api-routes">How to Test the API Routes</a></li>
<li><a class="post-section-overview" href="#heading-the-frontend-how-to-install-the-packages">The Frontend: How to Install the Packages</a></li>
<li><a class="post-section-overview" href="#heading-how-to-configure-vitejs">How to Configure Vite.js</a></li>
<li><a class="post-section-overview" href="#heading-reactjs-initial-integration">React.js – Initial Integration</a></li>
<li><a class="post-section-overview" href="#heading-how-to-add-css">How to Add CSS</a></li>
<li><a class="post-section-overview" href="#heading-a-service-for-the-api-requests">A Service for the API requests</a></li>
<li><a class="post-section-overview" href="#heading-reactjs-components">React.js Components</a></li>
<li><a class="post-section-overview" href="#heading-final-results">Final Results</a></li>
<li><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></li>
</ul>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>Before following along, it would be helpful to have a basic understanding of React.js, Laravel, and familiarity with fundamental web development concepts.</p>
<p>You'll need the following tools for the app that we'll build in this article:</p>
<ul>
<li><strong>PHP</strong> 8.1 or above (run <code>php -v</code> to check the version)</li>
<li><strong>Composer</strong> (run <code>composer</code> to check that it exists)</li>
<li><strong>Node.js</strong> 18 or above (run <code>node -v</code> to check the version)</li>
<li><strong>MySQL</strong> 5.7 or above (run <code>mysql --version</code> to check if it exists, or follow the <a target="_blank" href="https://dev.mysql.com/doc/mysql-windows-excerpt/5.7/en/windows-testing.html">docs</a>)</li>
</ul>
<p>Additional (optional) tools that you can use:</p>
<ul>
<li><strong>Postman</strong> – a program with a UI for testing the API routes</li>
<li><strong>curl</strong> – a CLI command for testing the API routes</li>
</ul>
<p>We'll start by building out the backend, and then move to the frontend.</p>
<h2 id="heading-the-backend-how-to-install-laravel">The Backend: How to Install Laravel</h2>
<p>First, if you don't have it already, you'll need to install the Laravel framework on your local machine.</p>
<p>One way to install Laravel is by using a popular dependency manager for PHP called Composer. Here's the command to do so:</p>
<pre><code class="lang-shell">composer create-project laravel/laravel tasklist
</code></pre>
<p>This will install the latest stable version of Laravel in your local machine (currently it's version 10).</p>
<p>The <em>tasklist</em> in the command is the app's root folder name, which you can set to whatever you want.</p>
<p>At this point, you can <code>cd</code> into the project's folder and run the backend app without needing to have a virtual server set up:</p>
<pre><code class="lang-shell">cd tasklist/ &amp;&amp; php artisan serve
</code></pre>
<p>The <code>artisan</code> in the above command is a CLI tool included in Laravel. It exists at the root of your Laravel application as the <code>artisan</code> script file, which provides a number of helpful commands that can assist you while you build your application.<br>We'll use it in this article often.</p>
<p>Visit <a target="_blank" href="http://127.0.0.1:8000"><code>http://127.0.0.1:8000</code></a> in your browser to see the default page. It should look like this:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/01/laravel.png" alt="Image" width="600" height="400" loading="lazy">
<em>Laravel welcome page</em></p>
<h2 id="heading-how-to-create-models-and-migrations">How to Create Models and Migrations</h2>
<p>Now, let's create <em>Project</em> and <em>Task</em> models, as well as migrations for them.</p>
<p>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. </p>
<p>You can create model and migration files manually as well as generate them using the <code>artisan</code> command:</p>
<pre><code class="lang-shell">php artisan make:model Project -m
php artisan make:model Task -m
</code></pre>
<p>The <code>-m</code> argument will automatically generate a migration file using the provided model name.</p>
<p>Keep the command execution sequence as it is, so the Project's migration later can run before the Task's migration.</p>
<p>This is important, because the <code>projects</code> and <code>tasks</code> 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.</p>
<p>Set the <code>Project</code> model's <code>$fillable</code> fields and the <code>task()</code> relationship method as below:</p>
<pre><code class="lang-php"><span class="hljs-meta">&lt;?php</span>

<span class="hljs-keyword">namespace</span> <span class="hljs-title">App</span>\<span class="hljs-title">Models</span>;

<span class="hljs-keyword">use</span> <span class="hljs-title">Illuminate</span>\<span class="hljs-title">Database</span>\<span class="hljs-title">Eloquent</span>\<span class="hljs-title">Factories</span>\<span class="hljs-title">HasFactory</span>;
<span class="hljs-keyword">use</span> <span class="hljs-title">Illuminate</span>\<span class="hljs-title">Database</span>\<span class="hljs-title">Eloquent</span>\<span class="hljs-title">Model</span>;
<span class="hljs-keyword">use</span> <span class="hljs-title">Illuminate</span>\<span class="hljs-title">Database</span>\<span class="hljs-title">Eloquent</span>\<span class="hljs-title">Relations</span>\<span class="hljs-title">HasMany</span>;

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Project</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">Model</span>
</span>{
    <span class="hljs-keyword">use</span> <span class="hljs-title">HasFactory</span>;

    <span class="hljs-keyword">protected</span> $table = <span class="hljs-string">'projects'</span>;
    <span class="hljs-keyword">public</span> $timestamps = <span class="hljs-literal">false</span>;
    <span class="hljs-keyword">protected</span> $fillable = [
        <span class="hljs-string">'id'</span>, <span class="hljs-comment">// primary key, auto-increment, integer</span>
        <span class="hljs-string">'name'</span>, <span class="hljs-comment">// string</span>
    ];

    <span class="hljs-comment">// a project can have multiple tasks</span>
    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">tasks</span>(<span class="hljs-params"></span>): <span class="hljs-title">HasMany</span>
    </span>{
        <span class="hljs-keyword">return</span> <span class="hljs-keyword">$this</span>-&gt;hasMany(Task::class);
    }
}
</code></pre>
<p>By default, the <code>$timestamps</code> public property has a <code>true</code> value, which is coming from the parent <code>Model</code> class. This means that the <code>created_at</code> and <code>updated_at</code> columns in your database table will be maintained automatically by <em>Eloquent</em> (the ORM included in Laravel).</p>
<p>But you can customize it by changing its value to <code>false</code>. We don't need to have <code>created_at</code> and <code>updated_at</code> fields in the <code>projects</code> table, so we'll set the <code>$timestamps</code> to <code>false</code>.</p>
<p>Set the <code>Task</code> model's <code>$fillable</code> fields, <code>project()</code> relationship method, and <code>created</code> accessor. An <a target="_blank" href="https://laravel.com/docs/10.x/eloquent-mutators#defining-an-accessor">accessor</a> in Laravel is like a function between the database and your code, that can access the already fetched database record and modify it.</p>
<pre><code class="lang-php"><span class="hljs-meta">&lt;?php</span>

<span class="hljs-keyword">namespace</span> <span class="hljs-title">App</span>\<span class="hljs-title">Models</span>;

<span class="hljs-keyword">use</span> <span class="hljs-title">Illuminate</span>\<span class="hljs-title">Database</span>\<span class="hljs-title">Eloquent</span>\<span class="hljs-title">Factories</span>\<span class="hljs-title">HasFactory</span>;
<span class="hljs-keyword">use</span> <span class="hljs-title">Illuminate</span>\<span class="hljs-title">Database</span>\<span class="hljs-title">Eloquent</span>\<span class="hljs-title">Model</span>;
<span class="hljs-keyword">use</span> <span class="hljs-title">Illuminate</span>\<span class="hljs-title">Database</span>\<span class="hljs-title">Eloquent</span>\<span class="hljs-title">Relations</span>\<span class="hljs-title">BelongsTo</span>;
<span class="hljs-keyword">use</span> <span class="hljs-title">Illuminate</span>\<span class="hljs-title">Database</span>\<span class="hljs-title">Eloquent</span>\<span class="hljs-title">SoftDeletes</span>;

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Task</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">Model</span>
</span>{
    <span class="hljs-keyword">use</span> <span class="hljs-title">HasFactory</span>, <span class="hljs-title">SoftDeletes</span>;

    <span class="hljs-keyword">protected</span> $table = <span class="hljs-string">'tasks'</span>;
    <span class="hljs-keyword">protected</span> $fillable = [
        <span class="hljs-string">'id'</span>, <span class="hljs-comment">// primary key, auto-increment, integer</span>
        <span class="hljs-string">'project_id'</span>, <span class="hljs-comment">// foreign key, integer</span>

        <span class="hljs-string">'priority'</span>, <span class="hljs-comment">// integer</span>
        <span class="hljs-string">'title'</span>, <span class="hljs-comment">// string</span>
        <span class="hljs-string">'description'</span>, <span class="hljs-comment">// text</span>
    ];
    <span class="hljs-keyword">protected</span> $appends = [
        <span class="hljs-string">'created'</span>,
    ];

    <span class="hljs-comment">// each task belongs to a single project</span>
    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">project</span>(<span class="hljs-params"></span>): <span class="hljs-title">BelongsTo</span>
    </span>{
        <span class="hljs-keyword">return</span> <span class="hljs-keyword">$this</span>-&gt;belongsTo(Project::class);
    }

    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getCreatedAttribute</span>(<span class="hljs-params"></span>)
    </span>{
        <span class="hljs-keyword">return</span> <span class="hljs-keyword">$this</span>-&gt;created_at-&gt;diffForHumans();
    }
}
</code></pre>
<p>Above, in the <code>Task</code> model, there is an accessor called <code>created</code>. For having an accessor, we have the <code>created</code> field in the <code>$appends</code> array, and also a public function <code>getCreatedAttribute()</code>.</p>
<p>In <code>get**&lt;WordsInCamelCase&gt;**Attribute()</code> function there is logic that will run to modify the already fetched database record.</p>
<p>In our case the <code>getCreatedAttribute()</code> function will return a human-friendly and readable time difference between the current time and the given time. For example, <em>"3 mins ago"</em> or <em>"4 months ago"</em>.</p>
<p>Now that the models are ready, let's set up the migrations.</p>
<p>First, set up a migration for the <code>projects</code> table:</p>
<pre><code class="lang-php"><span class="hljs-meta">&lt;?php</span>

<span class="hljs-keyword">use</span> <span class="hljs-title">Illuminate</span>\<span class="hljs-title">Database</span>\<span class="hljs-title">Migrations</span>\<span class="hljs-title">Migration</span>;
<span class="hljs-keyword">use</span> <span class="hljs-title">Illuminate</span>\<span class="hljs-title">Database</span>\<span class="hljs-title">Schema</span>\<span class="hljs-title">Blueprint</span>;
<span class="hljs-keyword">use</span> <span class="hljs-title">Illuminate</span>\<span class="hljs-title">Support</span>\<span class="hljs-title">Facades</span>\<span class="hljs-title">Schema</span>;

<span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">Migration</span>
</span>{
    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">up</span>(<span class="hljs-params"></span>): <span class="hljs-title">void</span>
    </span>{
        Schema::create(<span class="hljs-string">'projects'</span>, <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">Blueprint $table</span>) </span>{
            $table-&gt;id();
            $table-&gt;string(<span class="hljs-string">'name'</span>);
        });
    }

    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">down</span>(<span class="hljs-params"></span>): <span class="hljs-title">void</span>
    </span>{
        Schema::dropIfExists(<span class="hljs-string">'projects'</span>);
    }
};
</code></pre>
<p>Then set up a migration for the <code>tasks</code> table:</p>
<pre><code class="lang-php"><span class="hljs-meta">&lt;?php</span>

<span class="hljs-keyword">use</span> <span class="hljs-title">Illuminate</span>\<span class="hljs-title">Database</span>\<span class="hljs-title">Migrations</span>\<span class="hljs-title">Migration</span>;
<span class="hljs-keyword">use</span> <span class="hljs-title">Illuminate</span>\<span class="hljs-title">Database</span>\<span class="hljs-title">Schema</span>\<span class="hljs-title">Blueprint</span>;
<span class="hljs-keyword">use</span> <span class="hljs-title">Illuminate</span>\<span class="hljs-title">Support</span>\<span class="hljs-title">Facades</span>\<span class="hljs-title">Schema</span>;

<span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">Migration</span>
</span>{
    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">up</span>(<span class="hljs-params"></span>): <span class="hljs-title">void</span>
    </span>{
        Schema::create(<span class="hljs-string">'tasks'</span>, <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">Blueprint $table</span>) </span>{
            $table-&gt;id();

            $table-&gt;foreignId(<span class="hljs-string">'project_id'</span>)-&gt;nullable()-&gt;constrained();

            $table-&gt;integer(<span class="hljs-string">'priority'</span>);
            $table-&gt;string(<span class="hljs-string">'title'</span>);
            $table-&gt;text(<span class="hljs-string">'description'</span>)-&gt;nullable();

            $table-&gt;timestamps();
            $table-&gt;softDeletes();
        });
    }

    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">down</span>(<span class="hljs-params"></span>): <span class="hljs-title">void</span>
    </span>{
        <span class="hljs-comment">// drop existing foreign keys</span>
        Schema::table(<span class="hljs-string">'tasks'</span>, <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">Blueprint $table</span>) </span>{
            <span class="hljs-keyword">if</span> (Schema::hasColumn(<span class="hljs-string">'tasks'</span>, <span class="hljs-string">'project_id'</span>)) {
                $table-&gt;dropForeign([<span class="hljs-string">'project_id'</span>]);
            }
        });

        <span class="hljs-comment">// drop the table</span>
        Schema::dropIfExists(<span class="hljs-string">'tasks'</span>);
    }
};
</code></pre>
<p>The <code>tasks</code> table has a foreign key <code>project_id</code>, which is a reference to the <code>projects</code> table. So it's a good practice to update the <code>down()</code> method too, to be sure that the <code>project_id</code> foreign will be dropped before dropping the actual <code>projects</code> table.</p>
<p>There is also a <code>priority</code> field, which will be a non-nullable natural number for ordering the tasks. And optionally, you can add a soft deletion feature to the <code>Task</code> model.</p>
<h2 id="heading-how-to-create-seeders">How to Create Seeders</h2>
<p>Now we need to add dummy data to the <code>projects</code> and <code>tasks</code> tables. To seed some data in the database, you can use Laravel <em>seeders</em>. This allows you to create dummy data to use in your database. </p>
<p>If you want to read more about how this works, you can <a target="_blank" href="https://laravel.com/docs/10.x/seeding">check out the docs here</a>.</p>
<p>Laravel provides a way to generate those files by using <code>make:seeder</code> artisan command:</p>
<pre><code class="lang-shell">php artisan make:seeder ProjectsSeeder
php artisan make:seeder TasksSeeder
</code></pre>
<p>So with the above commands, you'll have <code>database/seeders/ProjectsSeeder.php</code> and <code>database/seeders/TasksSeeder.php</code> files created.</p>
<p>At first, you'll need to set up the <code>ProjectsSeeder</code> to add a few projects to the <code>projects</code> table. Then you can set up the <code>TasksSeeder</code> to add tasks to the <code>tasks</code> table.</p>
<p>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 <code>tasks</code> table will link to a specific entry in the <code>projects</code> table. Here's the importance of having a <code>project_id</code> foreign key in the <code>tasks</code> table to be able to relate each task to a specific project as well as retrieve the specific project's tasks.</p>
<p>You can imagine the database structure by looking at the following visuals:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/01/laravel_react_tasklist-1.png" alt="Image" width="600" height="400" loading="lazy">
<em>generated by PHPStorm IDE</em></p>
<p>Using the example below, you can generate 3 projects:</p>
<pre><code class="lang-php"><span class="hljs-meta">&lt;?php</span>

<span class="hljs-keyword">namespace</span> <span class="hljs-title">Database</span>\<span class="hljs-title">Seeders</span>;

<span class="hljs-keyword">use</span> <span class="hljs-title">App</span>\<span class="hljs-title">Models</span>\<span class="hljs-title">Project</span>;
<span class="hljs-keyword">use</span> <span class="hljs-title">Illuminate</span>\<span class="hljs-title">Database</span>\<span class="hljs-title">Seeder</span>;

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ProjectsSeeder</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">Seeder</span>
</span>{
    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">run</span>(<span class="hljs-params"></span>): <span class="hljs-title">void</span>
    </span>{
        <span class="hljs-keyword">for</span> ($i = <span class="hljs-number">1</span>; $i &lt;= <span class="hljs-number">3</span>; $i++) {
            Project::create([
                <span class="hljs-string">'name'</span> =&gt; <span class="hljs-string">"Project <span class="hljs-subst">$i</span>"</span>,
            ]);
        }
    }
}
</code></pre>
<p>Next, set up <code>TasksSeeder</code>. 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 <code>ProjectsSeeder</code> is ready to create a few projects.</p>
<p>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 <code>project_id</code> field.</p>
<p>Using the example below, you can generate 10 projects:</p>
<pre><code class="lang-php"><span class="hljs-meta">&lt;?php</span>

<span class="hljs-keyword">namespace</span> <span class="hljs-title">Database</span>\<span class="hljs-title">Seeders</span>;

<span class="hljs-keyword">use</span> <span class="hljs-title">App</span>\<span class="hljs-title">Models</span>\<span class="hljs-title">Project</span>;
<span class="hljs-keyword">use</span> <span class="hljs-title">App</span>\<span class="hljs-title">Models</span>\<span class="hljs-title">Task</span>;
<span class="hljs-keyword">use</span> <span class="hljs-title">Illuminate</span>\<span class="hljs-title">Database</span>\<span class="hljs-title">Seeder</span>;

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">TasksSeeder</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">Seeder</span>
</span>{
    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">run</span>(<span class="hljs-params"></span>): <span class="hljs-title">void</span>
    </span>{
        $project_ids = Project::all()-&gt;pluck(<span class="hljs-string">'id'</span>)-&gt;toArray();

        $now = now();
        $tasks = [];
        $project_priorities = [];
        <span class="hljs-keyword">foreach</span> ($project_ids <span class="hljs-keyword">as</span> $project_id) {
            $project_priorities[$project_id] = <span class="hljs-number">0</span>;
        }

        <span class="hljs-keyword">for</span> ($i = <span class="hljs-number">1</span>; $i &lt;= <span class="hljs-number">10</span>; $i++) {
            $project_id = $project_ids[array_rand($project_ids)];
            $project_priorities[$project_id]++;

            $tasks[] = [
                <span class="hljs-string">'project_id'</span> =&gt; $project_id,
                <span class="hljs-string">'priority'</span> =&gt; $project_priorities[$project_id],
                <span class="hljs-string">'title'</span> =&gt; <span class="hljs-string">"Task "</span> . $project_priorities[$project_id],
                <span class="hljs-string">'description'</span> =&gt; <span class="hljs-string">"Description for Task "</span> . $project_priorities[$project_id],

                <span class="hljs-string">'created_at'</span> =&gt; $now,
                <span class="hljs-string">'updated_at'</span> =&gt; $now,
            ];
        }

        Task::insert($tasks);
    }
}
</code></pre>
<p>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 <code>tasks</code> table. </p>
<p>As you may have noticed, we're inserting <code>$tasks</code> into the <code>tasks</code> table using the <code>insert()</code> static function, which allows us to insert all the items into the database table with a single query. </p>
<p>But it has a downside as well: it doesn't manage <code>created_at</code> and <code>updated_at</code> fields. That's why there's a need to set up those fields manually by assigning them the same <code>$now</code> timestamp.</p>
<p>Now, when you have all the seeders ready, you need to register them into the <code>DatabaseSeeder</code>.</p>
<pre><code class="lang-php"><span class="hljs-meta">&lt;?php</span>

<span class="hljs-keyword">namespace</span> <span class="hljs-title">Database</span>\<span class="hljs-title">Seeders</span>;

<span class="hljs-keyword">use</span> <span class="hljs-title">Illuminate</span>\<span class="hljs-title">Database</span>\<span class="hljs-title">Seeder</span>;

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">DatabaseSeeder</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">Seeder</span>
</span>{
    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">run</span>(<span class="hljs-params"></span>): <span class="hljs-title">void</span>
    </span>{
        <span class="hljs-keyword">$this</span>-&gt;call([
            ProjectsSeeder::class,
            TasksSeeder::class,
        ]);
    }
}
</code></pre>
<h2 id="heading-how-to-connect-to-the-mysql-database">How to Connect to the MySQL Database</h2>
<p>Before running migrations and seeds, create a MySQL database and set up the appropriate credentials in the <code>.env</code> file. If there is not a <code>.env</code>, then create it and paste the <code>.env.example</code> file's content into it.</p>
<p>After setting up the database credentials, you'll have these kinds of environment variables:</p>
<pre><code class="lang-env">DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE="&lt;DATABASE_NAME&gt;"
DB_USERNAME="&lt;USERNAME&gt;"
DB_PASSWORD="&lt;PASSWORD&gt;"
</code></pre>
<p>After setting up environment variables, optimize the cache:</p>
<pre><code class="lang-shell">php artisan optimize
</code></pre>
<p>Now you'll be able to create <code>projects</code> and <code>tasks</code> tables in the MySQL database, setup their structure, and add initial records with a single command:</p>
<pre><code class="lang-shell">php artisan migrate:fresh --seed
</code></pre>
<p>In the above command, the <code>migrate:fresh</code> argument will drop all tables from the database. Then it will execute the <code>migrate</code> command, which will run your migrations to create <code>projects</code> and <code>tasks</code> tables appropriately. </p>
<p>With the <code>--seed</code> argument, it will run <code>ProjectsSeeder</code> and <code>TasksSeeder</code> 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.</p>
<p>After running the command, you'll have these kinds of database records:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/01/image-74.png" alt="Image" width="600" height="400" loading="lazy">
<em>A screenshot from the PHPStorm IDE</em></p>
<h2 id="heading-service-injection">Service Injection</h2>
<p>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.</p>
<p>At first, use the below command to generate a controller.</p>
<pre><code class="lang-shell">php artisan make:controller TaskController
</code></pre>
<p>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. </p>
<p>Those classes are generally called <em>services</em>, and using service implementations in a controller method is called <strong>service injection</strong> (it comes from the term <em>dependency injection</em>).</p>
<p>One of the main advantages of using services is that it helps you create a maintainable codebase.</p>
<p>You can inject your service class into the controller's construction method as an argument, so after each controller execution (when a controller's <code>__construct()</code> method runs) you can initialize an object of service. This means that you can access your service's functions right in your controller.</p>
<p>Now, let's create two separate service classes, which will be used in the <code>TaskController</code>.</p>
<p>Manually create a <code>app/Services/ProjectService.php</code> service class, which will be responsible for the project-related logic.</p>
<pre><code class="lang-php"><span class="hljs-meta">&lt;?php</span>

<span class="hljs-keyword">namespace</span> <span class="hljs-title">App</span>\<span class="hljs-title">Services</span>;

<span class="hljs-keyword">use</span> <span class="hljs-title">App</span>\<span class="hljs-title">Models</span>\<span class="hljs-title">Project</span>;
<span class="hljs-keyword">use</span> <span class="hljs-title">Illuminate</span>\<span class="hljs-title">Database</span>\<span class="hljs-title">Eloquent</span>\<span class="hljs-title">Collection</span>;

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ProjectService</span>
</span>{
    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getAll</span>(<span class="hljs-params"></span>): <span class="hljs-title">Collection</span>
    </span>{
        <span class="hljs-keyword">return</span> Project::all();
    }
}
</code></pre>
<p>The second service class will be the <code>app/Services/TaskService.php</code>, which will be responsible for doing task manipulations:</p>
<pre><code class="lang-php"><span class="hljs-meta">&lt;?php</span>

<span class="hljs-keyword">namespace</span> <span class="hljs-title">App</span>\<span class="hljs-title">Services</span>;

<span class="hljs-keyword">use</span> <span class="hljs-title">App</span>\<span class="hljs-title">Models</span>\<span class="hljs-title">Task</span>;
<span class="hljs-keyword">use</span> <span class="hljs-title">Illuminate</span>\<span class="hljs-title">Support</span>\<span class="hljs-title">Facades</span>\<span class="hljs-title">DB</span>;

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">TaskService</span>
</span>{
    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">list</span>(<span class="hljs-params"><span class="hljs-keyword">int</span> $projectId</span>)
    </span>{
        <span class="hljs-keyword">return</span> Task::with(<span class="hljs-string">'project'</span>)-&gt;where(<span class="hljs-string">'project_id'</span>, $projectId)
            -&gt;orderBy(<span class="hljs-string">'priority'</span>)-&gt;get();
    }

    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getById</span>(<span class="hljs-params"><span class="hljs-keyword">int</span> $id</span>)
    </span>{
        <span class="hljs-keyword">return</span> Task::where(<span class="hljs-string">'id'</span>, $id)-&gt;with(<span class="hljs-string">'project'</span>)-&gt;first();
    }

    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">store</span>(<span class="hljs-params">$data</span>): <span class="hljs-title">void</span>
    </span>{
        $count = Task::where(<span class="hljs-string">'project_id'</span>, $data[<span class="hljs-string">'project_id'</span>])-&gt;count();
        $data[<span class="hljs-string">'priority'</span>] = $count + <span class="hljs-number">1</span>;

        Task::create($data);
    }

    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">update</span>(<span class="hljs-params"><span class="hljs-keyword">int</span> $id, <span class="hljs-keyword">array</span> $data</span>): <span class="hljs-title">void</span>
    </span>{
        $task = <span class="hljs-keyword">$this</span>-&gt;getById($id);
        <span class="hljs-keyword">if</span> (!$task) { <span class="hljs-keyword">return</span>; }

        $task-&gt;update($data);
    }

    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">delete</span>(<span class="hljs-params"><span class="hljs-keyword">int</span> $id</span>): <span class="hljs-title">void</span>
    </span>{
        $task = <span class="hljs-keyword">$this</span>-&gt;getById($id);
        <span class="hljs-keyword">if</span> (!$task) { <span class="hljs-keyword">return</span>; }

        $task-&gt;delete();

        $tasks = Task::where(<span class="hljs-string">'project_id'</span>, $task-&gt;project_id)
            -&gt;where(<span class="hljs-string">'priority'</span>, <span class="hljs-string">'&gt;'</span>, $task-&gt;priority)-&gt;get();
        <span class="hljs-keyword">if</span> ($tasks-&gt;isEmpty()) {
            <span class="hljs-keyword">return</span>;
        }

        $when_then = <span class="hljs-string">""</span>;
        $where_in = <span class="hljs-string">""</span>;
        <span class="hljs-keyword">foreach</span> ($tasks <span class="hljs-keyword">as</span> $task) {
            $when_then .= <span class="hljs-string">"WHEN "</span>.$task-&gt;id
                .<span class="hljs-string">" THEN "</span>.($task-&gt;priority - <span class="hljs-number">1</span>).<span class="hljs-string">" "</span>;
            $where_in .= $task-&gt;id.<span class="hljs-string">","</span>;
        }

        $table_name = (<span class="hljs-keyword">new</span> Task())-&gt;getTable();
        $bulk_update_query = <span class="hljs-string">"UPDATE `"</span>.$table_name
            .<span class="hljs-string">"` SET `priority` = (CASE `id` "</span>.$when_then.<span class="hljs-string">"END)"</span>
            .<span class="hljs-string">" WHERE `id` IN("</span>.substr($where_in, <span class="hljs-number">0</span>, <span class="hljs-number">-1</span>).<span class="hljs-string">");"</span>;

        <span class="hljs-comment">// there is no way to be SQL injected here</span>
        <span class="hljs-comment">// because all the values are not provided by the user</span>
        DB::update($bulk_update_query);
    }

    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">reorder</span>(<span class="hljs-params"><span class="hljs-keyword">int</span> $project_id, <span class="hljs-keyword">int</span> $start, <span class="hljs-keyword">int</span> $end</span>): <span class="hljs-title">void</span>
    </span>{
        $items = Task::where(<span class="hljs-string">'project_id'</span>, $project_id)
            -&gt;orderBy(<span class="hljs-string">'priority'</span>)-&gt;pluck(<span class="hljs-string">'priority'</span>, <span class="hljs-string">'id'</span>)-&gt;toArray();

        <span class="hljs-keyword">if</span> ($start &gt; count($items) || $end &gt; count($items)) {
            <span class="hljs-keyword">return</span>;
        }

        $ids = [];
        $priorities = [];
        <span class="hljs-keyword">foreach</span> ($items <span class="hljs-keyword">as</span> $id =&gt; $priority) {
            $ids[] = $id;
            $priorities[] = $priority;
        }

        $out_priority = array_splice($priorities, $start - <span class="hljs-number">1</span>, <span class="hljs-number">1</span>);
        array_splice($priorities, $end - <span class="hljs-number">1</span>, <span class="hljs-number">0</span>, $out_priority);

        $when_then = <span class="hljs-string">""</span>;
        $where_in = <span class="hljs-string">""</span>;
        <span class="hljs-keyword">foreach</span> ($priorities <span class="hljs-keyword">as</span> $out_k =&gt; $out_v) {
            $id = $ids[$out_v - <span class="hljs-number">1</span>];
            $when_then .= <span class="hljs-string">"WHEN "</span>.$id.<span class="hljs-string">" THEN "</span>.($out_k + <span class="hljs-number">1</span>).<span class="hljs-string">" "</span>;
            $where_in .= $id.<span class="hljs-string">","</span>;
        }

        $table_name = (<span class="hljs-keyword">new</span> Task())-&gt;getTable();
        $bulk_update_query = <span class="hljs-string">"UPDATE `"</span>.$table_name
            .<span class="hljs-string">"` SET `priority` = (CASE `id` "</span>.$when_then.<span class="hljs-string">"END)"</span>
            .<span class="hljs-string">" WHERE `id` IN("</span>.substr($where_in, <span class="hljs-number">0</span>, <span class="hljs-number">-1</span>).<span class="hljs-string">")"</span>
            .<span class="hljs-string">" AND `deleted_at` IS NULL;"</span>; <span class="hljs-comment">// soft delete</span>

        DB::update($bulk_update_query);
    }
}
</code></pre>
<p>In the above <code>TaskService</code> class, you'll use the following functions in the <code>TaskController</code>.</p>
<ul>
<li><strong>list</strong>: fetches tasks for a given project ID, including the related project, and orders them by <em>priority</em>.</li>
<li><strong>getById</strong>: retrieves a specific task by its ID, including the related project.</li>
<li><strong>store</strong>: stores a new task, calculating the <em>priority</em> based on existing tasks for the same project.</li>
<li><strong>update</strong>: updates an existing task by its ID.</li>
<li><strong>delete</strong>: deletes a task by its ID and adjusts the priorities of remaining tasks in the same project.</li>
<li><strong>reorder</strong>: changes the priorities of tasks within a project, (handles soft delete as well with <code>deleted_at IS NULL</code>).</li>
</ul>
<h2 id="heading-web-and-api-routes-in-laravel">Web and API Routes in Laravel</h2>
<p>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.</p>
<p>So now, set up a route in <code>routes/web.php</code> for the initial single-page:</p>
<pre><code class="lang-php"><span class="hljs-meta">&lt;?php</span>

<span class="hljs-keyword">use</span> <span class="hljs-title">Illuminate</span>\<span class="hljs-title">Support</span>\<span class="hljs-title">Facades</span>\<span class="hljs-title">Route</span>;
<span class="hljs-keyword">use</span> <span class="hljs-title">App</span>\<span class="hljs-title">Http</span>\<span class="hljs-title">Controllers</span>\<span class="hljs-title">TaskController</span>;

Route::group([<span class="hljs-string">'prefix'</span> =&gt; <span class="hljs-string">'/'</span>, <span class="hljs-string">'as'</span> =&gt; <span class="hljs-string">'tasks.'</span>], <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params"></span>) </span>{
    Route::get(<span class="hljs-string">'/'</span>, [TaskController::class, <span class="hljs-string">'index'</span>])-&gt;name(<span class="hljs-string">'index'</span>);
});
</code></pre>
<p>Set up API routes in <code>routes/api.php</code> like this:</p>
<pre><code class="lang-php"><span class="hljs-meta">&lt;?php</span>

<span class="hljs-keyword">use</span> <span class="hljs-title">Illuminate</span>\<span class="hljs-title">Support</span>\<span class="hljs-title">Facades</span>\<span class="hljs-title">Route</span>;
<span class="hljs-keyword">use</span> <span class="hljs-title">App</span>\<span class="hljs-title">Http</span>\<span class="hljs-title">Controllers</span>\<span class="hljs-title">TaskController</span>;

Route::group([<span class="hljs-string">'prefix'</span> =&gt; <span class="hljs-string">'/tasks'</span>, <span class="hljs-string">'as'</span> =&gt; <span class="hljs-string">'tasks.'</span>], <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params"></span>) </span>{
    Route::get(<span class="hljs-string">'/'</span>, [TaskController::class, <span class="hljs-string">'list'</span>]);
    Route::get(<span class="hljs-string">'/{id}'</span>, [TaskController::class, <span class="hljs-string">'get'</span>])
        -&gt;where(<span class="hljs-string">'id'</span>, <span class="hljs-string">'[1-9][0-9]*'</span>);
    Route::post(<span class="hljs-string">'/'</span>, [TaskController::class, <span class="hljs-string">'store'</span>]);
    Route::put(<span class="hljs-string">'/{id}'</span>, [TaskController::class, <span class="hljs-string">'update'</span>])
        -&gt;where(<span class="hljs-string">'id'</span>, <span class="hljs-string">'[1-9][0-9]*'</span>);
    Route::delete(<span class="hljs-string">'/{id}'</span>, [TaskController::class, <span class="hljs-string">'delete'</span>])
        -&gt;where(<span class="hljs-string">'id'</span>, <span class="hljs-string">'[1-9][0-9]*'</span>);
    Route::put(<span class="hljs-string">'/'</span>, [TaskController::class, <span class="hljs-string">'reorder'</span>]);
});
</code></pre>
<p>We have all the API routes in the <code>routes/api.php</code> instead of the <code>routes/web.php</code> because in the <em>web.php</em> file, all the routes by default are <a target="_blank" href="https://laravel.com/docs/10.x/routing#csrf-protection">CSRF protected</a>. So, in a stateless app, usually you won't need that – that's why <em>api.php</em> was invented in Laravel.</p>
<p>As you can see, there is a <em>"task"</em> 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.</p>
<p>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 <code>bootstrap/cache/routes-v7.php</code> file, and they won't be updated automatically right after your changes. It just generates one if it hasn't cached yet.</p>
<p>Use the below command to refresh Laravel caches as well as the route caches:</p>
<pre><code class="lang-shell">php artisan optimize
</code></pre>
<h2 id="heading-validation-requests-in-laravel">Validation Requests in Laravel</h2>
<p>Before writing controller methods, you'll need to add some validation request files. You can do that manually or by just using the <code>artisan</code> command:</p>
<pre><code class="lang-shell">php artisan make:request Task/CreateTaskRequest
php artisan make:request Task/ListTasksRequest
php artisan make:request Task/ReorderTasksRequest
php artisan make:request Task/UpdateTaskRequest
</code></pre>
<p>After creating them, you'll need to set up validation rules for each request.</p>
<p>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.</p>
<p>Laravel provides a <a target="_blank" href="https://laravel.com/docs/10.x/validation#available-validation-rules">set of rules</a> you can use to check incoming data. A field of the incoming request can have multiple rules. </p>
<p>One way to write validation rules for a single field is concatenating those rules by a "|" character.</p>
<p>Below are the validation rules for creating a new task:</p>
<pre><code class="lang-php"><span class="hljs-keyword">return</span> [
    <span class="hljs-string">'project_id'</span> =&gt; <span class="hljs-string">'required|integer|exists:projects,id'</span>,
    <span class="hljs-string">'title'</span> =&gt; <span class="hljs-string">'required|string|max:255'</span>,
    <span class="hljs-string">'description'</span> =&gt; <span class="hljs-string">'nullable|string'</span>,
];
</code></pre>
<p>Below is the validation rule for listing project tasks:</p>
<pre><code class="lang-php"><span class="hljs-keyword">return</span> [
    <span class="hljs-string">'project_id'</span> =&gt; <span class="hljs-string">'required|integer|exists:projects,id'</span>,
];
</code></pre>
<p>Below are the validation rules for tasks reordering:</p>
<pre><code class="lang-php"><span class="hljs-keyword">return</span> [
    <span class="hljs-string">'project_id'</span> =&gt; <span class="hljs-string">'required|integer|exists:projects,id'</span>,
    <span class="hljs-string">'start'</span> =&gt; <span class="hljs-string">'required|integer'</span>,
    <span class="hljs-string">'end'</span> =&gt; <span class="hljs-string">'required|integer|different:start'</span>,
];
</code></pre>
<p>Below are the validation rules for updating a task:</p>
<pre><code class="lang-php"><span class="hljs-keyword">return</span> [
    <span class="hljs-string">'title'</span> =&gt; <span class="hljs-string">'required|string|max:255'</span>,
    <span class="hljs-string">'description'</span> =&gt; <span class="hljs-string">'nullable|string'</span>,
];
</code></pre>
<p>Don't forget to return <code>true</code> in the <code>authorize()</code> method in all validation classes:</p>
<pre><code class="lang-php"><span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">authorize</span>(<span class="hljs-params"></span>): <span class="hljs-title">bool</span>
</span>{
    <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>;
}
</code></pre>
<p>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 <code>true</code> for all the cases.</p>
<h2 id="heading-how-to-write-a-controller-that-uses-services">How to Write a Controller that Uses Services</h2>
<p>As the last step in the backend part, it's time to write controller methods for each API route, which will use service functions.</p>
<pre><code class="lang-php"><span class="hljs-meta">&lt;?php</span>

<span class="hljs-keyword">namespace</span> <span class="hljs-title">App</span>\<span class="hljs-title">Http</span>\<span class="hljs-title">Controllers</span>;

<span class="hljs-keyword">use</span> <span class="hljs-title">App</span>\<span class="hljs-title">Http</span>\<span class="hljs-title">Requests</span>\<span class="hljs-title">Task</span>\<span class="hljs-title">CreateTaskRequest</span>;
<span class="hljs-keyword">use</span> <span class="hljs-title">App</span>\<span class="hljs-title">Http</span>\<span class="hljs-title">Requests</span>\<span class="hljs-title">Task</span>\<span class="hljs-title">ListTasksRequest</span>;
<span class="hljs-keyword">use</span> <span class="hljs-title">App</span>\<span class="hljs-title">Http</span>\<span class="hljs-title">Requests</span>\<span class="hljs-title">Task</span>\<span class="hljs-title">ReorderTasksRequest</span>;
<span class="hljs-keyword">use</span> <span class="hljs-title">App</span>\<span class="hljs-title">Http</span>\<span class="hljs-title">Requests</span>\<span class="hljs-title">Task</span>\<span class="hljs-title">UpdateTaskRequest</span>;
<span class="hljs-keyword">use</span> <span class="hljs-title">App</span>\<span class="hljs-title">Services</span>\<span class="hljs-title">ProjectService</span>;
<span class="hljs-keyword">use</span> <span class="hljs-title">App</span>\<span class="hljs-title">Services</span>\<span class="hljs-title">TaskService</span>;
<span class="hljs-keyword">use</span> <span class="hljs-title">Illuminate</span>\<span class="hljs-title">Http</span>\<span class="hljs-title">JsonResponse</span>;

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">TaskController</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">Controller</span>
</span>{
    <span class="hljs-keyword">protected</span> ?TaskService $taskService = <span class="hljs-literal">null</span>;

    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">__construct</span>(<span class="hljs-params">TaskService $taskService</span>)
    </span>{
        <span class="hljs-keyword">$this</span>-&gt;taskService = $taskService;
    }

    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">index</span>(<span class="hljs-params"></span>)
    </span>{
        $projects = (<span class="hljs-keyword">new</span> ProjectService())-&gt;getAll();

        <span class="hljs-keyword">return</span> view(<span class="hljs-string">'tasks.index'</span>, [
            <span class="hljs-string">'projects'</span> =&gt; $projects,
        ]);
    }

    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">list</span>(<span class="hljs-params">ListTasksRequest $request</span>): <span class="hljs-title">JsonResponse</span>
    </span>{
        $tasks = <span class="hljs-keyword">$this</span>-&gt;taskService-&gt;list($request-&gt;get(<span class="hljs-string">'project_id'</span>));

        <span class="hljs-keyword">return</span> response()-&gt;json([
            <span class="hljs-string">'success'</span> =&gt; <span class="hljs-literal">true</span>,
            <span class="hljs-string">'tasks'</span> =&gt; $tasks,
            <span class="hljs-string">'message'</span> =&gt; <span class="hljs-string">"Tasks retrieved successfully."</span>,
        ]); <span class="hljs-comment">// 200</span>
    }

    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">store</span>(<span class="hljs-params">CreateTaskRequest $request</span>): <span class="hljs-title">JsonResponse</span>
    </span>{
        <span class="hljs-keyword">$this</span>-&gt;taskService-&gt;store($request-&gt;all());

        <span class="hljs-keyword">return</span> response()-&gt;json([
            <span class="hljs-string">'success'</span> =&gt; <span class="hljs-literal">true</span>,
            <span class="hljs-string">'message'</span> =&gt; <span class="hljs-string">"Task created successfully."</span>,
        ], <span class="hljs-number">201</span>);
    }

    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">get</span>(<span class="hljs-params"><span class="hljs-keyword">int</span> $id</span>): <span class="hljs-title">JsonResponse</span>
    </span>{
        $task = <span class="hljs-keyword">$this</span>-&gt;taskService-&gt;getById($id);

        <span class="hljs-keyword">if</span> ($task) {
            <span class="hljs-keyword">return</span> response()-&gt;json([
                <span class="hljs-string">'success'</span> =&gt; <span class="hljs-literal">true</span>,
                <span class="hljs-string">'task'</span> =&gt; $task,
                <span class="hljs-string">'message'</span> =&gt; <span class="hljs-string">"Task retrieved successfully."</span>,
            ]); <span class="hljs-comment">// 200</span>
        } <span class="hljs-keyword">else</span> {
            <span class="hljs-keyword">return</span> response()-&gt;json([
                <span class="hljs-string">'success'</span> =&gt; <span class="hljs-literal">false</span>,
                <span class="hljs-string">'message'</span> =&gt; <span class="hljs-string">"Task not found!"</span>,
            ], <span class="hljs-number">404</span>);
        }
    }

    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">update</span>(<span class="hljs-params">UpdateTaskRequest $request, <span class="hljs-keyword">int</span> $id</span>): <span class="hljs-title">JsonResponse</span>
    </span>{
        <span class="hljs-keyword">$this</span>-&gt;taskService-&gt;update($id, $request-&gt;all());

        <span class="hljs-keyword">return</span> response()-&gt;json([
            <span class="hljs-string">'success'</span> =&gt; <span class="hljs-literal">true</span>,
            <span class="hljs-string">'message'</span> =&gt; <span class="hljs-string">"Task updated successfully."</span>,
        ], <span class="hljs-number">201</span>);
    }

    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">delete</span>(<span class="hljs-params"><span class="hljs-keyword">int</span> $id</span>): <span class="hljs-title">JsonResponse</span>
    </span>{
        <span class="hljs-keyword">$this</span>-&gt;taskService-&gt;delete($id);

        <span class="hljs-keyword">return</span> response()-&gt;json([
            <span class="hljs-string">'success'</span> =&gt; <span class="hljs-literal">true</span>,
            <span class="hljs-string">'message'</span> =&gt; <span class="hljs-string">"Task deleted successfully."</span>,
        ], <span class="hljs-number">201</span>);
    }

    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">reorder</span>(<span class="hljs-params">ReorderTasksRequest $request</span>): <span class="hljs-title">JsonResponse</span>
    </span>{
        <span class="hljs-keyword">$this</span>-&gt;taskService-&gt;reorder(
            $request-&gt;get(<span class="hljs-string">'project_id'</span>),
            $request-&gt;get(<span class="hljs-string">'start'</span>),
            $request-&gt;get(<span class="hljs-string">'end'</span>)
        );

        <span class="hljs-keyword">return</span> response()-&gt;json([
            <span class="hljs-string">'success'</span> =&gt; <span class="hljs-literal">true</span>,
            <span class="hljs-string">'message'</span> =&gt; <span class="hljs-string">"Tasks reordered successfully."</span>,
        ], <span class="hljs-number">201</span>);
    }
}
</code></pre>
<p>As you can see in the <code>TaskController:</code></p>
<ul>
<li><code>TaskService</code> is injected into the constructor method as an argument. In the constructor body, an instance of the <code>TaskService</code> class is created, and the <code>$taskService</code> property is initialized. So in the custom methods, you'll be able to access that <code>$taskService</code> and its functions.</li>
<li>The <code>index</code> method is for returning the HTML.</li>
<li>All the other custom methods ( <code>list</code>, <code>store</code>, <code>get</code>, <code>update</code>, <code>delete</code>, <code>reorder</code>) are using the <code>TaskService</code> functions through the already initialized <code>$taskService</code> property. So, all the logic implementation goes to the service, and this way, you just call a service function and return the response.</li>
</ul>
<h2 id="heading-how-to-test-the-api-routes">How to Test the API Routes</h2>
<p>At this point, you can test the API routes by requesting them via Postman or any similar tool. Just run (or rerun) the backend:</p>
<pre><code class="lang-shell">php artisan serve
</code></pre>
<p>Here's the published <a target="_blank" href="https://documenter.getpostman.com/view/1747137/2s9YsJArg7">Postman collection</a> with all the requests.</p>
<p>Instead of using Postman, you can use a command line tool such as <em>curl</em> right from your terminal.</p>
<p>Below are all the sample commands that you can run to test out the API routes:</p>
<ul>
<li>Create a new task for a specific project:</li>
</ul>
<pre><code class="lang-shell">curl --location '127.0.0.1:8000/api/tasks?project_id=1' \
--header 'Content-Type: application/json' \
--data '{
    "title": "Title",
    "description": "Description"
}'
</code></pre>
<ul>
<li>List project tasks:</li>
</ul>
<pre><code class="lang-shell">curl --location 'http://127.0.0.1:8000/api/tasks?project_id=1'
</code></pre>
<ul>
<li>Get a task by ID:</li>
</ul>
<pre><code class="lang-shell">curl --location 'http://127.0.0.1:8000/api/tasks/1'
</code></pre>
<ul>
<li>Update a task by ID:</li>
</ul>
<pre><code class="lang-shell">curl --location --request PUT 'http://127.0.0.1:8000/api/tasks/11' \
--header 'Content-Type: application/json' \
--data '{
    "title": "Title edited",
    "description": "Description edited"
}'
</code></pre>
<ul>
<li>Reorder project tasks:</li>
</ul>
<pre><code class="lang-shell">curl --location --request PUT 'http://127.0.0.1:8000/api/tasks' \
--header 'Content-Type: application/json' \
--data '{
    "project_id": "1",
    "start": "1",
    "end": "2"
}'
</code></pre>
<ul>
<li>Delete a task by ID:</li>
</ul>
<pre><code class="lang-shell">curl --location --request DELETE 'http://127.0.0.1:8000/api/tasks/11'
</code></pre>
<p>In the below screenshot, there is an example of getting project tasks by its ID using the <em>curl</em> command (at the bottom right):</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/01/image-84.png" alt="Image" width="600" height="400" loading="lazy">
<em>An example of using <strong>curl</strong> command to retrieve the existing tasks from the database</em></p>
<h2 id="heading-the-frontend-how-to-install-the-packages">The Frontend: How to Install the Packages</h2>
<p>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.</p>
<p>First, make sure you have Node.js version 18 or above by using this command:</p>
<pre><code class="lang-shell">node -v
</code></pre>
<p>Install these necessary npm packages:</p>
<pre><code class="lang-shell">npm i react-dom dotenv react-beautiful-dnd react-responsive-modal react-toastify @vitejs/plugin-react
</code></pre>
<ul>
<li><strong><code>react-dom</code></strong> is a library from the React team for rendering React components in the DOM (Document Object Model)</li>
<li><strong><code>dotenv</code></strong> is for loading environment variables from the <code>.env</code> file into the process environment</li>
<li><strong><code>react-beautiful-dnd</code></strong> is a library from Atlassian for creating drag-and-drop interfaces with animations</li>
<li><strong><code>react-responsive-modal</code></strong> is for creating simple and responsive modal dialogs</li>
<li><strong><code>react-toastify</code></strong> is for displaying notifications or toasts</li>
<li><strong><code>@vitejs/plugin-react</code></strong> is a plugin for the Vite build tool that enables seamless integration of React with fast development and optimized production builds</li>
</ul>
<p>Install the development dependencies with this command:</p>
<pre><code class="lang-shell">npm i -D @types/react-dom @types/react-beautiful-dnd
</code></pre>
<ul>
<li><strong><code>@types/react-dom</code></strong> is TypeScript type definitions for the <code>react-dom</code> package</li>
<li><strong><code>@types/react-beautiful-dnd</code></strong> is TypeScript type definitions for the <code>react-beautiful-dnd</code> package</li>
</ul>
<h2 id="heading-how-to-configure-vitejs">How to Configure Vite.js</h2>
<p>As Laravel v10 already has <code>vite.config.js</code>, you'll want to set up any React-related stuff there. Or if you still don't have this file, create one like this:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { defineConfig } <span class="hljs-keyword">from</span> <span class="hljs-string">'vite'</span>;
<span class="hljs-keyword">import</span> laravel <span class="hljs-keyword">from</span> <span class="hljs-string">'laravel-vite-plugin'</span>;
<span class="hljs-keyword">import</span> react <span class="hljs-keyword">from</span> <span class="hljs-string">'@vitejs/plugin-react'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">'dotenv/config'</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> defineConfig({
    <span class="hljs-attr">build</span>: {
        <span class="hljs-attr">minify</span>: process.env.APP_ENV === <span class="hljs-string">'production'</span> ? <span class="hljs-string">'esbuild'</span> : <span class="hljs-literal">false</span>,
        <span class="hljs-attr">cssMinify</span>: process.env.APP_ENV === <span class="hljs-string">'production'</span>,
    },
    <span class="hljs-attr">plugins</span>: [
        laravel({
            <span class="hljs-attr">input</span>: [<span class="hljs-string">'resources/react/app.tsx'</span>],
            <span class="hljs-attr">refresh</span>: <span class="hljs-literal">true</span>,
        }),
        react(),
    ],
});
</code></pre>
<p>As you can see in the Vite configuration file, there is a reference to the <code>resources/react/app.tsx</code>, which will be the entry point for Laravel to use React resources.</p>
<p>For the initial HTML page, create a <code>resources/views/tasks/index.blade.php</code> blade file, so all the frontend assets will be injected there in the <code>div</code> with ID <code>app</code>:</p>
<pre><code class="lang-php">&lt;!DOCTYPE html&gt;
&lt;html lang=<span class="hljs-string">"{{ str_replace('_', '-', app()-&gt;getLocale()) }}"</span>&gt;
&lt;head&gt;
    &lt;meta charset=<span class="hljs-string">"utf-8"</span>&gt;
    &lt;meta name=<span class="hljs-string">"viewport"</span> content=<span class="hljs-string">"width=device-width, initial-scale=1"</span>&gt;
    &lt;title&gt;{{ config(<span class="hljs-string">"app.name"</span>) }}&lt;/title&gt;
    &lt;link rel=<span class="hljs-string">"shortcut icon"</span> href=<span class="hljs-string">"{{ asset('favicon.ico') }}"</span> /&gt;
    &lt;link rel=<span class="hljs-string">"stylesheet"</span> href=<span class="hljs-string">"https://fonts.googleapis.com/css?family=Montserrat"</span>&gt;
    &lt;link rel=<span class="hljs-string">"stylesheet"</span> href=<span class="hljs-string">"https://fonts.googleapis.com/icon?family=Material+Icons"</span>&gt;
    @viteReactRefresh
    @vite(<span class="hljs-string">'resources/react/app.tsx'</span>)
&lt;/head&gt;
&lt;body&gt;
&lt;div id=<span class="hljs-string">"app"</span> data-projects=<span class="hljs-string">"{{ json_encode(<span class="hljs-subst">$projects</span>) }}"</span>&gt;&lt;/div&gt;
&lt;/body&gt;
&lt;/html&gt;
</code></pre>
<p>As you can see in the blade file, there is a <code>$projects</code> variable passed from the backend. It's the whole project data that will be used to filter tasks in the frontend.</p>
<h2 id="heading-reactjs-initial-integration">React.js – Initial Integration</h2>
<p>In this article, we'll just have a basic React.js app working with Laravel.</p>
<p>At first, it's a good idea to delete unnecessary resources, like default <code>resources/css</code> and <code>resources/js</code> directories.</p>
<p>Create a <code>resources/react/app.tsx</code> file like this:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> ReactDOM <span class="hljs-keyword">from</span> <span class="hljs-string">'react-dom/client'</span>;
<span class="hljs-keyword">import</span> Main <span class="hljs-keyword">from</span> <span class="hljs-string">"./Main"</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">'./index.css'</span>

ReactDOM.createRoot(<span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">'app'</span>)).render(
    &lt;Main /&gt;
);
</code></pre>
<p>So, the <code>resources/react</code> folder will be the root directory for all the upcoming React stuff.</p>
<p>Create an <code>index.css</code> with some temporary content:</p>
<pre><code class="lang-css"><span class="hljs-selector-class">.test-class</span> {
  <span class="hljs-attribute">color</span>: red;
}
</code></pre>
<p>Also create a <code>Main.tsx</code> with some temporary content:</p>
<pre><code class="lang-typescript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Main</span>(<span class="hljs-params"></span>) </span>{
    <span class="hljs-keyword">return</span> (
        &lt;div&gt;
            &lt;h2 className=<span class="hljs-string">"test-class"</span>&gt;React App&lt;/h2&gt;
        &lt;/div&gt;
    );
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> Main;
</code></pre>
<p>To check the result in the browser, make sure you have backend running and build the assets via the <code>vite</code> tool:</p>
<pre><code class="lang-shell">npm run build
</code></pre>
<p>Or, if you want to watch React file changes and automatically build assets, you can keep this command running:</p>
<pre><code class="lang-shell">npm run dev
</code></pre>
<p>The two <code>npm run</code> commands above refer to <code>vite</code>, which builds the assets.<br>You can see this by checking the <code>package.json</code> file, <em>"scripts"</em> field:</p>
<pre><code class="lang-json"><span class="hljs-string">"scripts"</span>: {
    <span class="hljs-attr">"dev"</span>: <span class="hljs-string">"vite"</span>,
    <span class="hljs-attr">"build"</span>: <span class="hljs-string">"vite build"</span>
}
</code></pre>
<p>Now you can open <em><a target="_blank" href="http://localhost:8000">http://localhost:8000</a></em> to see the initial rendered view:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/01/image-85.png" alt="Image" width="600" height="400" loading="lazy">
<em>Screenshot from browser</em></p>
<h2 id="heading-how-to-add-css">How to Add CSS</h2>
<p>Now, once you've set up Vite and have React integrated into your Laravel app, you can work on the React part.</p>
<p>We won't spend too much time on styles, so you can paste this CSS into your <code>index.css</code>:</p>
<pre><code class="lang-css"><span class="hljs-selector-tag">body</span> { <span class="hljs-attribute">background-color</span>: whitesmoke; <span class="hljs-attribute">color</span>: <span class="hljs-built_in">rgba</span>(<span class="hljs-number">255</span>, <span class="hljs-number">255</span>, <span class="hljs-number">255</span>, <span class="hljs-number">0.7</span>); <span class="hljs-attribute">font-family</span>: <span class="hljs-string">"Montserrat"</span>, sans-serif; <span class="hljs-attribute">cursor</span>: default; <span class="hljs-attribute">margin</span>: auto <span class="hljs-number">0</span>; }

<span class="hljs-comment">/* MODAL start */</span>
<span class="hljs-selector-class">.modal-content</span> { <span class="hljs-attribute">display</span>: flex; <span class="hljs-attribute">flex-direction</span>: column; <span class="hljs-attribute">justify-content</span>: center; <span class="hljs-attribute">align-items</span>: center; <span class="hljs-attribute">background-color</span>: <span class="hljs-number">#fff</span>; <span class="hljs-attribute">padding</span>: <span class="hljs-number">20px</span>; <span class="hljs-attribute">border-radius</span>: <span class="hljs-number">5px</span>; <span class="hljs-attribute">width</span>: <span class="hljs-number">500px</span>; <span class="hljs-attribute">color</span>: <span class="hljs-number">#2d3748</span>; }
<span class="hljs-selector-class">.modal-header</span> { <span class="hljs-attribute">font-size</span>: <span class="hljs-number">1.5rem</span>; <span class="hljs-attribute">font-weight</span>: <span class="hljs-number">600</span>; <span class="hljs-attribute">margin-bottom</span>: <span class="hljs-number">20px</span>; }
<span class="hljs-selector-class">.modal-input-header</span> { <span class="hljs-attribute">font-weight</span>: <span class="hljs-number">600</span>; <span class="hljs-attribute">margin-bottom</span>: <span class="hljs-number">10px</span>; }
<span class="hljs-selector-class">.modal-input</span> { <span class="hljs-attribute">width</span>: <span class="hljs-number">100%</span>; <span class="hljs-attribute">height</span>: <span class="hljs-number">30px</span>; <span class="hljs-attribute">border</span>: <span class="hljs-number">1px</span> solid <span class="hljs-number">#ccc</span>; <span class="hljs-attribute">border-radius</span>: <span class="hljs-number">5px</span>; <span class="hljs-attribute">padding</span>: <span class="hljs-number">5px</span>; }
<span class="hljs-selector-class">.modal-textarea</span> { <span class="hljs-attribute">width</span>: <span class="hljs-number">100%</span>; <span class="hljs-attribute">height</span>: <span class="hljs-number">100px</span>; <span class="hljs-attribute">border</span>: <span class="hljs-number">1px</span> solid <span class="hljs-number">#ccc</span>; <span class="hljs-attribute">border-radius</span>: <span class="hljs-number">5px</span>; <span class="hljs-attribute">padding</span>: <span class="hljs-number">5px</span>; <span class="hljs-attribute">margin-bottom</span>: <span class="hljs-number">20px</span>; <span class="hljs-attribute">resize</span>: vertical; }
<span class="hljs-selector-class">.modal-actions</span> { <span class="hljs-attribute">display</span>: flex; <span class="hljs-attribute">justify-content</span>: space-between; <span class="hljs-attribute">align-items</span>: center; <span class="hljs-attribute">width</span>: <span class="hljs-number">100%</span>; }
<span class="hljs-selector-class">.modal-btn</span> { <span class="hljs-attribute">padding</span>: <span class="hljs-number">10px</span> <span class="hljs-number">20px</span>; <span class="hljs-attribute">border-radius</span>: <span class="hljs-number">5px</span>; <span class="hljs-attribute">cursor</span>: pointer; }
<span class="hljs-selector-class">.modal-btn-cancel</span> { <span class="hljs-attribute">background-color</span>: <span class="hljs-number">#e53e3e</span>; <span class="hljs-attribute">color</span>: <span class="hljs-number">#fff</span>; <span class="hljs-attribute">border</span>: none; }
<span class="hljs-selector-class">.modal-btn-cancel</span><span class="hljs-selector-pseudo">:hover</span> { <span class="hljs-attribute">background-color</span>: <span class="hljs-number">#c53030</span>; }
<span class="hljs-selector-class">.modal-btn-submit</span> { <span class="hljs-attribute">background-color</span>: <span class="hljs-number">#2d3748</span>; <span class="hljs-attribute">color</span>: <span class="hljs-number">#fff</span>; <span class="hljs-attribute">border</span>: none; }
<span class="hljs-selector-class">.modal-btn-submit</span><span class="hljs-selector-pseudo">:hover</span> { <span class="hljs-attribute">background-color</span>: <span class="hljs-number">#4a5568</span>; }
<span class="hljs-selector-class">.modal-question</span> { <span class="hljs-attribute">font-size</span>: <span class="hljs-number">1.2rem</span>; <span class="hljs-attribute">font-weight</span>: <span class="hljs-number">600</span>; <span class="hljs-attribute">margin-bottom</span>: <span class="hljs-number">20px</span>; }
<span class="hljs-comment">/* MODAL end */</span>

<span class="hljs-comment">/* LEFT &amp; RIGHT SIDE start */</span>
<span class="hljs-selector-class">.left-side</span> { <span class="hljs-attribute">width</span>: <span class="hljs-number">50%</span>; <span class="hljs-attribute">float</span>: left; }
<span class="hljs-selector-class">.right-side</span> { <span class="hljs-attribute">width</span>: <span class="hljs-number">50%</span>; <span class="hljs-attribute">float</span>: left; }
<span class="hljs-comment">/* LEFT &amp; RIGHT SIDE end */</span>

<span class="hljs-comment">/* LEFT SIDE start */</span>
<span class="hljs-selector-class">.left-side</span> <span class="hljs-selector-class">.no-tasks</span> { <span class="hljs-attribute">font-size</span>: <span class="hljs-number">1.2rem</span>; <span class="hljs-attribute">font-weight</span>: <span class="hljs-number">600</span>; <span class="hljs-attribute">margin-bottom</span>: <span class="hljs-number">20px</span>; <span class="hljs-attribute">text-align</span>: center; <span class="hljs-attribute">color</span>: <span class="hljs-number">#2d3748</span>; }
<span class="hljs-selector-class">.left-side</span> <span class="hljs-selector-class">.task-item</span> { <span class="hljs-attribute">padding</span>: <span class="hljs-number">10px</span>; <span class="hljs-attribute">margin</span>: <span class="hljs-number">10px</span>; <span class="hljs-attribute">min-height</span>: <span class="hljs-number">20px</span>; <span class="hljs-attribute">min-width</span>: <span class="hljs-number">200px</span>; <span class="hljs-attribute">color</span>: <span class="hljs-number">#2d3748</span>; <span class="hljs-attribute">list-style-type</span>: none; }
<span class="hljs-selector-class">.left-side</span> <span class="hljs-selector-class">.task-item-content</span> { <span class="hljs-attribute">display</span>: flex; <span class="hljs-attribute">justify-content</span>: space-between; <span class="hljs-attribute">align-items</span>: center; }
<span class="hljs-selector-class">.left-side</span> <span class="hljs-selector-class">.task-project</span> { <span class="hljs-attribute">display</span>: flex; <span class="hljs-attribute">flex-direction</span>: column; <span class="hljs-attribute">justify-content</span>: center; <span class="hljs-attribute">align-items</span>: center; <span class="hljs-attribute">margin-right</span>: <span class="hljs-number">10px</span>; }
<span class="hljs-selector-class">.left-side</span> <span class="hljs-selector-class">.task-project-name</span> { <span class="hljs-attribute">font-size</span>: <span class="hljs-number">1.5rem</span>; <span class="hljs-attribute">font-weight</span>: <span class="hljs-number">600</span>; <span class="hljs-attribute">margin-bottom</span>: <span class="hljs-number">5px</span>; }
<span class="hljs-selector-class">.left-side</span> <span class="hljs-selector-class">.task-time</span> { <span class="hljs-attribute">font-size</span>: <span class="hljs-number">0.8rem</span>; }
<span class="hljs-selector-class">.left-side</span> <span class="hljs-selector-class">.task-title</span> { <span class="hljs-attribute">font-size</span>: <span class="hljs-number">1.2rem</span>; }
<span class="hljs-selector-class">.left-side</span> <span class="hljs-selector-class">.task-actions</span> { <span class="hljs-attribute">display</span>: flex; <span class="hljs-attribute">justify-content</span>: space-between; <span class="hljs-attribute">align-items</span>: center; }
<span class="hljs-selector-class">.left-side</span> <span class="hljs-selector-class">.task-edit-btn</span> { <span class="hljs-attribute">background-color</span>: <span class="hljs-number">#2d3748</span>; <span class="hljs-attribute">color</span>: <span class="hljs-number">#fff</span>; <span class="hljs-attribute">border</span>: none; <span class="hljs-attribute">border-radius</span>: <span class="hljs-number">5px</span>; <span class="hljs-attribute">padding</span>: <span class="hljs-number">5px</span> <span class="hljs-number">10px</span>; <span class="hljs-attribute">cursor</span>: pointer; <span class="hljs-attribute">margin</span>: <span class="hljs-number">0</span> <span class="hljs-number">5px</span>; }
<span class="hljs-selector-class">.left-side</span> <span class="hljs-selector-class">.task-edit-btn</span><span class="hljs-selector-pseudo">:hover</span> { <span class="hljs-attribute">background-color</span>: <span class="hljs-number">#4a5568</span>; }
<span class="hljs-selector-class">.left-side</span> <span class="hljs-selector-class">.task-delete-btn</span> { <span class="hljs-attribute">background-color</span>: <span class="hljs-number">#e53e3e</span>; <span class="hljs-attribute">color</span>: <span class="hljs-number">#fff</span>; <span class="hljs-attribute">border</span>: none; <span class="hljs-attribute">border-radius</span>: <span class="hljs-number">5px</span>; <span class="hljs-attribute">padding</span>: <span class="hljs-number">5px</span> <span class="hljs-number">10px</span>; <span class="hljs-attribute">cursor</span>: pointer; <span class="hljs-attribute">margin</span>: <span class="hljs-number">0</span> <span class="hljs-number">5px</span>; }
<span class="hljs-selector-class">.left-side</span> <span class="hljs-selector-class">.task-delete-btn</span><span class="hljs-selector-pseudo">:hover</span> { <span class="hljs-attribute">background-color</span>: <span class="hljs-number">#c53030</span>; }
<span class="hljs-comment">/* LEFT SIDE end */</span>

<span class="hljs-comment">/* RIGHT SIDE start */</span>
<span class="hljs-selector-class">.right-side</span> <span class="hljs-selector-class">.projects</span> { <span class="hljs-attribute">display</span>: flex; <span class="hljs-attribute">justify-content</span>: space-between; <span class="hljs-attribute">align-items</span>: center; <span class="hljs-attribute">margin-bottom</span>: <span class="hljs-number">20px</span>; }
<span class="hljs-selector-class">.right-side</span> <span class="hljs-selector-class">.projects-select</span> { <span class="hljs-attribute">width</span>: <span class="hljs-number">100%</span>; <span class="hljs-attribute">height</span>: <span class="hljs-number">30px</span>; <span class="hljs-attribute">border</span>: <span class="hljs-number">1px</span> solid <span class="hljs-number">#ccc</span>; <span class="hljs-attribute">border-radius</span>: <span class="hljs-number">5px</span>; <span class="hljs-attribute">padding</span>: <span class="hljs-number">5px</span>; }
<span class="hljs-selector-class">.right-side</span> <span class="hljs-selector-class">.no-project-selected</span> { <span class="hljs-attribute">font-size</span>: <span class="hljs-number">1.2rem</span>; <span class="hljs-attribute">font-weight</span>: <span class="hljs-number">600</span>; <span class="hljs-attribute">margin-bottom</span>: <span class="hljs-number">20px</span>; }
<span class="hljs-selector-class">.right-side</span> <span class="hljs-selector-class">.right-side-wrapper</span> { <span class="hljs-attribute">display</span>: flex; <span class="hljs-attribute">flex-direction</span>: column; <span class="hljs-attribute">justify-content</span>: center; <span class="hljs-attribute">align-items</span>: center; <span class="hljs-attribute">background-color</span>: <span class="hljs-number">#fff</span>; <span class="hljs-attribute">padding</span>: <span class="hljs-number">20px</span>; <span class="hljs-attribute">border-radius</span>: <span class="hljs-number">5px</span>; <span class="hljs-attribute">color</span>: <span class="hljs-number">#2d3748</span>; }
<span class="hljs-selector-class">.right-side</span> <span class="hljs-selector-class">.add-task-header</span> { <span class="hljs-attribute">font-size</span>: <span class="hljs-number">1.5rem</span>; <span class="hljs-attribute">font-weight</span>: <span class="hljs-number">600</span>; <span class="hljs-attribute">margin-bottom</span>: <span class="hljs-number">20px</span>; }
<span class="hljs-selector-class">.right-side</span> <span class="hljs-selector-class">.add-task-input-header</span> { <span class="hljs-attribute">font-weight</span>: <span class="hljs-number">600</span>; <span class="hljs-attribute">margin-bottom</span>: <span class="hljs-number">10px</span>; }
<span class="hljs-selector-class">.right-side</span> <span class="hljs-selector-class">.add-task-input</span> { <span class="hljs-attribute">width</span>: <span class="hljs-number">100%</span>; <span class="hljs-attribute">height</span>: <span class="hljs-number">30px</span>; <span class="hljs-attribute">border</span>: <span class="hljs-number">1px</span> solid <span class="hljs-number">#ccc</span>; <span class="hljs-attribute">border-radius</span>: <span class="hljs-number">5px</span>; <span class="hljs-attribute">padding</span>: <span class="hljs-number">5px</span>; }
<span class="hljs-selector-class">.right-side</span> <span class="hljs-selector-class">.add-task-textarea</span> { <span class="hljs-attribute">width</span>: <span class="hljs-number">100%</span>; <span class="hljs-attribute">height</span>: <span class="hljs-number">100px</span>; <span class="hljs-attribute">border</span>: <span class="hljs-number">1px</span> solid <span class="hljs-number">#ccc</span>; <span class="hljs-attribute">border-radius</span>: <span class="hljs-number">5px</span>; <span class="hljs-attribute">padding</span>: <span class="hljs-number">5px</span>; <span class="hljs-attribute">margin-bottom</span>: <span class="hljs-number">20px</span>; <span class="hljs-attribute">resize</span>: vertical; }
<span class="hljs-selector-class">.right-side</span> <span class="hljs-selector-class">.add-task-actions</span> { <span class="hljs-attribute">display</span>: flex; <span class="hljs-attribute">justify-content</span>: space-between; <span class="hljs-attribute">align-items</span>: center; <span class="hljs-attribute">width</span>: <span class="hljs-number">100%</span>; }
<span class="hljs-selector-class">.right-side</span> <span class="hljs-selector-class">.add-task-btn</span> { <span class="hljs-attribute">padding</span>: <span class="hljs-number">10px</span> <span class="hljs-number">20px</span>; <span class="hljs-attribute">border-radius</span>: <span class="hljs-number">5px</span>; <span class="hljs-attribute">cursor</span>: pointer; }
<span class="hljs-selector-class">.right-side</span> <span class="hljs-selector-class">.add-task-btn-cancel</span> { <span class="hljs-attribute">background-color</span>: <span class="hljs-number">#e53e3e</span>; <span class="hljs-attribute">color</span>: <span class="hljs-number">#fff</span>; <span class="hljs-attribute">border</span>: none; }
<span class="hljs-selector-class">.right-side</span> <span class="hljs-selector-class">.add-task-btn-cancel</span><span class="hljs-selector-pseudo">:hover</span> { <span class="hljs-attribute">background-color</span>: <span class="hljs-number">#c53030</span>; }
<span class="hljs-selector-class">.right-side</span> <span class="hljs-selector-class">.add-task-btn-submit</span> { <span class="hljs-attribute">background-color</span>: <span class="hljs-number">#2d3748</span>; <span class="hljs-attribute">color</span>: <span class="hljs-number">#fff</span>; <span class="hljs-attribute">border</span>: none; }
<span class="hljs-selector-class">.right-side</span> <span class="hljs-selector-class">.add-task-btn-submit</span><span class="hljs-selector-pseudo">:hover</span> { <span class="hljs-attribute">background-color</span>: <span class="hljs-number">#4a5568</span>; }
<span class="hljs-comment">/* RIGHT SIDE end */</span>
</code></pre>
<p>Later you'll attach the <code>index.css</code> file in your main component.</p>
<h2 id="heading-a-service-for-the-api-requests">A Service for the API Requests</h2>
<p>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 <code>utils.ts</code>, as there will be utilities in it we need.</p>
<p>Before that, just create <code>axiosConfig.ts</code> for the global Axios configuration, which you'll use in <code>utils.ts</code>.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> axios <span class="hljs-keyword">from</span> <span class="hljs-string">'axios'</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> axios.create({ baseURL: <span class="hljs-string">'/api'</span> });
</code></pre>
<p>Using the above setup, you can be sure that all the HTTP requests will have the <code>/api</code> prefix.</p>
<p>For example, if you use <code>axiosConfig.get('/example')</code>, it will send a GET request to the <code>/api/example</code>. This is an optional configuration, but it's a recommended way to have non-repetitive code.</p>
<p>As you'll have a few use cases for sending HTTP requests to the server, you can have separate utilities file for those operations:</p>
<ul>
<li>Create a new task for a project</li>
<li>Update a task</li>
<li>List project's tasks</li>
<li>Delete a task</li>
<li>Reorder project's tasks</li>
</ul>
<p>So below is the <code>utils.ts</code> file:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> axiosConfig <span class="hljs-keyword">from</span> <span class="hljs-string">'./axiosConfig'</span>;
<span class="hljs-keyword">import</span> { toast } <span class="hljs-keyword">from</span> <span class="hljs-string">'react-toastify'</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> getErrorMessage = <span class="hljs-function">(<span class="hljs-params">error: unknown</span>) =&gt;</span> {
    <span class="hljs-keyword">if</span> (error <span class="hljs-keyword">instanceof</span> <span class="hljs-built_in">Error</span>) <span class="hljs-keyword">return</span> error.message;
    <span class="hljs-keyword">return</span> <span class="hljs-built_in">String</span>(error)
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> getTasks = <span class="hljs-keyword">async</span> (projectId) =&gt; {
    <span class="hljs-keyword">if</span> (!projectId) {
        toast.error(<span class="hljs-string">"Project is required!"</span>);
        <span class="hljs-keyword">return</span>;
    }

    <span class="hljs-keyword">try</span> {
        <span class="hljs-keyword">const</span> response = <span class="hljs-keyword">await</span> axiosConfig.get(<span class="hljs-string">`/tasks?project_id=<span class="hljs-subst">${projectId}</span>`</span>);
        <span class="hljs-keyword">const</span> { success, tasks, message } = response.data;

        <span class="hljs-keyword">if</span> (success) {
            <span class="hljs-keyword">return</span> tasks;
        } <span class="hljs-keyword">else</span> {
            toast.error(message);
            <span class="hljs-keyword">return</span> [];
        }
    } <span class="hljs-keyword">catch</span> (err) {
        toast.error(getErrorMessage(err));
        <span class="hljs-keyword">return</span> [];
    }
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> reorderTasks = <span class="hljs-keyword">async</span> (projectId, start, end) =&gt; {
    <span class="hljs-keyword">try</span> {
        <span class="hljs-keyword">const</span> response = <span class="hljs-keyword">await</span> axiosConfig.put(<span class="hljs-string">'/tasks'</span>, {
            project_id: projectId,
            start,
            end,
        });
        <span class="hljs-keyword">const</span> { success, message } = response.data;

        toast[success ? <span class="hljs-string">'success'</span> : <span class="hljs-string">'error'</span>](message);
    } <span class="hljs-keyword">catch</span> (err) {
        toast.error(getErrorMessage(err));
    }
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> editTask = <span class="hljs-keyword">async</span> (task) =&gt; {
    <span class="hljs-keyword">if</span> (!task.id) <span class="hljs-keyword">return</span>;
    <span class="hljs-keyword">if</span> (!task.title) {
        toast.error(<span class="hljs-string">"Title is required!"</span>);
        <span class="hljs-keyword">return</span>;
    }

    <span class="hljs-keyword">try</span> {
        <span class="hljs-keyword">const</span> response = <span class="hljs-keyword">await</span> axiosConfig.put(<span class="hljs-string">`/tasks/<span class="hljs-subst">${task.id}</span>`</span>, {
            title: task.title,
            description: task.description,
        });
        <span class="hljs-keyword">const</span> { success, message } = response.data;

        toast[success ? <span class="hljs-string">'success'</span> : <span class="hljs-string">'error'</span>](message);
    } <span class="hljs-keyword">catch</span> (err) {
        toast.error(getErrorMessage(err));
    }
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> deleteTask = <span class="hljs-keyword">async</span> (id) =&gt; {
    <span class="hljs-keyword">if</span> (!id) {
        toast.error(<span class="hljs-string">"Invalid task!"</span>);
        <span class="hljs-keyword">return</span>;
    }

    <span class="hljs-keyword">try</span> {
        <span class="hljs-keyword">const</span> response = <span class="hljs-keyword">await</span> axiosConfig.delete(<span class="hljs-string">`/tasks/<span class="hljs-subst">${id}</span>`</span>);
        <span class="hljs-keyword">const</span> { success, message } = response.data;

        toast[success ? <span class="hljs-string">'success'</span> : <span class="hljs-string">'error'</span>](message);
    } <span class="hljs-keyword">catch</span> (err) {
        toast.error(getErrorMessage(err));
    }
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> createTask = <span class="hljs-keyword">async</span> (task, projectId) =&gt; {
    <span class="hljs-keyword">if</span> (!projectId) {
        toast.error(<span class="hljs-string">"Project is required!"</span>);
        <span class="hljs-keyword">return</span>;
    }
    <span class="hljs-keyword">if</span> (!task.title) {
        toast.error(<span class="hljs-string">"Title is required!"</span>);
        <span class="hljs-keyword">return</span>;
    }

    <span class="hljs-keyword">try</span> {
        <span class="hljs-keyword">const</span> response = <span class="hljs-keyword">await</span> axiosConfig.post(<span class="hljs-string">`/tasks?project_id=<span class="hljs-subst">${projectId}</span>`</span>, {
            title: task.title,
            description: task.description,
        });
        <span class="hljs-keyword">const</span> { success, message } = response.data;

        toast[success ? <span class="hljs-string">'success'</span> : <span class="hljs-string">'error'</span>](message);
    } <span class="hljs-keyword">catch</span> (err) {
        toast.error(getErrorMessage(err));
    }
}
</code></pre>
<p>In the above file, you'll find the following functions:</p>
<ul>
<li><strong>getErrorMessage</strong>: Returns the error message if the input is an instance of Error – otherwise, converts it to a string.</li>
<li><strong>getTasks</strong>: 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.</li>
<li><strong>reorderTasks</strong>: 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.</li>
<li><strong>editTask</strong>: 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.</li>
<li><strong>deleteTask</strong>: Sends a DELETE request to delete a task by its ID. Displays a success or error toast based on the API response.</li>
<li><strong>createTask</strong>: 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.</li>
</ul>
<h2 id="heading-reactjs-components">React.js Components</h2>
<p>Now, since you have utilities ready, in the <code>resources/react/components</code> folder, you can create the components you need to use in your <code>Main.tsx</code>.</p>
<p>First, create <code>SelectProject.tsx</code>, which will be responsible for choosing the current project:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> {getTasks} <span class="hljs-keyword">from</span> <span class="hljs-string">"../utils"</span>;

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">SelectProject</span>(<span class="hljs-params">{projectId, projects, setProjectId, setTasks}</span>) </span>{
    <span class="hljs-keyword">const</span> selectProject = <span class="hljs-function">(<span class="hljs-params">e</span>) =&gt;</span> {
        <span class="hljs-keyword">const</span> value = e.target.value;
        setProjectId(value);
        <span class="hljs-keyword">if</span> (value === <span class="hljs-string">''</span>) {
            setTasks([]);
        } <span class="hljs-keyword">else</span> {
            getTasks(value).then(<span class="hljs-function">(<span class="hljs-params">tasksData</span>) =&gt;</span> setTasks(tasksData));
        }
    };

    <span class="hljs-keyword">return</span> (
        &lt;div className=<span class="hljs-string">"projects"</span>&gt;
            &lt;select className=<span class="hljs-string">"projects-select"</span>
                    value={projectId}
                    onChange={selectProject}&gt;
                &lt;option value=<span class="hljs-string">""</span> defaultValue&gt;Choose a project&lt;/option&gt;
                {projects.map(<span class="hljs-function">(<span class="hljs-params">project</span>) =&gt;</span> (
                    &lt;option key={project.id}
                            value={project.id}&gt;{project.name}&lt;/option&gt;
                ))}
            &lt;/select&gt;
        &lt;/div&gt;
    );
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> SelectProject;
</code></pre>
<p>The <code>SelectProject</code> 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 <code>getTasks</code> utility function, and updates the state with the retrieved tasks, providing dynamic interaction with project selection and task loading.</p>
<p>Then create <code>TaskList.tsx</code>, which will be responsible for rendering the project's tasks and for their drag and drop manipulations:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> {DragDropContext, Draggable, Droppable} <span class="hljs-keyword">from</span> <span class="hljs-string">"react-beautiful-dnd"</span>;
<span class="hljs-keyword">import</span> Task <span class="hljs-keyword">from</span> <span class="hljs-string">"./Task"</span>;
<span class="hljs-keyword">import</span> {reorderTasks} <span class="hljs-keyword">from</span> <span class="hljs-string">"../utils"</span>;

<span class="hljs-keyword">const</span> getItemStyle = <span class="hljs-function">(<span class="hljs-params">isDragging, draggableStyle</span>) =&gt;</span> ({
    background: isDragging ? <span class="hljs-string">'lightgreen'</span> : <span class="hljs-string">'grey'</span>,
    ...draggableStyle,
});

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">TaskList</span>(<span class="hljs-params">{ tasks, setIsModalEditOpen, setModalEditTask, setIsModalDeleteOpen, setModalDeleteTaskId, projectId, setTasks }</span>)
</span>{
    <span class="hljs-keyword">const</span> handleDragEnd = <span class="hljs-function">(<span class="hljs-params">result</span>) =&gt;</span> {
        <span class="hljs-keyword">if</span> (!result.destination || result.destination.index === result.source.index) {
            <span class="hljs-keyword">return</span>;
        }

        <span class="hljs-keyword">const</span> items = <span class="hljs-built_in">Array</span>.from(tasks);
        <span class="hljs-keyword">const</span> [reorderedItem] = items.splice(result.source.index, <span class="hljs-number">1</span>);
        items.splice(result.destination.index, <span class="hljs-number">0</span>, reorderedItem);
        reorderTasks(projectId, result.source.index + <span class="hljs-number">1</span>, result.destination.index + <span class="hljs-number">1</span>);

        setTasks(items);
    };

    <span class="hljs-keyword">return</span> (
        &lt;DragDropContext onDragEnd={handleDragEnd}&gt;
            &lt;Droppable droppableId=<span class="hljs-string">"droppable"</span>&gt;
                {<span class="hljs-function">(<span class="hljs-params">provided</span>) =&gt;</span> (
                    &lt;ul {...provided.droppableProps} ref={provided.innerRef}&gt;
                        {tasks.map(<span class="hljs-function">(<span class="hljs-params">task, index</span>) =&gt;</span> (
                            &lt;Draggable key={task.id.toString()} draggableId={task.id.toString()} index={index}&gt;
                                {<span class="hljs-function">(<span class="hljs-params">provided, snapshot</span>) =&gt;</span> (
                                    &lt;li ref={provided.innerRef}
                                        {...provided.draggableProps}
                                        {...provided.dragHandleProps}
                                        className=<span class="hljs-string">"task-item"</span>
                                        style={getItemStyle(snapshot.isDragging, provided.draggableProps.style)}
                                    &gt;
                                        &lt;Task task={task}
                                              setIsModalEditOpen={setIsModalEditOpen}
                                              setModalEditTask={setModalEditTask}
                                              setIsModalDeleteOpen={setIsModalDeleteOpen}
                                              setModalDeleteTaskId={setModalDeleteTaskId}
                                        /&gt;
                                    &lt;/li&gt;
                                )}
                            &lt;/Draggable&gt;
                        ))}
                        {provided.placeholder}
                    &lt;/ul&gt;
                )}
            &lt;/Droppable&gt;
        &lt;/DragDropContext&gt;
    );
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> TaskList;
</code></pre>
<p>The <code>TaskList</code> component utilizes the <code>react-beautiful-dnd</code> 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 (<code>handleDragEnd</code>) that updates the task order both visually and in the backend using the <code>reorderTasks</code> utility function.</p>
<p>Now, create <code>Task.tsx</code>, which will be responsible for a single Task from a list:</p>
<pre><code class="lang-typescript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Task</span>(<span class="hljs-params">{ task, setIsModalEditOpen, setModalEditTask, setIsModalDeleteOpen, setModalDeleteTaskId }</span>)
</span>{
    <span class="hljs-keyword">const</span> handleEdit = <span class="hljs-function">() =&gt;</span> {
        setModalEditTask(task);
        setIsModalEditOpen(<span class="hljs-literal">true</span>);
    };

    <span class="hljs-keyword">const</span> handleDelete = <span class="hljs-function">() =&gt;</span> {
        setModalDeleteTaskId(task.id);
        setIsModalDeleteOpen(<span class="hljs-literal">true</span>);
    };

    <span class="hljs-keyword">return</span> (
        &lt;div className=<span class="hljs-string">"task-item-content"</span>&gt;
            &lt;span className=<span class="hljs-string">"task-project"</span>&gt;
                &lt;span className=<span class="hljs-string">"task-project-name"</span>&gt;
                    {task.project.name}
                &lt;/span&gt;
                &lt;span className=<span class="hljs-string">"task-time"</span>&gt;
                    Created {task.created}
                &lt;/span&gt;
               &lt;/span&gt;
            &lt;span className=<span class="hljs-string">"task-title"</span>&gt;{task.title}&lt;/span&gt;
            &lt;div className=<span class="hljs-string">"task-actions"</span>&gt;
                &lt;button className=<span class="hljs-string">"task-edit-btn"</span>
                    onClick={handleEdit}&gt;Edit&lt;/button&gt;
                &lt;button className=<span class="hljs-string">"task-delete-btn"</span>
                    onClick={handleDelete}&gt;Delete&lt;/button&gt;
            &lt;/div&gt;
        &lt;/div&gt;
    );
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> Task;
</code></pre>
<p>The <code>Task</code> 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 (<code>handleEdit</code> and <code>handleDelete</code>).</p>
<p>Next, create <code>AddTaskForm.tsx</code>, which will be responsible for the task form for adding tasks to the current selected project:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> {createTask} <span class="hljs-keyword">from</span> <span class="hljs-string">"../utils"</span>;

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">AddTaskForm</span>(<span class="hljs-params">{newTask, setNewTask, projectId, reloadTasks }</span>)
</span>{
    <span class="hljs-keyword">const</span> clearTaskCreate = <span class="hljs-function">() =&gt;</span> {
        setNewTask({title: <span class="hljs-string">''</span>, description: <span class="hljs-string">''</span>});
    };
    <span class="hljs-keyword">const</span> submitTaskCreate = <span class="hljs-function">() =&gt;</span> {
        createTask(newTask, projectId).then(<span class="hljs-function">() =&gt;</span> {
            setNewTask({title: <span class="hljs-string">''</span>, description: <span class="hljs-string">''</span>});
            reloadTasks();
        });
    };

    <span class="hljs-keyword">return</span> (
        &lt;&gt;
            &lt;h2 className=<span class="hljs-string">"add-task-header"</span>&gt;Add Task&lt;/h2&gt;

            &lt;h3 className=<span class="hljs-string">"add-task-header"</span>&gt;Title&lt;/h3&gt;
            &lt;input <span class="hljs-keyword">type</span>=<span class="hljs-string">"text"</span>
                   className=<span class="hljs-string">"add-task-input"</span>
                   onChange={<span class="hljs-function">(<span class="hljs-params">e</span>) =&gt;</span> setNewTask({
                       ...newTask,
                    title: e.target.value
                   })}
                   value={newTask.title}
            /&gt;
            &lt;h3 className=<span class="hljs-string">"add-task-input-header"</span>&gt;Description&lt;/h3&gt;
            &lt;textarea className=<span class="hljs-string">"add-task-textarea"</span>
                      onChange={<span class="hljs-function">(<span class="hljs-params">e</span>) =&gt;</span> setNewTask({
                          ...newTask,
                        description: e.target.value
                      })}
                      value={newTask.description || <span class="hljs-string">''</span>}
            /&gt;
            &lt;div className=<span class="hljs-string">"add-task-actions"</span>&gt;
                &lt;button className=<span class="hljs-string">"add-task-btn add-task-btn-cancel"</span>
                        onClick={clearTaskCreate}&gt;Clear
                &lt;/button&gt;
                &lt;button className=<span class="hljs-string">"add-task-btn add-task-btn-submit"</span>
                        onClick={submitTaskCreate}&gt;Add&lt;/button&gt;
            &lt;/div&gt;
        &lt;/&gt;
    );
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> AddTaskForm;
</code></pre>
<p>The <code>AddTaskForm</code> 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 <code>createTask</code> utility function to handle the task creation process, triggering a reload of tasks upon success.</p>
<p>Then, create <code>ModalEdit.tsx</code>, which will be responsible for the modal popup for editing and submitting changes to a task:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> {Modal} <span class="hljs-keyword">from</span> <span class="hljs-string">"react-responsive-modal"</span>;
<span class="hljs-keyword">import</span> React <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;
<span class="hljs-keyword">import</span> {editTask} <span class="hljs-keyword">from</span> <span class="hljs-string">"../utils"</span>;

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">ModalEdit</span>(<span class="hljs-params">{
    isModalEditOpen, setIsModalEditOpen, setModalEditTask,
    modalEditTask, reloadTasks
}</span>) </span>{
    <span class="hljs-keyword">const</span> submitTaskEdit = <span class="hljs-function">() =&gt;</span> {
        setIsModalEditOpen(<span class="hljs-literal">false</span>);
        editTask(modalEditTask).then(<span class="hljs-function">() =&gt;</span> {
            reloadTasks();
        });
    };

    <span class="hljs-keyword">return</span> (
        &lt;Modal open={isModalEditOpen} center
            onClose={<span class="hljs-function">() =&gt;</span> setIsModalEditOpen(<span class="hljs-literal">false</span>)}&gt;
            &lt;div className=<span class="hljs-string">"modal-content"</span>&gt;
                &lt;h2 className=<span class="hljs-string">"modal-header"</span>&gt;Edit Task&lt;/h2&gt;
                &lt;h3 className=<span class="hljs-string">"modal-input-header"</span>&gt;Title&lt;/h3&gt;
                &lt;input <span class="hljs-keyword">type</span>=<span class="hljs-string">"text"</span> value={modalEditTask.title}
                       className=<span class="hljs-string">"modal-input"</span>
                       onChange={<span class="hljs-function">(<span class="hljs-params">e</span>) =&gt;</span> setModalEditTask({
                           ...modalEditTask,
                        title: e.target.value
                       })}
                /&gt;
                &lt;h3 className=<span class="hljs-string">"modal-input-header"</span>&gt;Description&lt;/h3&gt;
                &lt;textarea className=<span class="hljs-string">"modal-textarea"</span>
                          onChange={<span class="hljs-function">(<span class="hljs-params">e</span>) =&gt;</span> setModalEditTask({
                              ...modalEditTask,
                            description: e.target.value
                          })}
                          value={modalEditTask.description || <span class="hljs-string">''</span>}
                /&gt;
                &lt;div className=<span class="hljs-string">"modal-actions"</span>&gt;
                    &lt;button className=<span class="hljs-string">"modal-btn modal-btn-cancel"</span>
                            onClick={<span class="hljs-function">() =&gt;</span> setIsModalEditOpen(<span class="hljs-literal">false</span>)}
                    &gt;Close
                    &lt;/button&gt;
                    &lt;button className=<span class="hljs-string">"modal-btn modal-btn-submit"</span>
                            onClick={submitTaskEdit}
                    &gt;Save&lt;/button&gt;
                &lt;/div&gt;
            &lt;/div&gt;
        &lt;/Modal&gt;
    )
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> ModalEdit;
</code></pre>
<p>The <code>ModalEdit</code> 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 <code>editTask</code> utility function to handle the task editing process and triggering a reload of tasks upon successful editing.</p>
<p>Next, create <code>ModalDelete.tsx</code>, which will be responsible for submitting a task deletion:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> {Modal} <span class="hljs-keyword">from</span> <span class="hljs-string">"react-responsive-modal"</span>;
<span class="hljs-keyword">import</span> {deleteTask} <span class="hljs-keyword">from</span> <span class="hljs-string">"../utils"</span>;

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">ModalDelete</span>(<span class="hljs-params">{
    isModalDeleteOpen, setIsModalDeleteOpen,
    modalDeleteTaskId, reloadTasks
}</span>) </span>{
    <span class="hljs-keyword">const</span> submitTaskDelete = <span class="hljs-function">() =&gt;</span> {
        setIsModalDeleteOpen(<span class="hljs-literal">false</span>);
        deleteTask(modalDeleteTaskId).then(<span class="hljs-function">() =&gt;</span> {
            reloadTasks();
        });
    };

    <span class="hljs-keyword">return</span> (
        &lt;Modal open={isModalDeleteOpen} onClose={<span class="hljs-function">() =&gt;</span> setIsModalDeleteOpen(<span class="hljs-literal">false</span>)} center&gt;
            &lt;div className=<span class="hljs-string">"modal-content"</span>&gt;
                &lt;h2 className=<span class="hljs-string">"modal-header"</span>&gt;Delete Task&lt;/h2&gt;
                &lt;p className=<span class="hljs-string">"modal-question"</span>&gt;
                    Are you sure you want to <span class="hljs-keyword">delete</span> <span class="hljs-built_in">this</span> task?
                &lt;/p&gt;
                &lt;div className=<span class="hljs-string">"modal-actions"</span>&gt;
                    &lt;button className=<span class="hljs-string">"modal-btn modal-btn-cancel"</span>
                            onClick={<span class="hljs-function">() =&gt;</span> setIsModalDeleteOpen(<span class="hljs-literal">false</span>)}
                    &gt;Cancel&lt;/button&gt;
                    &lt;button className=<span class="hljs-string">"modal-btn modal-btn-submit"</span>
                            onClick={submitTaskDelete}
                    &gt;Yes&lt;/button&gt;
                &lt;/div&gt;
            &lt;/div&gt;
        &lt;/Modal&gt;
    );
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> ModalDelete;
</code></pre>
<p>The <code>ModalDelete</code> 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 <code>deleteTask</code> utility function and triggering a reload of tasks upon successful deletion.</p>
<p>And lastly, set up the <code>Main.tsx</code> by using the above-defined components.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { useState } <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>;
<span class="hljs-keyword">import</span> {getTasks} <span class="hljs-keyword">from</span> <span class="hljs-string">"./utils"</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">"react-responsive-modal/styles.css"</span>;
<span class="hljs-keyword">import</span> { ToastContainer } <span class="hljs-keyword">from</span> <span class="hljs-string">'react-toastify'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">'react-toastify/dist/ReactToastify.css'</span>;
<span class="hljs-keyword">import</span> ModalEdit <span class="hljs-keyword">from</span> <span class="hljs-string">"./components/ModalEdit"</span>;
<span class="hljs-keyword">import</span> ModalDelete <span class="hljs-keyword">from</span> <span class="hljs-string">"./components/ModalDelete"</span>;
<span class="hljs-keyword">import</span> TaskList <span class="hljs-keyword">from</span> <span class="hljs-string">"./components/TaskList"</span>;
<span class="hljs-keyword">import</span> SelectProject <span class="hljs-keyword">from</span> <span class="hljs-string">"./components/SelectProject"</span>;
<span class="hljs-keyword">import</span> AddTaskForm <span class="hljs-keyword">from</span> <span class="hljs-string">"./components/AddTaskForm"</span>;

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Main</span> (<span class="hljs-params"></span>) </span>{
    <span class="hljs-keyword">const</span> [projectId, setProjectId] = useState(<span class="hljs-string">''</span>);
    <span class="hljs-keyword">const</span> projectsData = <span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">'app'</span>).getAttribute(<span class="hljs-string">'data-projects'</span>);
    <span class="hljs-keyword">const</span> projects = <span class="hljs-built_in">JSON</span>.parse(projectsData);
    <span class="hljs-keyword">const</span> [tasks, setTasks] = useState([]);
    <span class="hljs-keyword">const</span> [isModalEditOpen, setIsModalEditOpen] = useState(<span class="hljs-literal">false</span>);
    <span class="hljs-keyword">const</span> [modalEditTask, setModalEditTask] = useState({id: <span class="hljs-string">''</span>, title: <span class="hljs-string">''</span>, description: <span class="hljs-string">''</span>});
    <span class="hljs-keyword">const</span> [isModalDeleteOpen, setIsModalDeleteOpen] = useState(<span class="hljs-literal">false</span>);
    <span class="hljs-keyword">const</span> [modalDeleteTaskId, setModalDeleteTaskId] = useState(<span class="hljs-string">''</span>);
    <span class="hljs-keyword">const</span> [newTask, setNewTask] = useState({title: <span class="hljs-string">''</span>, description: <span class="hljs-string">''</span>});

    <span class="hljs-keyword">const</span> reloadTasks = <span class="hljs-function">() =&gt;</span> {
        getTasks(projectId).then(<span class="hljs-function">(<span class="hljs-params">tasksData</span>) =&gt;</span> setTasks(tasksData));
    };

    <span class="hljs-keyword">return</span> (
        &lt;div&gt;
            &lt;ToastContainer autoClose={<span class="hljs-number">2000</span>} /&gt;
            &lt;ModalEdit isModalEditOpen={isModalEditOpen}
                       setIsModalEditOpen={setIsModalEditOpen}
                       modalEditTask={modalEditTask}
                       setModalEditTask={setModalEditTask}
                       reloadTasks={reloadTasks}
            /&gt;
            &lt;ModalDelete isModalDeleteOpen={isModalDeleteOpen}
                         setIsModalDeleteOpen={setIsModalDeleteOpen}
                         modalDeleteTaskId={modalDeleteTaskId}
                         reloadTasks={reloadTasks}
            /&gt;
            &lt;div className=<span class="hljs-string">"left-side"</span>&gt;
                {tasks.length &gt; <span class="hljs-number">0</span> ? (
                    &lt;TaskList tasks={tasks}
                              setIsModalEditOpen={setIsModalEditOpen}
                              setModalEditTask={setModalEditTask}
                              setIsModalDeleteOpen={setIsModalDeleteOpen}
                              setModalDeleteTaskId={setModalDeleteTaskId}
                              projectId={projectId}
                              setTasks={setTasks}
                    /&gt;
                ) : (
                    &lt;div className=<span class="hljs-string">"no-tasks"</span>&gt;
                        {projectId === <span class="hljs-string">''</span> ? (
                            &lt;p&gt;Choose a project to see its tasks.&lt;/p&gt;
                        ) : (
                            &lt;p&gt;This project has no tasks.&lt;/p&gt;
                        )}
                    &lt;/div&gt;
                )}
            &lt;/div&gt;
            &lt;div className=<span class="hljs-string">"right-side"</span>&gt;
                &lt;div className=<span class="hljs-string">"right-side-wrapper"</span>&gt;
                    &lt;SelectProject projectId={projectId}
                                   projects={projects}
                                   setProjectId={setProjectId}
                                   setTasks={setTasks}
                    /&gt;
                    {projectId === <span class="hljs-string">''</span> ? (
                        &lt;div className=<span class="hljs-string">"no-project-selected"</span>&gt;
                            &lt;p&gt;Please select a project.&lt;/p&gt;
                        &lt;/div&gt;
                    ) : (
                        &lt;AddTaskForm newTask={newTask}
                                     setNewTask={setNewTask}
                                     projectId={projectId}
                                     reloadTasks={reloadTasks}
                        /&gt;
                    )}
                &lt;/div&gt;
            &lt;/div&gt;
        &lt;/div&gt;
    );
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> Main;
</code></pre>
<p>The <code>Main</code> 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.</p>
<h2 id="heading-final-results">Final Results</h2>
<p>At this point, all the components are ready to interact with each other. So you can build the frontend assets and run the server:</p>
<pre><code class="lang-shell">npm run build &amp;&amp; php artisan serve
</code></pre>
<p>By visiting <code>http://127.0.0.1:8000</code> you'll get this kind of result:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/01/tasklist.gif" alt="Image" width="600" height="400" loading="lazy">
<em>GIF generated from a local working project</em></p>
<p><strong>That's it!</strong></p>
<p>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.</p>
<p>So, this can be just an example app for your next full-stack Laravel and React.js project.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>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 <code>react-beautiful-dnd</code> package for having draggable items.<br>Now you know how to manually integrate React.js in your Laravel app and maintain it.</p>
<p>You can find the complete code of the project here on my <a target="_blank" href="https://github.com/boolfalse/laravel-react-tasklist"><strong>GitHub</strong>⭐</a>, where I actively publicize much of my work about various modern technologies.<br>For more information, you can visit my website: <a target="_blank" href="https://boolfalse.com/"><strong>boolfalse.com</strong></a></p>
<p>Feel free to share this article. 😇</p>
 ]]>
                </content:encoded>
            </item>
        
    </channel>
</rss>
