Create An Ordering System Using Laravel, RabbitMQ, And Twilio

In this article, we are going to build a fully functional tiny web app which will be able to store messages in the queue using RabbitMQ, and it will send an SMS to users on their phone using Twilio. We will be using the Twilio SDK and Laravel. 
 
Before we start building, let's discuss our workflow and plan what we are going to do and how. Excuse my design but this is what we are going to build.
 
 
 
The user will click on the order button on our website and then order API will be called and let's assume if there is any issue then it will retry 5 times to place the order. Once the order is placed then details of the order will be published to the message broker which is RabbitMQ. Then a notification API will read the message from the queue and it will call the Twillio API which in turn sends the message to the user's phone.
 
So let us install RabbitMQ first for storing messages and then we will write our order API. Follow the steps to install RabbitMQ
 
Let’s begin with updating,
  1. sudo apt-get    update   
  2. sudo apt-get -y upgrade  
Enable the RabbitMQ application repository,
  1. sudo echo "deb http://www.rabbitmq.com/debian/ testing main" >> /etc/apt/sources.list  
Add the verification key for the package,
  1. curl http://www.rabbitmq.com/rabbitmq-signing-key-public.asc | sudo apt-key add -  
Update the sources with our new addition from above,
  1. sudo apt-get update  
Download and install RabbitMQ,
  1. sudo apt-get install rabbitmq-server   
To enable RabbitMQ Management Console, run the following,
  1. sudo rabbitmq-plugins enable rabbitmq_management  
Once you’ve enabled the console, it can be accessed using your browser by visiting: http://[your droplet's IP]:15672/. for me, it is localhost so localhost:15672 and if everything is good then you will see a login screen like below.
 
The default username and password are both set to “guest” for the login. After successful login, you will see this screen.
 
 
Now let us begin with our order API.
 
Create a Laravel project using this command. You can learn more here  
  1. composer create-project --prefer-dist laravel/laravel:^7.0 order  
If you don't have composer you can install it from here.  
 
After successful installation cd in that folder:
  1. cd order   
After successful installation, now we are going to add a package that is an AMQP wrapper for Laravel to publish and consume messages especially from RabbitMQ.
 
You can find more description here
 
Open config/app.php and add the service provider and alias,
  1. 'Bschmitt\Amqp\AmqpServiceProvider',  
  2. 'Amqp' => 'Bschmitt\Amqp\Facades\Amqp',   
Like below add provider
 
 
Add Alias
 
 
Now to go to the config folder and open the amqp.php file and configure RabbitMQ as follows
  1.        'host'              => env('AMQP_HOST''localhost'),  
  2.        'port'              => env('AMQP_PORT', 5672),  
  3.        'username'     => env('AMQP_USER''guest'),  
  4.        'password'     => env('AMQP_PASSWORD''guest'),  
 
  
where the host is the localhost in our case the port is 5672 which is the main port for RabbitMQ communication and 15672 is for the management console. Username is guest and password is guest.
 
Now issue the following command or go to your localhost/order/public
  1. php artisan serve   
Now visit the URL generated by the command and you will see the default welcome page.
 
Now let us create a controller named orderController using the following command 
  1. php artisan make:controller orderController  
Now open your Ordercontroller and lets code
 
Create a function named index and dump 'Hello World' just for testing if everything is working 
  1. public function index()  
  2. {  
  3.   
  4.  dd('Hello Word');  
  5.   
  6. }  
Add a route and test our function and for this go-to routes folder and open web.php
 
 
 
Add the following code 
  1. <?php  
  2.   
  3. use Illuminate\Support\Facades\Route;  
  4.   
  5. Route::get('/testapi''orderController@index')->name('testapi');  
Go to this http://localhost/order/public/testapi and you will see Hello world
 
Perfect, now let's do some real coding. Now add the following code to your function.
  1.  try {  
  2.   
  3.             $min = 1;  
  4.             $max = 10000;  
  5.             $order = rand($min$max);  
  6.             $message = "Thank you for using ecommerce your order number is:"$order;  
  7.             Amqp::publish('routing-key'$message, ['queue' => 'test']);  
  8. } catch (Exception $exception) {  
  9.             dd($exception);  
  10.         }  
 So what I am doing here is a very simple task, I am creating a random number which is order id, and a message string and pushing this message with the order id to queue.
  1. Amqp::publish('routing-key'$message, ['queue' => 'test']);    
