Uno sguardo alle API RESTful

In questo articolo scoprirai le basi della creazione di API RESTful... ovviamente con Laravel!
francesco
Marco Spada
25/04/2014 in Tutorial

Traduzione dell'articolo "Laravel 4: A Start at a RESTful API" su Tuts+

L’approccio RESTful è favoloso ma richiede un pò di applicazione. Ci sono molti aspetti di cui dovrai occuparti durante la progettazione e l’implementazione.

Dovrai, ad esempio, occuparti dell’autenticazione, delle transizioni tra uno stato e l’altro (hypermedia/HATEOS), del versioning, dei limiti della velocità di banda e della negoziazione dei contenuti.

Chiaramente in questo articolo non tratteremo tutti questi aspetti ma solo di come si comporta REST a livello base usando Laravel. Realizzeremo alcuni endpoint JSON e un sistema di autenticazione di base imparando alcuni trucchi.

L’applicazione

Costruiremo un semplice sistema che permette di gestire dei collegamenti che l’utente vuole leggere più tardi. L’utente potrà creare, leggere, aggiornare e cancellare gli url. Niente di complesso.

Passiamo ai fatti e iniziamo a realizzare la nostra applicazione.

Installare Laravel 4

Come prima cosa installiamo Laravel 4. Puoi farlo semplicemente seguendo la nostra guida su come installare Laravel oppure da riga di comando seguendo questa guida. Infine posso suggeriti questo video tutorial su Nettuts+.

Dopo l’installazione andiamo a creare la nostra encryption key per la sicurezza delle password. Per farlo puoi usare il seguente comando Artisan posizionandoti sulla root del tuo progetto:

$ php artisan key:generate

Se non vuoi usare Artisan nessun problema, puoi modificare il file app/config/app.php alla voce 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('inserisci qui la tua chiave'),

Bene, dopo l’impostazione dell’encryption key andiamo avanti creando il nostro database.

Per semplicità costruiremo solo le tabelle di cui abbiamo bisogno:

  1. Users, con i campi username e password;
  2. URLs, con l’url e la descrizione;

Per creare e popolare il nostro database, useremo il sistema delle migrations.

Configurare il Database

Assumiamo di avere un database MySQL e configuriamo di conseguenza la connessione al database andando a modificare il file app/config/database.php.

'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'    => '',
    ),
),

Creazione delle Migration

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

Con questi comandi creeremo i file di migrazione per le nostre tabelle. Di seguito andiamo a modificare i file per aggiungere le colonne che ci interessano.

Apriamo il file migration per la tabella Users che sarà qualcosa come: app/database/migrations/DATA_create_users_table.php ed editiamolo così:

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

Abbiamo specificato uno username, che deve essere unico, una password e il timestamp. Salviamo e andiamo a modificare l’altro file di migration che sarà simile a app/database/migrations/DATA_create_urls_table.php, aggiungendo il metodo up() anche qui:

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

Molto simile al precedente se non per il fatto che abbiamo creato un link tra le due tabelle attraverso il campo user_id.

Aggiungiamo un utente

Possiamo usare il meccanismo dei seed per creare un utente di esempio.

Procediamo così: creiamo un file all’interno della cartella app/database/seeds con lo stesso nome della tabella su cui effettuare l’inserimento. Nel nostro caso quindi sarà UserTableSeeder.php. Modifichiamolo come di seguito:

<?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')
        ));
    }

}

Successivamente assicuriamoci di eseguire il seeding sul database modificando il file app/database/seeds/DatabaseSeeder.php:

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

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

Eseguiamo le Migration

Andiamo ora ad eseguire le migration per la creazione delle tabelle e rimpiamo il nostro database con l’operazione di seed. Apriamo la nostra riga di comando e digitiamo:

// Create the two tables
$ php artisan migrate

// Create the sample users
$ php artisan db:seed

Model

Come è noto, Laravel 4 usa Eloquent ORM che permette una facile gestione database. Andiamo quindi a creare un model per ogni tabella. Per quanto riguarda il model User, Laravel ne ha già uno di default e quindi non resta che creare il model Url in app/models/Url.php e modificarlo come segue:

<?php

class Url extends Eloquent {

    protected $table = 'urls';

}

Autenticazione

Attraverso i filtri di Laravel possiamo gestire l’autenticazione. Useremo in questo caso il filtro di Basic Authentication per gestire i nostri model con le richieste API. Andiamo ad aprire il file app/filters.php e notiamo il seguente codice:

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

Facciamo un piccolo aggiustamento. Di default questo filtro controlla l’indirizzo email come campo per l’identificazione. Dal momento che stiamo utilizzando lo username, andiamo a specificarlo come parametro del metodo Auth::basic() in questo modo:

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

Routes

Facciamo un test per vedere se tutto è ok. Creiamo una nuova route, chiamandola testauth, e verifichiamo che il nostro filtro auth.basic agisca prima della chiamata al controller. A tal fine editiamo app/routes.php:

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

Bene, siamo pronti per un primo vero test. Apriamo il nostro terminale e facciamo una richiesta curl. La richiesta dovrà chiaramente puntare al tuo url e quindi sarà simile alla seguente:

$ 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

