Intro to Alpine.js with Twilio Verify and Laravel Livewire

Created by: SD Rosenthal
Laravel
intro-to-alpine-js-with-twilio-verify-and-laravel-livewire

Alpine.js is a relatively new front-end framework that promises the reactive and declarative nature of big frameworks like Vue.js or React.js without having to run npm, compile scripts, configure webpack all in a nice 7.37kb cdn hosted file. You get to keep your DOM, and sprinkle in behavior as you see fit. Think of it like Tailwind for JavaScript. After using Alpine on several enterprise projects, I can testify to its ease of use and scalability.
According to the Alpine.js docs, its syntax is almost entirely borrowed from Vue.js (and by extension Angular.js), so if you already know either of these frameworks there is a very low learning curve to getting started.

Prerequisites

Before you begin this tutorial, make sure you have the following set up/installed:
A Laravel 7 application already installed
A Twilio account

NOTE: We are leveraging the power of Laravel Livewire for this demo, but it is not a requirement in learning Alpine.js. If you want to learn more about Laravel Livewire and learn the basics, check out my post where we build a phone-dialer app with Livewire and Twilio.

What We’ll Build

To demonstrate some of the basic functionality of Alpine.js, I thought it appropriate to create a simple Twilio Verify form that allows users to enter their phone number, receive a code and verify their phone number, all while showing some nice transitions and conditionals with Alpine.js in a Livewire component.

Install Dependencies

First, you will need to have the Twilio PHP SDK installed along with Laravel Livewire. Run the following commands in your terminal to install these dependencies inside of your Laravel application folder:

$ composer require twilio/sdk
$ composer require livewire/livewire

As with any Laravel project handling secure credentials, you will need to store them safely in a dotenv file. Grab your Twilio Account SID and Auth Token from the console and add them to your .env:

TWILIO_ACCOUNT_SID=ACXXXXXXXXXXXXXXXXXXXXXXXX
TWILIO_AUTH_TOKEN=XXXXXXXXXXXXXXXXXXXXXXXX

Routes and Components

Next, create a Livewire component. This command will generate a Livewire class and Blade file for our markup.

$ php artisan make:livewire verifyApi

Add a Livewire route to routes/web.php:

Route::livewire('verify', 'verify-api');

If it’s not present, create a layout file resources/views/layouts/app.blade.php and add the following code:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Twilio Livewire</title>
    <link href="https://unpkg.com/tailwindcss@^1.0/dist/tailwind.min.css" rel="stylesheet">
    @livewireStyles
</head>
<body class="h-screen w-screen flex items-center justify-center bg-gray-900">
    @yield('content')
    @livewireScripts
</body>
</html>

Basic Functionality

In your VerifyApi class, located in the app/Http/Livewire folder, add the following methods and properties:

<?php

namespace App\Http\Livewire;

use Livewire\Component;

class VerifyApi extends Component
{
    public $phone_number;
    public $code;
    public $status = '';
    public $error = '';

    public function sendCode()
    {
        $twilio = resolve('TwilioClient');
        $verification = $twilio
            ->verify
            ->v2
            ->services(getenv('TWILIO_VERIFICATION_SID'))
            ->verifications
            ->create('+1' . str_replace('-', '', $this->phone_number), "sms");

        $this->status = $verification->status;
    }

    public function verifyCode()
    {
        $twilio = resolve('TwilioClient');
        try {
            $verification_check = $twilio
                ->verify
                ->v2
                ->services(getenv('TWILIO_VERIFICATION_SID'))
                ->verificationChecks
                ->create($this->code, // code
                    ["to" => '+1' . str_replace('-', '', $this->phone_number)]
                );
        } catch (\Exception $e) {
            $this->error = $e->getMessage();
        }

        if ($verification_check->valid == false) {
            $this->error = 'That code is invalid, please try again.';
        }else{
            $this->error = '';
            $this->status = $verification_check->status;
        }
    }

    public function render()
    {
        return view('livewire.verify-api');
    }
}

NOTE: I added a binding in our container to make it simpler to resolve our Twilio Client. In app/AppServiceProvider.php in the register method you can add the following:

$this->app->singleton('TwilioClient', function(){
    return new Client(
        getenv('TWILIO_ACCOUNT_SID'),
        getenv('TWILIO_AUTH_TOKEN')
    );
});