So this code here creates a queue named to test and pushes a message to the queue. 
 
Let us test if everything is fine, go to the browser and refresh the page, and log in to RabbitMQ management and navigate to the Queue tab to check if there is any message.  
You must see a queue named to test and one message. I am seeing four because I have sent four messages to the queue.
 
We have pushed a message to the queue. Now let us consume that message and send this message to the user's mobile. Before doing that we need to install Twilio SDK for PHP. Use this command or you can follow documentation for installation.
  1. composer require twilio/sdk  
Once this installation is complete, let us create another function called notification and add a route to web.php
  1. Route::get('/notification',[OrderController::class'notification'])->name('notification');  
  1. public function notiication()  
  2.     {  
  3.         try {  
  4.   
  5.             Amqp::consume('test'function ($message$resolver) {  
  6.   
  7.                 $sid = 'ACf3ba81136a16388fc91a87e921d0254b';  
  8.                 $token = '822b5d4253b6f4f604caaded8cd7a7e7';  
  9.                 $client = new Client($sid$token);  
  10.   
  11.                 // Use the client to do fun stuff like send text messages!  
  12.                 $client->messages->create(  
  13.                     // the number you'd like to send the message to  
  14.                     '+9771234567890,  
  15.                     [  
  16.                         // A Twilio phone number you purchased at twilio.com/console  
  17.                         'from' => '+1234567890',  
  18.                         // the body of the text message you'd like to send  
  19.                         'body' => $message->body,  
  20.                     ]  
  21.                 );  
  22.          
  23.                 $resolver->acknowledge($message);  
  24.   
  25.                 $resolver->stopWhenProcessed();  
  26.             });  
  27.             $data = [  
  28.                 'status'=>'success',  
  29.             ];  
  30.   
  31.             return response($data,200);  
  32.   
  33.         } catch (\Exception $exception) {  
  34.             dd($exception);  
  35.   
  36.             return response('error', 500);  
  37.         }  
  38.     }  
This is the code where we are going to read the message from the queue named test which we have created above. You will get $sid,$token from the Twillio dashboard and the rest is self-explanatory I think. Now we are going to call this function right after the message is pushed to the queue. If you want to decouple both the operations then definitely you can but since this is just for demonstration purposes I am using it at one place.
 
So our backend code looks something like this.
  1. public function index()  
  2.     {  
  3.         try {  
  4.   
  5.             $min = 1;  
  6.             $max = 10000;  
  7.             $order = rand($min$max);  
  8.             $message = "Thank you for using ecommerce your order number is:" . $order;  
  9.             Amqp::publish('routing-key'$message, ['queue' => 'test']);  
  10.             $this->notification();  
  11.   
  12.         } catch (Exception $exception) {  
  13.             dd($exception);  
  14.         }  
  15.     }  
  16.   
  17.   
  18. public function notiication()  
  19.     {  
  20.         try {  
  21.   
  22.             Amqp::consume('test'function ($message$resolver) {  
  23.   
  24.                 $sid = 'ACf3ba81136a16388fc91a87e921d0254b';  
  25.                 $token = '822b5d4253b6f4f604caaded8cd7a7e7';  
  26.                 $client = new Client($sid$token);  
  27.   
  28.                 // Use the client to do fun stuff like send text messages!  
  29.                 $client->messages->create(  
  30.                     // the number you'd like to send the message to  
  31.                     '+9779807214786',  
  32.                     [  
  33.                         // A Twilio phone number you purchased at twilio.com/console  
  34.                         'from' => '+16202368003',  
  35.                         // the body of the text message you'd like to send  
  36.                         'body' => $message->body,  
  37.                     ]  
  38.                 );  
  39.              
  40.                 $resolver->acknowledge($message);  
  41.   
  42.                 $resolver->stopWhenProcessed();  
  43.             });  
  44.             $data = [  
  45.                 'status'=>'success',  
  46.             ];  
  47.             return response($data,200);  
  48.   
  49.         } catch (\Exception $exception) {  
  50.             dd($exception);  
  51.   
  52.             return response('error', 500);  
  53.         }  
  54.     }  
 We are pretty much done with the Backend and now let's move to our fronted and for this, I am going to create a simple HTML file and include jquery and bootstrap for design and Ajax call to our API. I am creating index.html and adding a button to test our work. Let's add our retry code. If you refer to the diagram above there is retry pattern included. This is the code I have written for it.
  1. <script>    
  2. $(document).on('click''#button'function () {    
  3.     
  4.     retry(retryCount=5);    
  5.     
  6. });    
  7.         function retry( retryCount)    
  8.         {    
  9.             var successCount = 0;    
  10.             var errorCount = 0;    
  11.             // var retryCount = 5;    
  12.             $.ajax({    
  13.                url: 'http://localhost/shoping/public/testapi',  
  14.                 method: 'get',    
  15.                 success: function (data) {    
  16.                     return false;    
  17.                 },    
  18.                 error: function (err) {    
  19.                     if (retryCount > 0) {    
  20.                         setTimeout(function () {    
  21.                             return retry(retryCount - 1);    
  22.                         }, 500);    
  23.     
  24.                     }    
  25.                 },    
  26.             });    
  27.             console.log('Retry :'+ retryCount);    
  28.             // console.log('Success Ateempt:'+ successCount);    
  29.     
  30.         }    
  31.     
  32. </script>     
