Creating a RESTful API using Laravel for AngularJS Frontend

This will be the last part of the AngularJS Polling Application Tutorial series. In this part, we will create a RESTful API using Laravel for the AngularJS Single Page Application that we had created in the previous parts of this tutorial series.

Requirements

  • Laravel
  • MySQL
  • Faker

Recommended Articles

Previous Parts:

Now, let us get started.

Migrations, Models and Seeding

The RESTful API will be utilizing two database tables for the data persistence: a polls table and a stats table. The polls table will be used to store all the polls, whereas the stats table will be used to store the related poll options and a voting count for each option. We will query the stats table to summarize the stats for a single poll.

The following screenshot shows the association between these two tables:

RESTful API using Laravel Tutorial
Polls and Stats table relationship

Migrations

For more details and explanation on Database Migrations in Laravel, please refer to the Database Migrations article (see Recommended Articles above).

Quick Tip:

  • the artisan migrate:make create_tablename_table --create=tablename command is used to create a migration class for the tablename.
  • The artisan migrate command is used to run migrations.

The Migration class for the polls table:

<?php
// created using: artisan migrate:make create_polls_table --create=polls
// file: xxxx_xx_xx_xxxxxx_create_polls_table.php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;

class CreatePollsTable extends Migration {

    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('polls', function(Blueprint $table)
        {
            $table->increments('id');
            $table->string('question');
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::drop('polls');
    }

}

In the above Migration class, the question table field will be used to store the poll questions. Here is the Migration class for the stats table:

<?php
// created using: artisan migrate:make create_stats_table --create=stats
// file: xxxx_xx_xx_xxxxxx_create_stats_table.php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;

class CreateStatsTable extends Migration {

    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('stats', function(Blueprint $table)
        {
            $table->increments('id');
            $table->unsignedInteger('poll_id');
            $table->string('option');
            $table->unsignedInteger('vote_count');
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::drop('stats');
    }

}

The poll_id field will be used as the foreign key for the polls table. The option field will be used to store the related available options for a poll. Whenever an option will be selected by the user, we will increment its vote_count.

Models

Here is the code for both the Poll and the Stat models:

<?php
// file: app/models/Poll.php

class Poll extends Eloquent {

    protected $hidden = ['created_at','updated_at'];
    protected $appends = ['options'];

    public function stats()
    {
        return $this->hasMany('Stat');
    }

    public function getOptionsAttribute()
    {
        return array_flatten($this->stats()->get(['option'])->toArray());
    }
}
<?php
// file: app/models/Stat.php

class Stat extends Eloquent {

    protected $hidden = ['created_at','updated_at'];

    public function poll()
    {
        return $this->belongsTo('Poll');
    }

}

To read more on Laravel Eloquent Models and Seeding, please read the article in the Recommended Articles section.

Database Seeding

Database Seeding in Laravel helps us in feeding the database tables with test data. We can create seeding classes by extending the Seeder class provided by the Laravel.

Here is the code for the PollsTableSeeder class which will be used to seed the polls table:

<?php
// file: app/database/seeds/PollsTableSeeder.php

use Faker\Factory as Faker;

class PollsTableSeeder extends Seeder {

    public function run()
    {
        // going 'Faker' πŸ™‚ on the polls table.
        $faker = Faker::create();
        for($i = 1; $i <= 15 ; $i++)
        {   
            $poll = new Poll;
            $poll->question = preg_replace('/\.$/', '?', $faker->sentence());
            $poll->save();
            foreach ($faker->words as $option) {
                $stat = new Stat;
                $stat->option = $option;
                $stat->vote_count = 0;
                $poll->stats()->save($stat);
            }
        }
    }

}

Installing Faker
To install the Faker library, add "fzaninotto/faker": "1.3.*@dev" in the require-dev section of your composer.json file and run the composer update --dev command from Terminal.
For more details you can refer to the Faker documentation here.

Quick Tips:

  • We use artisan db:seed command to seed the database.
  • Add the following line inside the run method of DatabaseSeeder class (/app/database/seeds/DatabaseSeeder.php) before running the above command:
     $this->call('PollsTableSeeder');

A screenshot of the seeded polls table:

RESTful API using Laravel Tutorial
Seeded Polls Table

RESTful Routing

The Routing Component in Laravel provides us with an easy RESTful interface through its get, post, put and delete methods. Each of these methods accepts two arguments: the name of the route and a Closure defining the route action. Alternatively, we can also pass the name of a controller action as the second argument instead of the Closure. For our simple RESTful API, Routing Closures are more then enough — IMHO — so, I will be sticking to the first option i.e, Closures.

Note:
Route Closures are great for developing small applications and quick prototypes. For larger applications and better code organization, you should always consider using Controllers.

Here is the code for our app/routes.php:

<?php
// file: app/routes.php

Route::group(['prefix' => 'api', 'after' => 'allowOrigin'], function() {

    Route::get('/polls/{page}', function ($page) {
        return Response::json(['status' => 200,'polls' => Poll::skip(($page - 1) * 5)
                ->take(5)
                ->get(['id', 'question'])->toArray()
        ]);
    });

    Route::get('/poll/{id}', function ($id) {
        $poll = Poll::find($id);
        //out going format: {"status":200,"poll":{"id":"1","question":"What is your preferred framework for 2014 ?","options":["Laravel","PhalconPHP","CakePHP"]}}
        return Response::json(['status' => 200, 'poll' => $poll->toArray()]);
    });

    Route::post('/poll/{id}/option', function ($id) {
        $option = Input::get('option');
        $poll = Poll::find($id);
        $options = implode(',', $poll->options);
        $rules = [
            'option' => 'in:' . $options,
        ];
        $valid = Validator::make(compact('option'), $rules);
        if ($valid->passes()) {
            $poll->stats()->where('option','=',$option)->increment('vote_count');
            return Response::json(['status' => 200, 'mesg' => 'saved successfully!']);
        } else
            return Response::json(['status' => 400, 'mesg' => 'option not allowed!'],400);

    });

    Route::get('/stats/{id}', function ($id) {
        $poll = Poll::find($id);
        $stats = $poll->stats()
            ->select(['option', 'vote_count as stats'])
            ->get()->toArray();
        return Response::json(['status' => 200, 'stats' => $stats]);
    });
});

The GET polls/{page} route will return a JSON encoded array of five polls. The value of the offset in the query depends on the value of the $page variable. For example, if the value is 1, the query will retrieve the first five polls. Similarly, for a page value of 2, the next five polls starting from the sixth record in the polls table will be retrieved. The GET poll/{id} route will return a single poll with options as a JSON encoded object.

The next route POST poll/{id}/option, will increment the vote_count for the user’s selected / submitted option. The route action also validates the posted data to make sure that the submitted option belongs to the poll.

In the last route GET stats/{id}, we are querying the many side of the polls table i.e,stats to retrieve the vote_count for all the options of a single poll. This data is then returned as a part of the JSON response.

Enabling Cross-origin Resource Sharing
To enable simple CORS requests, we need to add the following header to the final response returned by the server:

Access-Control-Allow-Origin: *

In the above code, the after filter allowOrigin is applied to inject this header field in the final response returned by the API. Here is the code for this filter:

// file: app/filters.php

Route::filter('allowOrigin', function($route, $request, $response) 
{
    $response->header('access-control-allow-origin','*');
});

Testing with cURL

β”Œβ”€[usm4n@usm4n-desktop]―[~]
└─‒echo "`curl -s localhost:8000/api/poll/1`"
{"status":200,"poll":{"id":"1","question":"Sint minima aut autem corrupti aliquid corporis doloribus?","options":["consequuntur","voluptatem","odit"]}}
β”Œβ”€[usm4n@usm4n-desktop]―[~]
└─‒echo "`curl -s --data "option=odit" localhost:8000/api/poll/1/option`"
{"status":200,"mesg":"saved successfully!"}
β”Œβ”€[usm4n@usm4n-desktop]―[~]
└─‒echo "`curl -s --data "option=xyz" localhost:8000/api/poll/1/option`"
{"status":400,"mesg":"option not allowed!"}
β”Œβ”€[usm4n@usm4n-desktop]―[~]
└─‒echo "`curl -s localhost:8000/api/stats/1`"
{"status":200,"stats":[{"option":"consequuntur","stats":"0"},{"option":"voluptatem","stats":"0"},{"option":"odit","stats":"2"}]}

That’s all for now. If you need further clarification do not hesitate to ask!

2 thoughts on “Creating a RESTful API using Laravel for AngularJS Frontend”

  1. While I was developing API system for a project I got help from this page. I was not able to begin to coding, you gave me a start point. Thank you so much!

Leave a Reply

Your email address will not be published. Required fields are marked *

Time limit is exhausted. Please reload CAPTCHA.