Now, whenever we need to get the Twilio Client, we can resolve it out of the container, and will already have our credentials attached.

$twilio = resolve('TwilioClient');

Before we can begin using this code to make verification requests, we need to head over to the Twilio console and create our Verify Service. Click create and make a friendly name for your app (this will display in the SMS messages sent to your users). Once that is done, Twilio will display an SID. Copy this and add it to your .env.

https://twilio-cms-prod.s3.amazonaws.com/images/IJ_5qicfyRJfejaDH1YK4FTXGuqDCRBHH5J0KzdwskvhG.width-1600.png

TWILIO_VERIFICATION_SID=VA6c1fb42e927c73664eaff944caef1df4

Besides the render method in our VerifyApi class (which simply tells Laravel which blade file to render), we have added a sendCode and a verifyCode method. These methods will be called from our blade file by submitting a form. Add the following markup to the verify-api.blade.php file in the views/livewire folder:

<div class="bg-gray-200 p-5 rounded shadow-lg w-1/3">
    <div >
        <form wire:submit.prevent="sendCode">
            <x-inputs.group label="Verify Your Phone Number" for="phone_number">
                <x-inputs.text for="phone_number" wire:model="phone_number"/>
            </x-inputs.group>
            <button type="submit" class="w-full bg-indigo-500 text-white rounded-lg my-4 py-2 uppercase font-bold">Verify</button>
        </form>
    </div>
    <div>
        <form wire:submit.prevent="verifyCode" class="w-full">
            <x-inputs.group label="Enter code" for="code">
                <x-inputs.text for="code" wire:model="code"/>
            </x-inputs.group>
            <button type="submit" class="w-full bg-indigo-500 text-white rounded-lg my-4 py-2 uppercase font-bold">Verify Code</button>
        </form>
    </div>
</div>

