May 07, 2015

Using Lumen Micro-framework with AngularJS

About three weeks ago we have a surprisingly good news from the creator of Laravel framework, Taylor Otwell, that he was releasing a new micro-framework built on top of Laravel and he named it "Lumen". As I was guessing, and turns out to be proven right, that this micro-framework will have the same cozy syntax as Laravel was. Benchmarks was done and resulting in Lumen being faster than micro-frameworks such as Slim and Silex (http://taylorotwell.com/how-lumen-is-benchmarked/).

So...

Today we are going to occupy Lumen as a web-service to our CRUD application built with AngularJS.

Step 1: Prepare the initial data

Before diving into lumen, we are going to create some fake data to play with. In this tutorial, we are going to use MySQL database and I have prepared you a MySQL data dump file for "users" table that will be used for our CRUD application in this tutorial. You can download the dump file, just open open this URL and click on the download link.

Now, let's create a new database called "db_lumen".
Continue by clicking on the database name to select it, then click on the import menu button. Choose the file we have dowloaded earlier and click the "Go" button to begin importing the data.
And there we have the data imported.




Step 2: Build the web-service with Lumen

Assuming that you have already install composer on your computer, let's open your favorite shell (command prompt on windows machine) and update your composer if you haven't got the latest version.
composer self-update
Next, go to your project directory (or any other directory you usually build your project in) and create a new lumen project using composer, these are the commands:
cd ~/Projects
composer create-project laravel/lumen --prefer-dist
Get in to lumen directory and run the lumen service using the artisan command like this:
cd lumen
php artisan serve
Open up your browser and go to page http://localhost:8000, where the lumen service is running, you should see something like this:
Inside the lumen directory you will find a ".env.example" file. Duplicate the file and rename the duplicate as ".env". Modify some settings inside the ".env" file according to your project environment, for example, mine looks like this:
APP_ENV=local
APP_DEBUG=true
APP_KEY=w3h84fFfrwpKExMXimYom810303FNb64

APP_LOCALE=en
APP_FALLBACK_LOCALE=en

DB_CONNECTION=mysql
DB_HOST=localhost
DB_DATABASE=db_lumen
DB_USERNAME=root
DB_PASSWORD=

CACHE_DRIVER=memcached
SESSION_DRIVER=memcached
QUEUE_DRIVER=database
Note that you should set APP_DEBUG=false when APP_ENV=production (your application is on live production state). Next you have to open the app.php file inside folder bootstrap, find these lines:
Line-5  : // Dotenv::load(__DIR__.'/../');
Line-22 : // $app->withFacades();
Line-24 : // $app->withEloquent();
and un-comment these lines, so that it becomes like this:
Dotenv::load(__DIR__.'/../');
$app->withFacades();
$app->withEloquent();
Ok, that's done for the settings. Now let's go down to business and code our CRUD services. Open the routes.php file inside folder app/Http/ and modify the existing code so that is will look like this:
<?php
$app->get('/', function() use ($app) {
    return view('index'); 
});

$app->post('/create-user',      'App\Http\Controllers\UserController@store');
$app->get('/read-users',        'App\Http\Controllers\UserController@index');
$app->get('/read-user/{id}',    'App\Http\Controllers\UserController@show');
$app->post('/edit-user/{id}', 'App\Http\Controllers\UserController@update');
$app->post('/delete-user/{id}', 'App\Http\Controllers\UserController@destroy');
Next, we are going to code the controller. Get into folder app/Http/Controllers, create a new file called UserController.php, and write these basic function declarations to start with:
<?php 
namespace App\Http\Controllers;

use Laravel\Lumen\Routing\Controller as BaseController;
use App\User; //loads the User model
use Illuminate\Http\Request; //loads the Request class for retrieving inputs
use Illuminate\Support\Facades\Hash; //load this to use the Hash::make method

class UserController extends BaseController
{
    public function index(){}
    public function show(){}
    public function store(){}
    public function update(){}
    public function destroy(){}
}
Next we should fill up those functions to make a full CRUD application, let's do it part by part.

The -C- Part
/* The -C- Part */
public function store(Request $request){ //Insert new record to users table
    $this->validate($request, [
        'email'  => 'required|unique:users',
        'password' => 'sometimes',
        'name'  => 'required',
        'address' => 'required',
        'phone'  => 'required'
    ]); 
    $user   = new User;
    $user->email  = $request->input('email');
    $user->password  = Hash::make( $request->input('password') );
    $user->name  = $request->input('name');
    $user->address  = $request->input('address');
    $user->phone  = $request->input('phone');
    $user->save();
}
The -R- Part
public function index(){ //Get all records from users table
    return User::all();
}

public function show($id){ //Get a single record by ID
    return User::find($id); 
}
The -U- Part
public function update(Request $request, $id){ //Update a record
    $this->validate($request, [
        'email'  => 'required',
        'password' => 'sometimes',
        'name'  => 'required',
        'address' => 'required',
        'phone'  => 'required'
    ]); 
    $user    = User::find($id);
    $user->email   = $request->input('email');
    if($request->has('password')){
        $user->password = Hash::make( $request->input('password') );
    }
    $user->name   = $request->input('name');
    $user->address   = $request->input('address');
    $user->phone   = $request->input('phone');
    $user->save();
}
The -D- Part
public function destroy(Request $request){
    $this->validate($request, [
        'id' => 'required|exists:users'
    ]);
    $user = User::find($request->input('id'));
    $user->delete();
}
Okay, now that we have done building our Lumen services, lets build a user interface with AngularJS

Step 3: Build the AngularJS application

First, we are going to create an index.php file inside the resource/views folder. We are not using blade in this case, because we are only use lumen for the background service for AngularJS application.
<!DOCTYPE html>
<html lang="en-US">
<head>
   <title>The Amazing PHP - AngularJS Single-page Application with Lumen CRUD Services</title>
 
   <!-- Load Bootstrap CSS -->
   <link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.4/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>

   <div ng-app="myApp" ng-controller="usersCtrl">
      
      <!-- There will be a table, to dispay the data, here -->

      <!-- There will be a modal to pop-up a Form (One form used as a create and edit form) -->
  
   </div>

   <!-- Load Javascript Libraries (AngularJS, JQuery, Bootstrap) -->
   <script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.3.14/angular.min.js"></script>
   <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>
   <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.4/js/bootstrap.min.js"></script>

   <!-- There will be our javascript Application here -->

</body>
</html>
Notice that the above script is not complete yet. There are some missing parts need to be filled just to show you the big picture of the application. Now, let's fill those blank scripts starting from the table part as below:
<!-- Table-to-load-the-data Part -->
   <table class="table">
      <thead>
         <tr>
            <th>ID</th>
            <th>Name</th>
            <th>Email</th>
            <th>Phone</th>
            <th>Date Created</th>
            <th><button id="btn-add" class="btn btn-primary btn-xs" ng-click="toggle('add',0)">Add New User</button></th>
         </tr>
      </thead>
      <tbody>
         <tr ng-repeat="user in users">
            <td>{{ $index + 1 }}</td>
            <td>{{ user.name }}</td>
            <td>{{ user.email }}</td>
            <td>{{ user.phone }}</td>
            <td>{{ user.created_at }}</td>
            <td>
               <button class="btn btn-default btn-xs btn-detail" ng-click="toggle('edit',user.id)">Edit</button>
               <button class="btn btn-danger btn-xs btn-delete" ng-click="confirmDelete(user.id)">Delete</button>
            </td>
         </tr>
      </tbody>
   </table>
<!-- End of Table-to-load-the-data Part -->
Next, we are going to put the modal, which actually contains the Add New User and Edit User form. Just one modal for both Add and Edit usage.
<!-- Modal (Pop up when detail button clicked) -->
   <div class="modal fade" id="myModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
      <div class="modal-dialog">
         <div class="modal-content">
            <div class="modal-header">
               <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
               <h4 class="modal-title" id="myModalLabel">{{state}}</h4>
            </div>
            <div class="modal-body">
               <form class="form-horizontal">

                  <div class="form-group">
                     <label for="inputEmail3" class="col-sm-3 control-label">Name</label>
                     <div class="col-sm-9">
                        <input type="text" class="form-control" id="inputEmail3" placeholder="Fullname" value="{{name}}" ng-model="formData.name">
                     </div>
                  </div>

                  <div class="form-group">
                     <label for="inputEmail3" class="col-sm-3 control-label">Email</label>
                     <div class="col-sm-9">
                        <input type="text" class="form-control" id="inputEmail3" placeholder="Email Address" value="{{email}}" ng-model="formData.email">
                     </div>
                  </div>

                  <div class="form-group">
                     <label for="inputPassword3" class="col-sm-3 control-label">Password</label>
                     <div class="col-sm-9">
                        <input type="password" class="form-control" id="inputPassword3" placeholder="Leave empty to unchange" ng-model="formData.password">
                     </div>
                  </div>

                  <div class="form-group">
                     <label for="inputEmail3" class="col-sm-3 control-label">Phone</label>
                     <div class="col-sm-9">
                        <input type="text" class="form-control" id="inputEmail3" placeholder="Phone Number" value="{{phone}}" ng-model="formData.phone">
                     </div>
                  </div>

                  <div class="form-group">
                     <label for="inputEmail3" class="col-sm-3 control-label">Address</label>
                     <div class="col-sm-9">
                        <textarea class="form-control" placeholder="Full Address" ng-model="formData.address">{{address}}</textarea>
                     </div>
                  </div>

               </form>
            </div>
            <div class="modal-footer">
               <button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
               <button type="button" class="btn btn-primary" id="btn-save" ng-click="save(modalstate,user_id)">Save changes</button>
            </div>
         </div>
      </div>
   </div>
<!-- End of Modal -->
Lastly, the angularjs application will be coded before the </body> tag as below: 
<!-- AngularJS Application Script Part -->
<script>
   var app = angular.module('myApp', []);
   app.controller('usersCtrl', function($scope, $http) {

      /* The -R- part */
      $http.get("http://localhost:8000/read-users")
      .success(function(response) {
         $scope.users = response;
      });
      /* End of the -R- part */
         
      /* The -C- and -U- part */
         $scope.save = function(modalstate,user_id){
            switch(modalstate){
               case 'add': var url = "http://localhost:8000/create-user"; break;
               case 'edit': var url = "http://localhost:8000/edit-user/"+user_id; break;
               default: break;
            }
            $http({
               method  : 'POST',
               url     : url,
               data    : $.param($scope.formData),  // pass in data as strings
               headers : { 'Content-Type': 'application/x-www-form-urlencoded' }  // set the headers so angular passing info as form data (not request payload)
            }).
            success(function(response){
               location.reload();
            }).
            error(function(response){
               console.log(response);
               alert('Incomplete Form');
            });
         }
      /* End of the -C- and -U- part */

      /* The -D- Part */
         $scope.confirmDelete = function(id){
            var isOkDelete = confirm('Is it ok to delete this?');
            if(isOkDelete){
               $http.post('http://localhost:8000/delete-user', {id:id}).
               success(function(data){
                  location.reload();
               }).
               error(function(data) {
                  console.log(data);
                  alert('Unable to delete');
               });
            } else {
               return false;
            }
         }
      /* End of the -D- Part */

      /* Show the modal */
      $scope.toggle = function(modalstate,id) {
            $scope.modalstate = modalstate;
            switch(modalstate){
               case 'add':
                           $scope.state = "Add New User";
                           $scope.user_id = 0;
                           $scope.name = '';
                           $scope.email = '';
                           $scope.phone = '';
                           $scope.address = '';
                           break;
               case 'edit':
                           $scope.state = "User Detail";
                           $scope.user_id = id;
                           $http.get("http://localhost:8000/read-user/" + id)
                           .success(function(response) {
                              console.log(response);
                              $scope.formData = response;
                           });
                           break;
               default: break;
            }
            
            //console.log(id);
            $('#myModal').modal('show');
         }
   });
</script>
<!-- End of AngularJS Application Script Part -->
The application would be like the screenshot picture shown below:

And tadaa, we have just build an AngularJS application with Lumen running as services. Feel free to improve the code from this tutorial, and please let me know if you build something amazing with it. Have fun ^_^

Download source code here

6 comments:

  1. I try this on vagrant and I got this error:
    "FatalErrorException in UserController.php line 12:
    Class 'App\User' not found"
    in UserController.php line 12
    at Application->Laravel\Lumen\{closure}()
    Any idea why?

    ReplyDelete
  2. try adding "use App\User" in the UserController.php file,
    or
    change line 12, to "\App\User"

    ReplyDelete
  3. This comment has been removed by a blog administrator.

    ReplyDelete
  4. Me gustó mucho el tutorial y no tuve ningún problema, me gustaría que refactorizaras el código usando servicios de AngularJS.

    Saludos.

    ReplyDelete
  5. I meet similar problem. The current version of Lumen (I've just get the latest one on 04 January 2016) seems not have User model. So I go to laravel sample app, copy App/User.php to lumen, then it works!

    ReplyDelete
  6. Sorted it. I had the wrong database password in the end... just the lack of seeing any error messages threw my. Great article.

    ReplyDelete