Laravel 4: Começando com API RESTful
() translation by (you can also view the original English article)
APIs RESTful são difíceis! Há muitos aspectos quando se quer planejar e criar uma que seja bem sucedida. Por exemplo, alguns dos tópicos que você acabará lidando, incluem auntenticação, hipermídia/HATEOS, versionamento, limitação de uso e negociação de conteúdo. Ao invés de lidar com todos esses conceitos de uma vez, vamos focar nos conceitos básicos do REST. Criaremos alguns pontos de acesso JSON protegidos por um sistema básico de autenticação, e aprenderemos alguns truques do Laravel 4 no caminho.
A Aplicação
Criaremos uma API para uma simples app de administração e leitura de artigos da internet. Os usuários serão capazes de criar, ler, atualizar e apagar URLs (links) que eles salvaram para ler depois. Pronto para começar?
Instale o Laravel 4
Crie uma nova instalação do Laravel 4. Se você sabe usar a linha de comando, use esse guia básico. Se não, nós temos um tutorial em vídeo, aqui no Nettuts+ que cobre todo o processo.
Nós, primeiro, criaremos uma chave de criptografia para segurar as senhas. Você pode fazer isso, facilmente, executando o comando a seguir, na linha de comando, tendo como diretório base o diretório raiz do seu projeto:
1 |
$ php artisan key:generate
|
Alternativamente, você pode editar, diretamente, o arquivo app/config/app.php
e inserir sua chave de criptografia:
1 |
/*
|
2 |
|--------------------------------------------------------------------------
|
3 |
| Encryption Key
|
4 |
|--------------------------------------------------------------------------
|
5 |
|
|
6 |
| This key is used by the Illuminate encrypter service and should be set
|
7 |
| to a random, long string, otherwise these encrypted values will not
|
8 |
| be safe. Make sure to change it before deploying any application!
|
9 |
|
|
10 |
*/
|
11 |
|
12 |
'key' => md5('altere esse texto para o que será sua chave de criptografia'), |
Usando a função md5()
, ao invés de uma cadeia de caracteres fixa, criará sua chave de criptografia. O método da linha de comando é mais aconselhável
Base de Dados
Assim que você tiver com uma cópia do Laravel 4 funcionando, poderemos começar a nos divertir de verdade. Começaremos criando a base de dados da app.
Ela só vai precisar de duas tabelas:
- Users, com os campos username e password
- URLs, com os campos url e description
Nós usaremos as migrações (do inglês, migrations) do Laravel para criar e popular a base de dados.
Configure sua Base de Dados
Edite o arquivo app/config/database.php
preenchendo com as configurações da sua base de dados. Nota: é importante criar uma base de dados para ser usada com essa aplicação. Nesse artigo, assumiremos que você usará uma base de dados MySQL.
1 |
'connections' => array( |
2 |
|
3 |
'mysql' => array( |
4 |
'driver' => 'mysql', |
5 |
'host' => 'localhost', |
6 |
'database' => 'nome_da_base_de_dados', |
7 |
'username' => 'usuario_do_mysql', |
8 |
'password' => 'senha_do_mysql', |
9 |
'charset' => 'utf8', |
10 |
'collation' => 'utf8_unicode_ci', |
11 |
'prefix' => '', |
12 |
),
|
13 |
),
|
Criando os Arquivos de Migração
1 |
$ php artisan migrate:make create_users_table --table=users --create |
2 |
$ php artisan migrate:make create_urls_table --table=urls --create |
Esses comandos criarão os arquivos necessários que usaremos para gerar nossas tabelas no banco de dados. Nosso trabalho, agora, é preenchê-los com as colunas corretas das tabelas.
Edite o arquivo app/database/migrations/SOME_DATE_create_users_table.php
e adicione o método up()
:
1 |
public function up() |
2 |
{
|
3 |
Schema::create('users', function(Blueprint $table) |
4 |
{
|
5 |
$table->increments('id'); |
6 |
$table->string('username')->unique(); |
7 |
$table->string('password'); |
8 |
$table->timestamps(); |
9 |
});
|
10 |
}
|
No código acima, estamos criando os campos username (que deve ser único), password, bem como os campos timestamps (campos de criação e alteração do registro da tabela). Salve o arquivo e, então, edite o app/database/migrations/SOME_DATE_create_urls_table.php
(SOME_DATE está no formato YYYY_MM_DD_HMS), e adicione o método up()
, como o abaixo:
1 |
public function up() |
2 |
{
|
3 |
Schema::create('urls', function(Blueprint $table) |
4 |
{
|
5 |
$table->increments('id'); |
6 |
$table->integer('user_id'); |
7 |
$table->string('url'); |
8 |
$table->string('description'); |
9 |
$table->timestamps(); |
10 |
});
|
11 |
}
|
O único ponto importante nesse trecho de código é que estamos criando um elo entre a tabela url
e users
, através do campo user_id
.
Adicionando Usuários de Exemplo
Nós podemos usar os semeadores (do inglês, seeder) do Laravel para criar alguns usuários para usarmos de exemplo.
Crie um arquivo dentro da pasta app/database/seeds
com o mesmo nome da tabela que ele preencherá; no nosso caso, UserTableSeeder.php
. Digite:
1 |
<?php
|
2 |
|
3 |
class UserTableSeeder extends Seeder { |
4 |
|
5 |
public function run() |
6 |
{
|
7 |
DB::table('users')->delete(); |
8 |
|
9 |
User::create(array( |
10 |
'username' => 'firstuser', |
11 |
'password' => Hash::make('first_password') |
12 |
));
|
13 |
|
14 |
User::create(array( |
15 |
'username' => 'seconduser', |
16 |
'password' => Hash::make('second_password') |
17 |
));
|
18 |
}
|
19 |
|
20 |
}
|
Depois disso, garanta que a classe semeadora seja executada quando a base de dados tiver de ser semeada. Edite o arquivo app/database/seeds/DatabaseSeeder.php
:
1 |
public function run() |
2 |
{
|
3 |
Eloquent::unguard(); |
4 |
|
5 |
// Adicione ou remova o comentário da linha abaixo
|
6 |
$this->call('UserTableSeeder'); |
7 |
}
|
Execute as Migrações
São os comandos a seguir que permitirão os códigos acima criar as duas tabelas e inserir os usuários de exemplo.
1 |
// Cria as duas tabelas
|
2 |
$ php artisan migrate |
3 |
|
4 |
// Cria os usuários exemplo
|
5 |
$ php artisan db:seed |
Modelos
O Laravel 4 continua a usar o excelente ORM Eloquent. Por conta disso, lidar com as chamadas à base de dados será moleza. Nós utilizaremos um modelo por tabela.
Por sorte, o Laravel vem com um modelo User já pronto. A nós, somente resta a criação do modelo para nossa tabela urls.
Crie e edite o arquivo app/models/Url.php
.
1 |
<?php
|
2 |
|
3 |
class Url extends Eloquent { |
4 |
|
5 |
protected $table = 'urls'; |
6 |
|
7 |
}
|
Autenticação
Os filtros são os responsáveis por lidar com a autenticação para nós. Peculiarmente, agora, o Laravel vem com um filtro de Autenticação Básica (via HTTP), o qual podemos utilizar como um simples modelo de autenticação para as requisições à nossa API.
Se você abrir o arquivo app/filters.php
, você verá como ele é:
1 |
Route::filter('auth.basic', function() |
2 |
{
|
3 |
return Auth::basic(); |
4 |
});
|
Nós só precisamos fazer um ajuste. Por padrão, o filtro busca pelo campo "email" da tabela para identificar o usuário. Como estamos lidando com o campo "username" (nomes de usuários), devemos indicar ao filtro que ele deve usar esse campo. Mude a chamada ao método estático Auth::basic()
para que ele receba o campo "username" como parâmetro:
1 |
Route::filter('auth.basic', function() |
2 |
{
|
3 |
return Auth::basic("username"); |
4 |
});
|
Rotas
Vamos testar a aplicação. Crie uma rota chamada testauth
, e faça com que o filtro auth.basic
seja executado logo antes dela.
Edite o arquivo app/routes.php
:
1 |
Route::get('/authtest', array('before' => 'auth.basic', function() |
2 |
{
|
3 |
return View::make('hello'); |
4 |
}));
|
Podemos verificar se está funcionando, ao fazer uma requisição curl. Da sua linha de comando, faça uma requisição para o endereço local da sua aplicação Laravel. No meu caso, se parece com isso (Sua URL, provavelmente, será diferente!):
1 |
$ curl -i localhost/l4api/public/index.php/authtest |
2 |
HTTP/1.1 401 Unauthorized |
3 |
Date: Tue, 21 May 2013 18:47:59 GMT |
4 |
WWW-Authenticate: Basic |
5 |
Vary: Accept-Encoding |
6 |
Content-Type: text/html; charset=UTF-8 |
7 |
|
8 |
Invalid credentials |
Como você pode ver, uma requisição não autorizada (HTTP/1.1 401 Unauthorized) foi detectada e a mensagem "Invalid Credentials" (em português, Credenciais Inválidas) foi retornada com um código de estado 401. Agora, tente a mesma requisição passando o nome de usuário e senha (username e password).
1 |
$ curl --user firstuser:first_password localhost/l4api/public/index.php/authtest |
2 |
HTTP/1.1 200 OK |
3 |
Date: Tue, 21 May 2013 18:50:51 GMT |
4 |
Vary: Accept-Encoding |
5 |
Content-Type: text/html; charset=UTF-8 |
6 |
|
7 |
<h1>Hello World!</h1> |
Funcionou!
Até agora, a base de nossa API está pronta. Nós já temos:
- Instalação do Laravel 4
- Base de Dados criada
- Modelos criados
- Modelo de Autenticação criado
Criando Requisições Funcionais
Você já deve conhecer os controladores RESTful do Laravel. Eles ainda existem no Laravel 4; contudo, nós também podemos usar os controladores inteligentes (Resourceful Controllers) do Laravel, que cria um paradigma que podemos usar para criar uma API consistente. Nós usaremos esses controladores inteligentes.
Eis um resumo do que cada controlador inteligente é capaz de lidar. Atente que você pode desconsiderar as rotas /resource/create e /resource/{id}/edit, uma vez que não mostraremos formulários para criar ("create") ou editar ("edit") formulários em um API.
Criando Controladores Inteligentes
1 |
$ php artisan controller:make UrlController
|
Agora, crie uma rota para usar o controlador e faça com que todas as rotas precisem de autenticação.
Edite o arquivo app/routes.php
e adicione:
1 |
// Agrupamento de rota para garantir versionamento da API
|
2 |
Route::group(array('prefix' => 'api/v1', 'before' => 'auth.basic'), function() |
3 |
{
|
4 |
Route::resource('url', 'UrlController'); |
5 |
});
|
Eis o que está acontecendo:
- Essa rota responderá a requisições feitas ao ponto de acesso
http://example.com/api/v1/url
. - Isso permite que adicionemos rotas extras, se precisarmos expandir nossa API. Por exemplo, você pode adicionar um ponto de acesso aos usuários, como
/api/v1/user
.
- Também há uma mecanismo de nomeação de rotas para o versionamento da API, permitindo que lancemos novas versões de nossa API sem quebrar a comaptibilidade com versões anteriores – basta criarmos um agrupamento de rotas chamado v2 e apontá-lo para um novo controlador!
Nota: Talvez você possa querer usar técnicas mais avançadas de versionamento de APIs, como através do uso do cabeçalho HTTP Accept
ou através de um subdomínio, os quais podem ajudar você a ter diferentes conjuntos de código para cada uma das versões da API em questão.
Adicionando Funcionalidade
Edite o arquivo app/controllers/UrlController.php
recém-criado:
1 |
// Edite isso:
|
2 |
public function index() |
3 |
{
|
4 |
return 'Hello, API'; |
5 |
}
|
Vamos testá-lo:
1 |
$ curl -i localhost/l4api/public/index.php/api/v1/url |
2 |
HTTP/1.1 401 Unauthorized |
3 |
Date: Tue, 21 May 2013 19:02:59 GMT |
4 |
WWW-Authenticate: Basic |
5 |
Vary: Accept-Encoding |
6 |
Content-Type: text/html; charset=UTF-8 |
7 |
|
8 |
Invalid credentials. |
9 |
|
10 |
$ curl --user firstuser:first_password localhost/l4api/public/index.php/api/v1/url |
11 |
HTTP/1.1 200 OK |
12 |
Date: Tue, 21 May 2013 19:04:19 GMT |
13 |
Vary: Accept-Encoding |
14 |
Content-Type: text/html; charset=UTF-8 |
15 |
|
16 |
Hello, API |
Agora, temos um novo controlador inteligente com sistema de autenticação básica funcionando, pronto para adicionarmos funcionalidades.
Salvando uma nova URL
Edite o arquivo app/controllers/UrlController.php
:
1 |
/**
|
2 |
* Salva um novo recurso - nova url - na base de dados
|
3 |
*
|
4 |
* @return Response
|
5 |
*/
|
6 |
public function store() |
7 |
{
|
8 |
$url = new Url; |
9 |
$url->url = Request::get('url'); |
10 |
$url->description = Request::get('description'); |
11 |
$url->user_id = Auth::user()->id; |
12 |
|
13 |
// Filtragem e validação é extremamento necessário!!
|
14 |
// sério, sou um cara mal por deixá-las de fora.
|
15 |
|
16 |
$url->save(); |
17 |
|
18 |
return Response::json(array( |
19 |
'error' => false, |
20 |
'urls' => $urls->toArray()), |
21 |
200
|
22 |
);
|
23 |
}
|
É hora de testar, usando outra requisição curl. Dessa vez, enviaremos uma requisição do tipo POST, que corresponderá ao método store()
criado logo acima.
1 |
$ curl -i --user firstuser:first_password -d 'url=http://google.com&description=Um mecanismo de busca' localhost/l4api/public/index.php/api/v1/url |
2 |
HTTP/1.1 201 Created |
3 |
Date: Tue, 21 May 2013 19:10:52 GMT |
4 |
Content-Type: application/json |
5 |
|
6 |
{"error":false,"message":"URL created"} |
Ótimo! Vamos salvar algumas outras URL para os nossos dois usuários.
1 |
$ curl --user firstuser:first_password -d 'url=http://fideloper.com&description=Um otimo blog' localhost/l4api/public/index.php/api/v1/url |
2 |
|
3 |
$ curl --user seconduser:second_password -d 'url=http://digitalsurgeons.com&description=Agencia de Marketing' localhost/l4api/public/index.php/api/v1/url |
4 |
|
5 |
$ curl --user seconduser:second_password -d 'url=http://www.poppstrong.com/&description=Tenho pena dele' localhost/l4api/public/index.php/api/v1/url |
Agora, criaremos os métodos que retornarão as URLs salvas.
1 |
/**
|
2 |
* Mostra uma lista de recursos.
|
3 |
*
|
4 |
* @return Response
|
5 |
*/
|
6 |
public function index() |
7 |
{
|
8 |
// Antes: return 'Hello, API';
|
9 |
|
10 |
$urls = Url::where('user_id', Auth::user()->id)->get(); |
11 |
|
12 |
return Response::json(array( |
13 |
'error' => false, |
14 |
'urls' => $urls->toArray()), |
15 |
200
|
16 |
);
|
17 |
}
|
18 |
|
19 |
/**
|
20 |
* Mostra um recurso em específico
|
21 |
*
|
22 |
* @param int $id
|
23 |
* @return Response
|
24 |
*/
|
25 |
public function show($id) |
26 |
{
|
27 |
// Garanta que o usuário atual é dono do recurso requisitado
|
28 |
$url = Url::where('user_id', Auth::user()->id) |
29 |
->where('id', $id) |
30 |
->take(1) |
31 |
->get(); |
32 |
|
33 |
return Response::json(array( |
34 |
'error' => false, |
35 |
'urls' => $url->toArray()), |
36 |
200
|
37 |
);
|
38 |
}
|
Vamos testá-los:
1 |
$ curl --user firstuser:first_password localhost/l4api/public/index.php/api/v1/url |
2 |
{
|
3 |
"error": false, |
4 |
"urls": [ |
5 |
{
|
6 |
"created_at": "2013-02-01 02:39:10", |
7 |
"description": "Um mecanismo de busca", |
8 |
"id": "2", |
9 |
"updated_at": "2013-02-01 02:39:10", |
10 |
"url": "http://google.com", |
11 |
"user_id": "1" |
12 |
},
|
13 |
{
|
14 |
"created_at": "2013-02-01 02:44:34", |
15 |
"description": "Um otimo blog", |
16 |
"id": "3", |
17 |
"updated_at": "2013-02-01 02:44:34", |
18 |
"url": "http://fideloper.com", |
19 |
"user_id": "1" |
20 |
}
|
21 |
]
|
22 |
}
|
23 |
|
24 |
$ curl --user firstuser:first_password localhost/l4api/public/index.php/api/v1/url/1 |
25 |
{
|
26 |
"error": false, |
27 |
"urls": [ |
28 |
{
|
29 |
"created_at": "2013-02-01 02:39:10", |
30 |
"description": "Um mecanismo de busca", |
31 |
"id": "2", |
32 |
"updated_at": "2013-02-01 02:39:10", |
33 |
"url": "http://google.com", |
34 |
"user_id": "1" |
35 |
}
|
36 |
]
|
37 |
}
|
Quase pronto. Agora, precisamos permitir os usuários apagar uma das URLs salvas.
1 |
/**
|
2 |
* Remove o recurso requisitado da base de dados.
|
3 |
*
|
4 |
* @param int $id
|
5 |
* @return Response
|
6 |
*/
|
7 |
public function destroy($id) |
8 |
{
|
9 |
$url = Url::where('user_id', Auth::user()->id)->find($id); |
10 |
|
11 |
$url->delete(); |
12 |
|
13 |
return Response::json(array( |
14 |
'error' => false, |
15 |
'message' => 'url apagada'), |
16 |
200
|
17 |
);
|
18 |
}
|
Agora podemos apagar uma URL ao fazer uma requisição do tipo DELETE:
1 |
$ curl -i -X DELETE --user firstuser:first_password localhost/l4api/public/index.php/api/v1/url/1 |
2 |
HTTP/1.1 200 OK |
3 |
Date: Tue, 21 May 2013 19:24:19 GMT |
4 |
Content-Type: application/json |
5 |
|
6 |
{"error":false,"message":"url apagada"} |
Por último, temos de permitir que os usuários atualizem as URLs.
1 |
/**
|
2 |
* Atualiza um recurso especificado na base de dados.
|
3 |
*
|
4 |
* @param int $id
|
5 |
* @return Response
|
6 |
*/
|
7 |
public function update($id) |
8 |
{
|
9 |
$url = Url::where('user_id', Auth::user()->id)->find($id); |
10 |
|
11 |
if ( Request::get('url') ) |
12 |
{
|
13 |
$url->url = Request::get('url'); |
14 |
}
|
15 |
|
16 |
if ( Request::get('description') ) |
17 |
{
|
18 |
$url->description = Request::get('description'); |
19 |
}
|
20 |
|
21 |
$url->save(); |
22 |
|
23 |
return Response::json(array( |
24 |
'error' => false, |
25 |
'message' => 'url atualizada'), |
26 |
200
|
27 |
);
|
28 |
}
|
Para testar se conseguimos atualizar as URL, execute:
1 |
$ curl -i -X PUT --user seconduser:second_password -d 'url=http://yahoo.com' localhost/l4api/public/index.php/api/v1/url/4 |
2 |
HTTP/1.1 200 OK |
3 |
Date: Tue, 21 May 2013 19:34:21 GMT |
4 |
Content-Type: application/json |
5 |
|
6 |
{"error":false,"message":"url atualizada"} |
7 |
|
8 |
// View our changes |
9 |
$ curl --user seconduser:second_password localhost/l4api/public/index.php/api/v1/url/4 |
10 |
{
|
11 |
"error": false, |
12 |
"urls": [ |
13 |
{
|
14 |
"created_at": "2013-02-01 02:44:34", |
15 |
"description": "Tenho pena dele", |
16 |
"id": "3", |
17 |
"updated_at": "2013-02-02 18:44:18", |
18 |
"url": "http://yahoo.com", |
19 |
"user_id": "1" |
20 |
}
|
21 |
]
|
22 |
}
|
Isso é tudo!
Agora, temos o começo de uma API totalmente funcional. Espero que você tenha aprendido bastante sobre o funcionamento e criação de uma API com o Laravel 4.
Para recapitular, nós fomos capazes de realizar as seguintes tarefas, seguinda esse tutorial:
- Instalar o Laravel
- Criar uma base de dados, usando migrações (migrations) e semeadores (seeders)
- Usar modelos baseados no ORM Eloquent
- Autenticar usuários com Autenticação HTTP básica
- Criar rotas, incluindo versionamento da API
- Criar as funcionalidades da API usando Controladores Inteligentes (Resourceful Controllers)
Os próximos passos
Se você quer ter uma API profissional, talvez queira dar uma olhada nas indicações a seguir.
- Validação (dica: Laravel tem uma biblioteca de Validação).
- Tratamento de erros em requisições a API – É possível receber respostas HTML em requisições a APIs (Dica: Tratamento de Error no Laravel e Negociação de Conteúdo)
- Negociação de Conteúdo - ouvindo ao cabeçalho HTTP Accept. (Dica: A classe Request do Laravel possibilita acesso aos cabeçalhos da requisição).
- Dê uma olhada no grupo API Craft Google Group
- Aprenda sobre os diferentes tipos de caching e como o caching de validação pode aprimorar sua API.
- Realize testes unitários para sua App
- Confira a ótima seleção da Apigee sobre APIs
Seja o primeiro a saber sobre novas traduções–siga @tutsplus_pt no Twitter!