Advertisement

Laravel 4: A Start at a RESTful API (Updated)

by
Student iconAre you a student? Get a yearly Tuts+ subscription for $45 →

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/HATEOS, 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.

We're going to first create an encryption key for secure password hashing. You can do this easily by running this command from your project root:

$ php artisan key:generate

Alternatively, you can simple edit your app/config/app.php encryption key:

/*
|--------------------------------------------------------------------------
| 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(Blueprint $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(Blueprint $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, UserTableSeeder.php. Add:

<?php

class UserTableSeeder extends Seeder {

    public function run()
    {
        DB::table('users')->delete();

        User::create(array(
            'username' => 'firstuser',
            'password' => Hash::make('first_password')
        ));

        User::create(array(
            'username' => 'seconduser',
            'password' => Hash::make('second_password')
        ));
    }

}

Next, make sure that seeder class gets run when the database is seeded. Edit app/database/seeds/DatabaseSeeder.php:

public function run()
{
    Eloquent::unguard();

    // Add or Uncomment this line
    $this->call('UserTableSeeder');
}

Run the Migrations

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

// Create the two tables
$ php artisan migrate

// Create the sample 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';

}

Authentication

Laravel's filters can handle authentication for us. In particular, Laravel now comes with a Basic Authentication filter, which we can use as a quick authentication model to be used with our API requests.

If you open app/filters.php, you'll see what it looks like:

Route::filter('auth.basic', function()
{
    return Auth::basic();
});

We just need to make one adjustment. By default, this filter looks for an "email" field to identify the user. Since we're using usernames instead of emails, we just need to adjust that preference. Change the Auth::basic() call by giving it our username field as a parameter:

Route::filter('auth.basic', function()
{
    return Auth::basic("username");
});

Routes

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

Edit app/routes.php:

Route::get('/authtest', array('before' => 'auth.basic', 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 -i localhost/l4api/public/index.php/authtest
HTTP/1.1 401 Unauthorized
Date: Tue, 21 May 2013 18:47:59 GMT
WWW-Authenticate: Basic
Vary: Accept-Encoding
Content-Type: text/html; charset=UTF-8

Invalid credentials

As you can see, an unauthorized request is detected and a "Invalid Credentials" message is returned with a 401 status code. Next, try including basic authentication.

$ curl --user firstuser:first_password localhost/l4api/public/index.php/authtest
HTTP/1.1 200 OK
Date: Tue, 21 May 2013 18:50:51 GMT
Vary: Accept-Encoding
Content-Type: text/html; charset=UTF-8

<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. We'll be using a Resourceful 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.

Create a Resourceful Controller

$ php artisan controller:make UrlController

Next, setup a route to use the controller, and require each route to be authenticated.

Edit app/routes.php and add:

// Route group for API versioning
Route::group(array('prefix' => 'api/v1', 'before' => 'auth.basic'), 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!

Note: You may want to consider more advanced API versioning techniques, such as using an Accept header or subdomain which can help you point different API versions separate code bases.

Add the Functionality

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

// Edit this:
public function index()
{
    return 'Hello, API';
}

Let's test it:

$ curl -i localhost/l4api/public/index.php/api/v1/url
HTTP/1.1 401 Unauthorized
Date: Tue, 21 May 2013 19:02:59 GMT
WWW-Authenticate: Basic
Vary: Accept-Encoding
Content-Type: text/html; charset=UTF-8

Invalid credentials.

$ curl --user firstuser:first_password localhost/l4api/public/index.php/api/v1/url
HTTP/1.1 200 OK
Date: Tue, 21 May 2013 19:04:19 GMT
Vary: Accept-Encoding
Content-Type: text/html; charset=UTF-8

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(array(
        'error' => false,
        'urls' => $urls->toArray()),
        200
    );
}

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 -i --user firstuser:first_password -d 'url=http://google.com&description=A Search Engine' localhost/l4api/public/index.php/api/v1/url
HTTP/1.1 201 Created
Date: Tue, 21 May 2013 19:10:52 GMT
Content-Type: application/json

{"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(array(
        'error' => false,
        'urls' => $urls->toArray()),
        200
    );
}

/**
 * Display the specified resource.
 *
 * @param  int  $id
 * @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(array(
        '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/1
{
    "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.
 *
 * @param  int  $id
 * @return Response
 */
public function destroy($id)
{
    $url = Url::where('user_id', Auth::user()->id)->find($id);

    $url->delete();

    return Response::json(array(
        'error' => false,
        'message' => 'url deleted'),
        200
        );
}

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

$ curl -i -X DELETE --user firstuser:first_password localhost/l4api/public/index.php/api/v1/url/1
HTTP/1.1 200 OK
Date: Tue, 21 May 2013 19:24:19 GMT
Content-Type: application/json

{"error":false,"message":"url deleted"}

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

/**
 * Update the specified resource in storage.
 *
 * @param  int  $id
 * @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(array(
        'error' => false,
        'message' => 'url updated'),
        200
    );
}

To test URL updates, run:

$ curl -i -X PUT --user seconduser:second_password -d 'url=http://yahoo.com' localhost/l4api/public/index.php/api/v1/url/4
HTTP/1.1 200 OK
Date: Tue, 21 May 2013 19:34:21 GMT
Content-Type: application/json

{"error":false,"message":"url updated"}

// View our changes
$ curl --user seconduser:second_password localhost/l4api/public/index.php/api/v1/url/4
{
    "error": false,
    "urls": [
        {
            "created_at": "2013-02-01 02:44:34",
            "description": "I feel for him",
            "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 library).
  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).
  4. Check out the API Craft Google Group
  5. Learn about the different types caching and how Validation Caching can improve your API
  6. Unit test your code
  7. Check out Apigee's great API resources
Advertisement