FB Web Tips

Laravel 4: A Start at a RESTful API

By Chris Fidao

RESTful API’s are hard! There are a lot of aspects to designing and writing a successful one. For instance, some of the topics that you may find yourself handling include authentication, hypermedia, versioning, rate limits, and content negotiation. Rather than tackling all of these concepts, however, let’s instead focus on the basics of REST. We’ll make some JSON endpoints behind a basic authentication system, and learn a few Laravel 4 tricks in the process.

 


The App

Let’s build an API for a simple Read-It-Later app. Users will be able to create, read, update and delete URLs that they wish to read later.

Ready to dive in and get started?

Install Laravel 4

Create a new install of Laravel 4. If you’re handy with CLI, try this quickstart guide. Otherwise, we have a video tutorial here on Nettuts+ that covers the process.

Edit your app/config/app.php encryption key for secure password hashing.

/*
|--------------------------------------------------------------------------
| Encryption Key
|--------------------------------------------------------------------------
|
| This key is used by the Illuminate encrypter service and should be set
| to a random, long string, otherwise these encrypted values will not
| be safe. Make sure to change it before deploying any application!
|
*/
'key' => md5('this is one way to get an encryption key set'),

Database

Once you have a working install of Laravel 4, we can get started with the fun. We’ll begin by creating the app’s database.

This will only require two database tables:

  1. Users, including a username and password
  2. URLs, including a url and description

We’ll use Laravel’s migrations to create and populate the database.

Configure Your Database

Edit app/config/database.php and fill it with your database settings. Note: this means creating a database for this application to use. This article assumes a MySQL database.

'connections' => array(
    'mysql' => array(
        'driver'    => 'mysql',
        'host'      => 'localhost',
        'database'  => 'read_it_later',
        'username'  => 'your_username',
        'password'  => 'your_password',
        'charset'   => 'utf8',
        'collation' => 'utf8_unicode_ci',
        'prefix'    => '',
    ),
),

Create Migration Files

$ php artisan migrate:make create_users_table --table=users --create
$ php artisan migrate:make create_urls_table --table=urls --create

These commands set up the basic migration scripts that we’ll be using to create the database tables. Our job now is to fill them with the correct table columns.

Edit app/database/migrations/SOME_DATE_create_users_table.php and add to the up() method:

public function up()
{
    Schema::create('users', function($table)
    {
        $table->increments('id');
        $table->string('username')->unique();
        $table->string('password');
        $table->timestamps();
    });
}

Above, we’re setting a username (which should be unique), a password, as well as the timestamps. Save that, and now edit app/database/migrations/SOME_DATE_create_urls_table.php, and add to the up() method:

public function up()
{
    Schema::create('urls', function($table)
    {
        $table->increments('id');
        $table->integer('user_id');
        $table->string('url');
        $table->string('description');
        $table->timestamps();
    });
}

The only important note in this snippet is that we’re creating a link between the url and users table, via the user_id field.

Add Sample Users

We can use Laravel’s seeds to create a few sample users.

Create a file within the app/database/seeds folder that has the same name as the table that it corresponds to; in our case, users.php. Add:

<?php
return [
    [
        'username' => 'firstuser',
        'password' => Hash::make('first_password'),
        'created_at' => new DateTime,
        'updated_at' => new DateTime
    ],
    [
        'username' => 'seconduser',
        'password' => Hash::make('second_password'),
        'created_at' => new DateTime,
        'updated_at' => new DateTime
    ]
];

Run the Migrations

Here’s how to create those two tables, and insert our sample users.

// Make sure the auto-loader knows about these new classes
$ php composer.phar dump-autoload
// Create the tables
$ php artisan migrate
// Create the users
$ php artisan db:seed

Models

Laravel 4 continues to use the excellent Eloquent ORM. This will make the process of handling database calls a snap. We’ll require one model per table.

Luckily, Laravel comes with a User model setup, so let’s create a model for our urls table.

Create and edit file app/models/Url.php.

<?php
class Url extends Eloquent {
    protected $table = 'urls';
}

To ensure that Laravel knows about this new class, run a dump-autoload again.

$ php composer.phar dump-autoload

Authentication

Laravel’s filters can handle authentication for us.

Open app/filters.php. Note the auth filter, which already exists:

Route::filter('auth', function()
{
    if (Auth::guest()) return Redirect::route('login');
});