Come puoi vedere, otterrai uno status code 401 e il conseguente messaggio di "Invalid Credentials". Questo perché non abbiamo fatto una richiesta con autenticazione. Proviamo ora a inviare una richiesta con username e password in questo modo e vediamo cosa succede:

$ 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>

Il gioco è fatto! Non ci crederai, ma abbiamo già sistemato tutta la parte basilare del nostro lavoro. Riepilogando, in breve, abbiamo:

  • Installato Laravel 4
  • Creato il database
  • Creato i nostri models
  • Creato un model per l’autenticazione

Creazione di richieste funzionali

Se non l’hai ancora fatto, ti consiglio di leggere qualcosa sui RESTful controllers. Questo ti aiuterà a capire i Resourceful Controllers che useremo nel nostro lavoro per realizzare i giusti paradigmi al fine di implementare un API consistente.

Qui puoi farti un’idea di quello che questo tipo di controller è in grado di gestire. Prima di iniziare ti anticipo che puoi eliminare i metodi /resource/create e /resource/{id}/edit poiché non abbiamo bisogno di mostrare form di creazione o modifica.

Creare un Resourceful Controller

Partiamo con la creazione del controller. Quindi apriamo un terminale e digitiamo:

$ php artisan controller:make UrlController

Successivamente andiamo ad impostare la nostra route modificandola in questo modo:

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

In queste righe di codice ci sono alcune cose molto interessanti.

  1. L’url http://example.com/api/v1/url risponderà alle nostre richieste;
  2. Possiamo aggiungere altre route all’interno di questa per espandere la nostra API. Ad esempio possiamo aggiungere l’end-point /api/v1/user;
  3. Abbiamo creato un meccanismo di versioning per la nostra API. Questo ci dà l'opportunità di implementare le nuove versioni delle API senza toccare le vecchie versioni. Possiamo semplicemente creare una route v2, e puntare ad un nuovo controller;

Nota: puoi prendere in considerazione teniche più avanzate per gestire il versioning delle API usando ad esempio un Accept header o un sottodominio per puntare a differenti versioni dell’API.

Aggiungere Funzionalità

Modifichiamo il nostro controller app/controllers/UrlController.php:

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

Testiamolo:

$ 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

Ora abbiamo un resourceful controller con autenticazione perfettamente funzionante e siamo pronti per aggiungere le più svariate funzionalità alla nostra applicazione.

Creare un URL

Modifichiamo app/controllers/UrlController.php:

public function store()
{
    $url = new Url;
    $url->url = Request::get('url');
    $url->description = Request::get('description');
    $url->user_id = Auth::user()->id;

    $url->save();

    return Response::json(array(
        'error' => false,
        'urls' => $urls->toArray()),
        200
    );
}

Dopo questa modifica testiamo il tutto con una richiesta curl. Inviamo una richiesta POST che corrisponderà al metodo **store()**appena creato.

$ 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"}

Se tutto è andato bene, andiamo ad aggiungere delle url per entrambi i nostri utenti.

$ 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

Bene, ora andiamo a creare dei metodi per recuperare le URL come di seguito:

public function index()
{
    //In precedenza era: return 'Hello, API';

    $urls = Url::where('user_id', Auth::user()->id)->get();

    return Response::json(array(
        'error' => false,
        'urls' => $urls->toArray()),
        200
    );
}

public function show($id)
{
    $url = Url::where('user_id', Auth::user()->id)
            ->where('id', $id)
            ->take(1)
            ->get();

    return Response::json(array(
        'error' => false,
        'urls' => $url->toArray()),
        200
    );
}

Facciamo il nostro consueto test:

$ 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"
        }
    ]
}

Ok ci siamo quasi. Consentiamo ora all’utente di cancellare un’ URL

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
        );
}

Ora possiamo cancellare un’URL semplicemente effettuando una richiesta DELETE

$ 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"}

Infine, consentiamo all’utente di effettuare l’ update di un’url

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
    );
}

Testiamo il tutto con la seguente richiesta:

$ 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"}

Lavoro completato!

Con questo articolo ho posto solo le basi per un API completa e funzionante. Spero ti possa essere d’aiuto per costruire qualcosa di tuo tuo.

Facciamo un riepilogo di ciò che abbiamo implementato:

  1. Installazione di Laravel
  2. Creazione del database usando il sistema delle migrations e il seeding
  3. Utilizzo di Eloquent ORM models
  4. Autenticazione con il filtro Basic Auth
  5. Impostazione delle route con gestione del versioning per l’ API
  6. Abbiamo utilizzato l’approccio RESTful (resourceful controllers) per gestire le funzionalità della nostra API

Quali sono i prossimi passi?

Nello sviluppare la nostra applicazione abbiamo tralasciato alcuni aspetti per motivi pratici. Tu puoi continuare a far crescere la tua API aggiungendo ad esempio:

  1. La validazione dei dati (Validazione).
  2. La gestione degli errori per le richieste. Ad esempio puoi ricevere delle risposte in HTML (Laravel Error Handling)
  3. Gestione degli header http (Laravel's Request Class).
  4. Dai uno sguardo anche a API Craft Google Group
  5. Gestire la cache (different types caching) e come il Validation Caching può aumentare le prestazioni della tua API.
  6. Unit test
  7. Dai uno sguardo a questo ebook Apigee's great API resources