Try Tuts+ Premium, Get Cash Back!
Laravel 4: A Start at a RESTful API

Laravel 4: A Start at a RESTful API

Tutorial Details
  • Difficulty: Intermediate
  • Completion Time: 30 Minutes

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.)

Note: Want to add some source code? Type <pre><code> before it and </code></pre> after it. Find out more
  • http://twitter.com/montogeek Fernando Montoya

    Exactly what i need, THANKS!

  • http://nikkobautista.com Nikko Bautista

    Awesome tutorial!

  • http://www.facebook.com/profile.php?id=676201327 Jake Toolson

    Excited for the L4 tutorials to start rolling in.

  • http://twitter.com/craigkeller Craig Keller

    This is awesome!

  • http://euantor.com/ euantor

    One of the best Laravel 4 tutorials yet. I’ve never used much of the Request class.

  • Ian Simmons

    last time I checked (a couple days ago) the artisan command key:generate was back in the beta so no need to manually put in md5(‘some key’). Should check with “php artisan list” on a regular basis while it’s in development to see when things that were removed get added back in.

    • fideloper
      Author

      Awesome, I’ll check on that.
      Thanks

  • Nathan Daly

    Thank you very much been waiting for an article such as this or even more on Laravel 4 itself, nice one Chris! :)

  • Alix G

    I’m pretty sure php composer.phar dump-autoload is not needed anymore

    • codedungeon

      That is what I was just asking? Is this confirmed?

  • mattsah

    Does anyone know if they fix non-available methods returning 404s yet?

    A lot of frameworks promote the idea that they are “restful” and what they tend to mean by that is that they support some way of differentiating various HTTP verbs and maybe a few common response types (like JSON). Laravel achieves this, but at least with version 3 it failed miserably at responding with appropriate HTTP status codes or having simple ways to manage accept types for example.

    I’ve done *a lot* of work for RESTful services over HTTP, and there’s is a ton that goes into it. For example, normalizing redirect types between HTTP 1.0 and 1.1, or if you don’t, ensuring you reject 1.0 if you plan to serve 1.1 types.

    I’ve yet to find a third-party framework which convinces me it’d make my life easier in this department.

    • fideloper
      Author

      Indeed! There are many, many aspects to consider. Most frameworks are working towards standard web-work and so API’s are more of an add-on, rather than a focus.

      Perhaps in the case where your API will be fairly robust, a truly smaller framework would help more than hinder (Silex comes to mind in the PHP world).

      It would be great to see a framework specifically for API’s. (NodeJS has a few, such as Restify)

      Laravel 4 does give you more potential control, both at error handling and controller/routing logic. With its use of Composer and packages you could *conceivably* use what you want and “overwrite” what you don’t like (For instance, standard controller logic to handle not-found methods).

      • mattsah

        Sorry @fideloper I just saw your sidenote. I don’t currently have any blogs around it, although it has been something I’ve considered writing about in the past. A lot of the stuff that has worked well over time are patterns that I’ve moreso baked into my framework, on 1.0 vs. 1.1 specifically normalizing 303/307 redirects to 302 for 1.0. Additionaly 40X responses (such as the 405 mentioned here) down to 400, etc.

        But even, for example on the 405, if the method is not allowed, it’ll go ahead an automatically specify the allowed methods via the Allow header.

        Things like that which create a very robust API which doesn’t duplicate or customize things that are nicely baked into HTTP.

    • http://twitter.com/taylorotwell Taylor Otwell

      Author of Laravel here. Yes, actually a lot of improvements were made in this area with Laravel 4, primarily thanks for the underlying code of the HttpFoundation component from Symfony, which handles a lot of this “nitty gritty” stuff. For example, as you mentioned, in Laravel 3 you would get a 404 when calling methods with the wrong HTTP verb, now you will get the proper 405 code with the proper headers.

      • mattsah

        Taylor, that’s good to hear. At least for web “apps” which I will differentiate from web sites in some ways, I’ve had the most success treating my URLs precisely the same as any API I might offer, i.e. accepting the same verbs, and just flipping based on expected content type.

        Good HTTP support massively helps this as your API doesn’t end up so dependent on the fluff of the content, and can be almost solely implemented against the spec for errors and things like that.

  • Adam Soffer

    Yes!

  • Fabio Alessandro Locati

    Am I wrong or for API Auth the Oauth is the only non-deprecated system?

  • Liam

    I checked. exactly what i need, thanks for share!

  • codedungeon

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

    Can’t artisan execute this command internally so we dont have to do it every time?

    • fideloper
      Author

      Possibly, but composer.phar can be in any location, not necessary inside the project root.

      For instance, I ALWAYS put it inside of a ‘bin’ folder, so on my computer, I actually run:
      $ php bin/composer.phar dump-autoload.

      Even better, tho, I believe Laravel is making some strives towards doing this automatically, or setting up autoloading in a way that it’s not necessary.

    • http://brayanrastelli.com/ Brayan Rastelli

      For those who don’t want to do the dump-autoload, here’s an example of a PSR-0 implementation by Dayle Rees:

      https://github.com/daylerees/autoload-demo

  • codedungeon

    Looking for the source code files for this tutorial, however, it does not appear in the members area as stated above You can grab source files and bonus tutorials from the members area.

  • http://twitter.com/rodripcg Rodrigore Campos

    Yes! Really cool. Keep going with Laravel.

  • http://twitter.com/fdammassa Fabrizio D’Ammassa

    Very interesting tutorial! Does Laravel provide an authentication method alternative to the basic one (e.g. token based)?

  • http://www.sinaneldem.com.tr Sinan Eldem

    Thanks. A good Tut.

  • http://brayanrastelli.com/ Brayan Rastelli

    Awesome article.

    As a complement, I recommend u guys to use Ardent.

    It’s a package for Laravel 4 that handles validation nicely, check it out:

    https://github.com/laravelbook/ardent

    • laravelbook

      @brayann:disqus: Thanks for the heads up!

  • http://twitter.com/SkyIron Andre Ferreira

    Great post. My question is How would you use a browser instead of using cURL as the client with the Read-It-Later app. How would you use a browser to send the password and username to get authenticated by the server….. e.g http://laravel:8888/api/v1/url?user&password…. Thank you in advance

    • fideloper
      Author

      You would likely need a browser plugin to help you do this. Keep in mind I used basic auth as an example. While some popular API’s have used it, most use oAuth (Which is a better, but more complex, solution)

  • laravelbook

    Great all-round tutorial!

    I have a package named Ardent for Laravel 4 which makes model validation a cinch.

    Details here: http://laravelbook.github.com/ardent/

    • fideloper
      Author

      Looks awesome!

    • jeff_way

      I’m curious – is Ardent identical to Aware, other than you updating it to work with L4? Or are there changes?

      • http://brayanrastelli.com/ Brayan Rastelli

        Seems like it’s just an update to L4.

      • SirElroyDaxter

        Hi Jeffrey, I have found a sql database analyzer and automatic blade template builder. It scaffolds Blade templates with backbone.js and other technologies. . It’s a small project, but looks very cool, because you can use it with a lot of frameworks. I am not the owner of the project. just somebody wh discovered it when I was googling vanilla php. I really want to know what you think of it. I think it is alien awesomeness https://github.com/jasonhinkle/phreeze/

  • http://twitter.com/KarimMaassen Karim Maassen

    Waiting on Jeffrey’s big Laravel 4 tutorial! ;)

    • jeff_way

      I’m going to get started on it tomorrow. :)

  • http://twitter.com/rei_liit John Kevin Basco

    This is really helpful. Thank you very much! :)

  • Pablo

    in what cases is convenient to use an API like this one instead of a classic server side app? great tutorial btw!!

  • http://www.facebook.com/chuckoh Jinseok Oh

    I wonder what’s the best approach to deal with extra parameters when using L4′s resourceful controllers.

    like, as in localhost/l4api/public/index.php/api/v1/urls?type=english&limit=10&offset=20

    should I make any extra routes for these? any comments would be appreciated.

  • http://twitter.com/psophy Sophy Prak

    Awesome tut… I hope you will provide more RESTful with L4

  • Scott

    question: I used composer to install the dependancies so I know it works. But when I tried to php composer.phar dump-autoload I got an error which read: Could not open input file: composer.phar. Any ideas as to what’s causing this? Awesome tutorial subject matter by the way.

    • Ian Simmons

      That message happens when the file is not found in the directory you are in. If you followed the quickstart link at the beginning of the tutorial you’ll have to either cd into the bin/ directory or type the full path “php bin/composer.phar dump-autoload” This is why I prefer to install composer globally on my system. Instructions for that global install HERE for both nix and win systems. Then you just type “composer dump-autoload” from the root of your app

  • The Shift Exchange

    If you are having trouble with the ‘seeds’ from this tutorial – this SO answer shows how to fix them for this tutorial, due to a change in beta code: http://stackoverflow.com/q/14791421/1317935

  • http://twitter.com/andreaswarnaar Andreas Warnaar

    Having some issues,
    In the migrations, you set the user and url timstamp without a parametes.
    But in dev-master d18a37a it gives a fatal error. (Missing param)..
    $table->timestamp(); Need column name.
    $table->timestamp(‘timestamp’); works for me.

    So far nice tut!

  • Andrew Del Prete

    Question: I’m very new to REST API Stuff, but how would one utilize the Auth capabilities as seen in this tut without having to pass a username and password with every request? I’m trying to build an Angular JS + Laravel application solely with REST so I’m looking for a solution where a user logs in once and then utilizes the API for the rest of the actions. Any help would be greatly appreciative.

    • NB

      comment out (do not filter)
      $this->beforeFilter(‘apiauth’);
      inside your resource controller’s construct (in this tut, UrlController.php);

  • http://gary.cheeseman.me.uk Gary Cheeseman

    I’m really liking Laravel 4. My question is about DRYing up the form code. I would like to use the same _form.blade.php file included within the edit and new, view files. What would be the recommended way to achieve this?

  • http://gary.cheeseman.me.uk Gary Cheeseman

    How do I use an L4 restful controller with form data. To add a new record create is called and the form submitted to store, if validation fails create is called again. How do I persist the form data to create?

  • Danny Swaby

    for anyone else who wonders why their database isnt adding any records when running ‘php artisan db:seed’ the process for seeding database has changed in laravel 4 since this tutorial http://four.laravel.com/docs/migrations#database-seeding

  • Eric

    How did you discover Request::getUser() ? I can’t find the documentation on it anywhere. Anyway, it returns null for me over curl. What are my options?

  • http://twitter.com/braunshizzle Braunson

    Much of this tutorial is outdated now, half the functions don’t work, It’s a good starting point but quite a bit of it (if you don’t know L3) has changed (if your using the latest dev branch) FYI.

  • http://twitter.com/sujalgaur Sujal Gaur

    Hi nice article but Request::get() is not working for me with backbone post data.
    I have used this Request::json() and its working.