If a user isn’t logged in, he or she will be directed to the login route. This filter is more friendly to a typical browser experience. As we’re building an API, however, let’s create an authentication filter specifically for API calls.

Route::filter('apiauth', function()
{
    // Test against the presence of Basic Auth credentials
    $creds = array(
        'username' => Request::getUser(),
        'password' => Request::getPassword(),
    );
    if ( ! Auth::attempt($creds) ) {
        return Response::json([
            'error' => true,
            'message' => 'Unauthorized Request'],
            401
        );
    }
});

This looks for a Basic Authentication user and password and attempts to authenticate against them. If the user is not found, we return a “401 Not Authorized” response.

Routes

Let’s test this out. Create a route, called testauth, and make sure that our apiauth filter runs before it.

Edit app/routes.php:

Route::get('/authtest', array('before' => 'apiauth', function()
{
    return View::make('hello');
}));

We can test this with a curl request. From your terminal, try pointing to your build of Laravel. In mine, it looks like this (Your URL will likely be different!):

$ curl localhost/l4api/public/index.php/authtest
{"error":true,"message":"Unauthorized Request"}

As you can see, an unauthorized request is detected and a “Not Authorized” message is returned. Next, try including basic authentication.

$ curl --user firstuser:first_password localhost/l4api/public/index.php/authtest
<h1>Hello World!</h1>

It worked!

At this point, the baseline work of our API is done. We have:

  • Installed Laravel 4
  • Created our database
  • Created our models
  • Created an authentication model

Creating Functional Requests

You may be familiar with Laravel’s RESTful controllers. They still exist in Laravel 4; however, we can also use Laravel’s Resourceful Controllers, which set up some paradigms that we can use to make a consistent API interface.

Create a Resourceful Controller

$ php artisan controller:make UrlController
$ php composer.phar dump-autoload # Yep, this again!

Next, setup a route to use the controller.

Edit app/routes.php and add:

// Route group for API versioning
Route::group(array('prefix' => 'api/v1'), function() {
    Route::resource('url', 'UrlController');
});

A few things are happening there.

  1. This is going to respond to requests made to http://example.com/api/v1/url.
  2. This allows us to add extra routes, if we need to expand our API. For instance, if you add a user end-point, such as /api/v1/user.
  3. There is also a naming mechanism in place for versioning our API. This gives us the opportunity to roll out new API versions without breaking older versions – We can simply create a v2 route group, and point it to a new controller!

Here’s a breakdown of what each method in the resourceful controller will handle. Please note that you can remove the /resource/create and /resource/{id}/edit routes, since we won’t be needing to show ‘create’ or ‘edit’ forms in an API.

Add the Functionality

Edit the new app/controllers/UrlController.php file:

// Add this:
public function __construct()
{
    $this->beforeFilter('apiauth');
}
// Edit this:
public function index()
{
    return 'Hello, API';
}

Let’s test it:

$ curl localhost/l4api/public/index.php/api/v1/url
{"error":true,"message":"Unauthorized Request"}
$ curl --user firstuser:first_password localhost/l4api/public/index.php/api/v1/url
Hello, API

We now have a resourceful controller with authentication working, and are ready to add functionality.

Create a URL

Edit app/controllers/UrlController.php:

/**
* Store a newly created resource in storage.
*
* @return Response
*/
public function store()
{
    $url = new Url;
    $url->url = Request::get('url');
    $url->description = Request::get('description');
    $url->user_id = Auth::user()->id;
    // Validation and Filtering is sorely needed!!
    // Seriously, I'm a bad person for leaving that out.
    $url->save();
    return Response::json([
            'error' => false,
            'message' => 'URL created'],
            201
        );
}

It’s time to test this with another curl request. This one will send a POST request, which will correspond to the store() method created above.

$ curl --user firstuser:first_password -d 'url=http://google.com&description=A Search Engine' localhost/l4api/public/index.php/api/v1/url
{"error":false,"message":"URL created"}

Cool! Let’s create a few more, for both of our users.

$ curl --user firstuser:first_password -d 'url=http://fideloper.com&description=A Great Blog' localhost/l4api/public/index.php/api/v1/url
$ curl --user seconduser:second_password -d 'url=http://digitalsurgeons.com&description=A Marketing Agency' localhost/l4api/public/index.php/api/v1/url
$ curl --user seconduser:second_password -d 'url=http://www.poppstrong.com/&description=I feel for him' localhost/l4api/public/index.php/api/v1/url