And this is the final HTML code.
  1. <!DOCTYPE html>    
  2. <html>    
  3. <head>    
  4.         <meta charset="utf-8">    
  5.         <meta name="viewport" content="width=device-width, initial-scale=1">    
  6.         <title>Laravel</title>    
  7.         <!-- Fonts -->    
  8.         <link href="https://fonts.googleapis.com/css?family=Nunito:200,600" rel="stylesheet">    
  9.         <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNhE263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">    
  10.         <script src="https://code.jquery.com/jquery3.2.1.slim.min.js" integrity="sha384KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous"></script>    
  11.         <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js" integrity="sha384ApNbgh9B+Y1QKtv3Rn7W3mgPxhU9K/ScQsAP7hUibX39j7fakFPskvXusvfa0b4Q" crossorigin="anonymous"></script>    
  12.         <script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js" integrity="sha384JZR6Spejh4U02d8jOt6vLEHfe/JQGiRRSQQxSfFWpi1MquVdAyjUar5+76PVCmYl" crossorigin="anonymous"></script>    
  13.         <script src="https://code.jquery.com/jquery-2.2.4.js" integrity="sha256-iT6Q9iMJYuQiMWNd9lDyBUStIq/8PuOW33aOqmvFpqI=" crossorigin="anonymous"></script>    
  14.         <!-- Styles -->    
  15.     </head>    
  16.     <body>    
  17. <button name="button" id="button" value=' CLick me'>Click me</button>    
  18. <script>    
  19. $(document).on('click''#button'function () {    
  20.     
  21.     retry(retryCount=5);    
  22.     
  23. });    
  24.         function retry( retryCount)    
  25.         {    
  26.             var successCount = 0;    
  27.             var errorCount = 0;    
  28.             // var retryCount = 5;    
  29.             $.ajax({    
  30.                 url: 'http://localhost/shoping/public/testapi',    
  31.                 method: 'get',    
  32.                 success: function (data) {    
  33.                     return false;    
  34.                 },    
  35.                 error: function (err) {    
  36.                     if (retryCount > 0) {    
  37.                         setTimeout(function () {    
  38.                             return retry(retryCount - 1);    
  39.                         }, 500);    
  40.     
  41.                     }    
  42.                 },    
  43.             });    
  44.             console.log('Retry :'+ retryCount);    
  45.             // console.log('Success Ateempt:'+ successCount);    
  46.     
  47.         }    
  48.     
  49. </script>    
  50. </html>     
Now let us test our work, open your HTML file in the browser and you will see a button.
 
 
Now click on that button and it will try to push the message and consume the message and send the message to the provided mobile number which must be verified to Twillio in order to receive the message. So this is the final output we should get.
 
 
Thank you for reading. Suggestions and feedback are welcome and next, we are going to dockerize this application and run it in an isolated container. 
 
Thank you and stay safe.