In order for these x-input components to work, create a directory inside ‘resources/viewscalledcomponentsand another directory there calledinputs. Create two new files, group.blade.phpandtext.blade.php`.

Inside of group.blade.php add the following code:

@props(['label', 'for'])
<div {{ $attributes->merge(['class' => 'sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start sm:border-gray-200 sm:pt-5']) }}>
    <label for="{{ $for }}"
           class="block text-sm font-medium leading-5 text-gray-700 sm:mt-px sm:pt-2">
        {{ $label }}
    </label>
    <div class="mt-1 sm:mt-0 sm:col-span-2">
        {{ $slot }}
    </div>
</div>

Andi inside text.blade.php add this code:

@props([
    'for',
    'type' => 'text',
    'value' => ''
])
<div class="rounded-md shadow-sm">
    <input
        type="{{ $type }}"
        value="{{ $value }}"
        {{ $attributes->merge(['class' => 'bg-white border border-gray-200 py-2 px-3 rounded-lg w-full block w-full transition duration-150 ease-in-out sm:text-sm sm:leading-5']) }}
        id="{{ $for }}"
        />
</div>

NOTE: We are using anonymous blade components that are explained in detail here, as well as Tailwind CSS for styling.

Take a moment and start your Laravel application by running php artisan serve. Navigate to http://127.0.0.1:8000/verify and you should see the following:

https://twilio-cms-prod.s3.amazonaws.com/images/dCAjBYouG7q15hP-1kAte5KrVSQX1BdoDu6maFoLcdKCU.width-1600.png

If you enter your phone number in the first input and click “Verify” you should receive an SMS message with a code, and by entering that code into the second field and clicking “Verify Code” we will have executed the functionality of this nifty tool!

x-data

Now, I don’t know about you, but this is nowhere near prime-time ready for me. What if there is an error? How do we tell the user the code is sent and verified accordingly? This is what Alpine was created for. We can achieve this flexibility (and much more) without having to manage any dependencies in webpack/mix and without running npm run ….

Adding Alpine.js is as easy as adding a single line of code into the head section of your layout app.blade.php file:

<script src="https://cdn.jsdelivr.net/gh/alpinejs/[email protected]/dist/alpine.min.js" defer></script>

That’s it!

Much like Vue.js, we can add properties to our DOM and initialize them with some default values and conditionally change our site when these properties are changed.

Back in the blade file, change the outer-most div to the following:

<div x-data="{status: '{{$status}}', error: '{{$error}}'}" class="bg-gray-200 p-5 rounded shadow-lg w-1/3">

This x-data attribute tells Alpine what we are watching and sets the initial values. In this case status and error are now properties we can use anywhere inside of this div. To demonstrate, let’s hide the second form, until a phone number is submitted and the code is sent. Replace the inner code in resources/views/livewire/verify-api.blade.php with the following:

<div x-show="status == ''">
        <form wire:submit.prevent="sendCode">
            <x-inputs.group label="Verify Your Phone Number" for="phone_number">
                <x-inputs.text for="phone_number" wire:model="phone_number"/>
            </x-inputs.group>
            <button type="submit" class="w-full bg-indigo-500 text-white rounded-lg my-4 py-2 uppercase font-bold">Verify</button>
        </form>
    </div>
    <div x-show="status == 'pending'">
        <form wire:submit.prevent="verifyCode" class="w-full">
            <x-inputs.group label="Enter code" for="code">
                <x-inputs.text for="code" wire:model="code"/>
            </x-inputs.group>
            <button type="submit" class="w-full bg-indigo-500 text-white rounded-lg my-4 py-2 uppercase font-bold">Verify Code</button>
        </form>
    </div>

x-show will display that portion of your code, based on a condition. In this case, if the status is an empty string (which is set in our VerifyApi class on the public property), it will display the first form. When that status changes to pending, we hide the first form and show the second form. Refreshing your page should show the following:

https://twilio-cms-prod.s3.amazonaws.com/images/gsmVOwLuFDUcWll6me6inyJ2OADfXsTQ9-h5HPC01tyFv.width-1600.png

Now, when we enter our phone number and submit the form, the first form hides and the second form is shown conditionally! Perfect!

But wait, there’s more!

Change your whole blade file to the following:

<div x-data="{status: '{{$status}}', error: '{{$error}}'}" class="bg-gray-200 p-5 rounded shadow-lg w-1/3">
    <h3 class="uppercase text-sm text-center text-indigo-500 font-bold"
        :class="{ 'text-indigo-500' : status === 'pending', 'text-green-500' : status === 'approved'}"
    >{{$status}}
    </h3>
    <h3 x-show="error != ''" class="uppercase text-sm text-center text-red-500 font-bold">{{$status}}</h3>
    <div x-show="status == ''">
        <form wire:submit.prevent="sendCode">
            <x-inputs.group label="Verify Your Phone Number" for="phone_number">
                <x-inputs.text for="phone_number" wire:model="phone_number"/>
            </x-inputs.group>
            <button type="submit" class="w-full bg-indigo-500 text-white rounded-lg my-4 py-2 uppercase font-bold">Verify</button>
        </form>
    </div>
    <div x-show="status == 'pending'"
        x-transition:enter="transition ease-out duration-300"
        x-transition:enter-start="opacity-0 transform scale-90"
        x-transition:enter-end="opacity-100 transform scale-100"
        x-transition:leave="transition ease-in duration-300"
        x-transition:leave-start="opacity-100 transform scale-100"
        x-transition:leave-end="opacity-0 transform scale-90"
    >
        <form wire:submit.prevent="verifyCode" class="w-full">
            <x-inputs.group label="Enter code" for="code">
                <x-inputs.text for="code" wire:model="code"/>
            </x-inputs.group>
            <button type="submit" class="w-full bg-indigo-500 text-white rounded-lg my-4 py-2 uppercase font-bold">Verify Code</button>
        </form>
    </div>
</div>

You can now start to see the incredible power and flexibility we can obtain with Alpine. Transitions and conditionally binding classes to elements is only scraping the surface of what Alpine can do!

Next Steps

If you want to extend this tutorial, you can check out Alpine’s docs on Github, Awesome Alpine (a curated list of awesome resources related to Alpine.js) ,iterate through an array with x-for, add two-way binding with x-model, and even add @click handlers to your buttons, all without creating a separate component or running npm!

What will you build with Alpine? Are you already using it in the wild? Leave some comments below and keep the conversation going!

Shane D. Rosenthal is an AWS Certified and experienced full-stack developer that has worked with Laravel since 2013. He is open for consultation regarding Laravel, Vue.js applications, and AWS architecture