Next, let’s create methods for retrieving URLs.

/**
 * Display a listing of the resource.
 *
 * @return Response
 */
public function index()
{
    //Formerly: return 'Hello, API';
    $urls = Url::where('user_id', Auth::user()->id)->get();
    return Response::json([
          'error' => false,
          'urls' => $urls->toArray()],
          201
      );
}
/**
 * Display the specified resource.
 *
 * @return Response
 */
public function show($id)
{
    // Make sure current user owns the requested resource
    $url = Url::where('user_id', Auth::user()->id)
            ->where('id', $id)
            ->take(1)
            ->get();
    return Response::json([
          'error' => false,
          'urls' => $url->toArray()],
          200
      );
}

Let’s test them out:

$ curl --user firstuser:first_password localhost/l4api/public/index.php/api/v1/url
{
      "error": false,
      "urls": [
          {
              "created_at": "2013-02-01 02:39:10",
              "description": "A Search Engine",
              "id": "2",
              "updated_at": "2013-02-01 02:39:10",
              "url": "http://google.com",
              "user_id": "1"
          },
          {
              "created_at": "2013-02-01 02:44:34",
              "description": "A Great Blog",
              "id": "3",
              "updated_at": "2013-02-01 02:44:34",
              "url": "http://fideloper.com",
              "user_id": "1"
          }
      ]
  }
$ curl --user firstuser:first_password localhost/l4api/public/index.php/api/v1/url/2
{
    "error": false,
    "urls": [
        {
            "created_at": "2013-02-01 02:39:10",
            "description": "A Search Engine",
            "id": "2",
            "updated_at": "2013-02-01 02:39:10",
            "url": "http://google.com",
            "user_id": "1"
        }
    ]
}

Almost done. Let’s now allow users to delete a url.

/**
 * Remove the specified resource from storage.
 *
 * @return Response
 */
public function destroy($id)
{
    $url = Url::where('user_id', Auth::user()->id)->find($id);
    $url->delete();
    return Response::json([
          'error' => false,
          'message' => 'url deleted'],
          200
      );
}

Now, we can delete a URL by using a DELETE request:

$ curl -X DELETE --user firstuser:first_password localhost/l4api/public/index.php/api/v1/url/2
{"error":false,"message":"url deleted"}

Lastly, let’s allow users to update a url.

/**
 * Update the specified resource in storage.
 *
 * @return Response
 */
public function update($id)
{
    $url = Url::where('user_id', Auth::user()->id)->find($id);
    if ( Request::get('url') )
    {
        $url->url = Request::get('url');
    }
    if ( Request::get('description') )
    {
        $url->description = Request::get('description');
    }
    $url->save();
    return Response::json([
          'error' => false,
          'message' => 'url updated'],
          200
      );
}

To test URL updates, run:

$ curl -X PUT --user seconduser:second_password -d 'url=http://yahoo.com' localhost/l4api/public/index.php/api/v1/url/3
{"error":false,"message":"url updated"}
// View our changes
$ curl --user seconduser:second_password localhost/l4api/public/index.php/api/v1/url/3
    {
      "error": false,
      "urls": [
          {
              "created_at": "2013-02-01 02:44:34",
              "description": "A Search Engine",
              "id": "3",
              "updated_at": "2013-02-02 18:44:18",
              "url": "http://yahoo.com",
              "user_id": "1"
          }
      ]
  }

And That’s It!

We now have the beginnings of a fully-functioning API. I hope that you’ve learned a lot about how to get an API underway with Laravel 4.

To recap, we achieved the following in this lesson:

  1. Install Laravel
  2. Create the database, using migrations and seeding
  3. Use Eloquent ORM models
  4. Authenticate with Basic Auth
  5. Set up Routes, including versioning the API
  6. Create the API functionality using Resourceful Controllers

The Next Steps

If you’d like to push your API up a notch, you might consider any of the following as a next step.

  1. Validation (Hint: Laravel has a Validation libary.)
  2. API-request error handling – It’s still possible to receive HTML response on API requests (Hint: Laravel Error Handling, plus Content Negotiation.)
  3. Content Negotiation – listening for the Accept header. (Hint: Laravel’s Request class will give you the request headers.)

Source: Nettuts+

I've worked very hard for this, so please help me by donating a cup of coffee. Thanks in advance.

0 replies

Leave a Reply

Want to join the discussion?
Feel free to contribute!

Leave a Reply

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