RESTful API Design
RESTful API Design
Índice
Introdução...................................................................................................................2
Conceitos Gerais.........................................................................................................2
KISS – « Keep it simple, stupid »...........................................................................2
Exemplos de cURL.................................................................................................3
Granularidade.........................................................................................................4
Nomes de Domínio da API.....................................................................................5
Segurança...............................................................................................................7
URIs............................................................................................................................8
Nomes > verbos......................................................................................................8
Plural > singular......................................................................................................9
Consistência de caixa.............................................................................................9
Caixa das URIs...................................................................................................9
Caixa do “body”................................................................................................10
Versionamento......................................................................................................11
CRUD....................................................................................................................12
Respostas parciais...............................................................................................14
Query strings.............................................................................................................14
Paginação.............................................................................................................14
A paginação no request....................................................................................15
A paginação no response.................................................................................16
Links de navegação..........................................................................................16
Exemplos..........................................................................................................16
Filtros....................................................................................................................17
Ordenação............................................................................................................18
Ordenação, Filtro e Paginação.........................................................................18
Busca....................................................................................................................19
Busca nos recursos..........................................................................................19
Busca global.....................................................................................................19
Outros conceitos importantes...................................................................................20
Negociação de conteúdo......................................................................................20
Cross-domain........................................................................................................20
CORS...............................................................................................................20
Jsonp................................................................................................................21
HATEOAS.............................................................................................................21
Conceito............................................................................................................21
Implementação.................................................................................................22
Cenários “Sem Recursos”....................................................................................23
Erros..........................................................................................................................24
Estrutura do erro...................................................................................................24
Status Codes........................................................................................................25
SUCCESS........................................................................................................25
CLIENT ERROR...............................................................................................25
SERVER ERROR.............................................................................................27
Fontes.......................................................................................................................28
INTRODUÇÃO
Logo que se começa a desenvolver uma API, surge a questão: “como se
projeta uma API?”. Queremos fazer APIs robustas e bem projetadas. Sabemos que
APIs mal projetadas dificultam o uso, ou mesmo são rejeitadas pelos seus clientes
exigentes: os desenvolvedores de aplicativos.
Para que a sua API seja uma obra prima, é preciso levar em conta:
• Os princípios de APIs RESTful descritos na literatura (Roy Fielding, Leonard
Richardson, Martin Fowler, HTTP specification, etc.)
• As práticas dos “Gigantes da Web“
Geralmente encontramos duas posições opostas: os “puristas“, que insistem
em seguir os princípios REST sem concessões, e os “pragmáticos“, que preferem
uma abordagem mais prática, para dar aos clientes uma API mais fácil de usar. O
melhor caminho é o do meio.
Projetar uma API REST gera questões e problemas para os quais não há
unanimidade. As boas práticas REST ainda estão sendo debatidas e consolidadas,
o que torna esse trabalho mais interessante.
Para facilitar e acelerar o projeto e desenvolvimento das suas APIs, nós
compartilhamos nossa visão e nossa experiência em projetos de APIs.
AVISO: Esse artigo é um apanhado de boas práticas com o objetivo de serem
discutidas. Você está convidado a discutir e questionar no nosso blog.
CONCEITOS GERAIS
A API é projetada para seus clientes, os desenvolvedores, e não deve ser um apenas
um acesso ao modelo de dados. A API deve prover funcionalidades simples que casam
com as necessidades dos desenvolvedores. Um erro comum é projetar a API baseada no
modelo de dados, quase sempre complexo.
Durante a fase de concepção, é melhor focar nos principais casos de uso, e deixar os
casos excepcionais para depois.
EXEMPLOS DE CURL
Exemplos de cURL são muito usados para ilustrar chamadas à API: os
Gigantes da Web fazem isso, assim como a literatura técnica em geral:
• https://developer.github.com/v3/
• https://developers.google.com/youtube/v3/live/authentication#client-side-
apps
• https://developer.paypal.com/docs/api/
• https://developers.facebook.com/docs/graph-api/making-multiple-requests
• https://www.dropbox.com/developers/blog/45/using-oauth-20-with-the-core-
api
• http://instagram.com/developer/endpoints/likes/
• http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/AESDG-chapter-
instancedata.html
• etc.
Nós recomendamos que você sempre ilustre a sua documentação de API com
exemplos cURL, que permitem ao desenvolvedor copiar e colar, e adaptar para
acelerar sua utilização.
Exemplo
CURL –X POST \
-H "Accept: application/json" \
-d '{"state":"running"}' \
https://api.fakecompany.com/v1/clients/007/orders
GRANULARIDADE
Apesar da regra “um recurso = uma URL”, consideramos importante manter o
número de recursos (e de URLs) num limite razoável.
Por exemplo: uma pessoa tem um endereço que contém um país.
É importante evitar de fazer 3 chamadas à API:
CURL https://api.fakecompany.com/v1/users/1234
< 200 OK
< {"id":"1234", "name":"Antoine Jaby",
"address":"https://api.fakecompany.com/v1/addresses/4567"}CURL
https://api.fakecompany.com/addresses/4567
< 200 OK
< {"id":"4567", "street":"sunset bd", "country": "http://api.fakecompany.com/v1/
countries/98"}CURL https://api.fakecompany.com/v1/countries/98
< 200 OK
< {"id":"98", "name":"France"}
URIS
Um dos pontos principais de uma API REST é usar HTTP como protocolo da
aplicação. Ele dá consistência, e falicita a interação entre os sistemas de
informação. Ele também nos salva de tentar inventar a roda com protocolos
caseiros “tipo” SOAP/RPC/EJB.
Então devemos sempre usar os verbos HTTP para descrever todas as
operações feitas sobre os recursos (veja tópico CRUD)
O uso dos verbos HTTP também torna a API mais intuitiva, e faz com que os
desenvolvedores entendam como manipular os recursos sem precisar olhar uma
longa documentação, aumentando o “affordance” da sua API.
Na prática, o desenvolvedor vai usar ferramentas que vão gerar requisições
HTTP, com os verbos e payloads corretos, a partir de um modelo de dados, desde
que esse esteja atualizado.
CONSISTÊNCIA DE CAIXA
Para URIs, nós recomendamos que seja utilizada, de forma consistente, uma
das duas formas a seguir:
• spinal-case (destacada na RFC3986)
• e snake_case (mais usada pelos Gigantes da Web)
Exemplos
POST /v1/specific-orders
ou
POST /v1/specific_orders
CAIXA DO “BODY”
Existem dois formatos principais para o corpo (body) dos dados.
Por um lado, o snake_case é muito mais usado pelos Gigantes da Web, e,
principalmente, foi adotado na especificação OAuth2. Por outro lado, a
popularidade crescente do JavaScript contribuiu muito para a adoção do
CamelCase, mesmo que, teoricamente, o REST deveria promover a independência
de linguagens e expor uma API de última geração sobre XML.
Nós recomendamos usar a caixa de forma consistente para o body, a escolher
entre:
• snake_case (mais usada pela comunidade Ruby)
• lowerCamelCase (mais usada pelas comunidades Java e Javascript)
Exemplos
GET /orders?id_client=007 ou GET /orders?idClient=007
POST /orders {"id_client":"007"} ou POST /orders {"idClient":"007”}
VERSIONAMENTO
Qualquer API precisa evoluir com o tempo. Existem várias maneiras de
versionar uma API:
• Por um timestamp, pelo número do release
• No path, no início ou no fim da URI
• Como um parâmetro do request
• Num Header HTTP
• Com um versioning obrigatório ou opcional
Exemplo
GET /v1/orders
CRUD
Como falamos antes, um dos
objetivos da abordagem REST é usar
o HTTP como protocolo de aplicação,
e com isso evitar a criação de
protocolos caseiros.
Então devemos usar de forma
sistemática os verbos HTTP para
descrever quais ações serão feitas
nos recursos, e facilitar o trabalho
dos desenvolvedores nas tarefas
CRUD corriqueiras. A tabela a seguir
sintetiza as práticas mais comuns:
Verbo
Ação CRUD Coleção: /orders Instância: /orders/{id}
HTTP
Lê a lista de orders. 200
GET READ Lê os detalhes de uma order. 200 OK.
OK.
Cria uma nova order.
POST CREATE –
201 Criada.
UPDATE/ Full Update. 200 OK. Cria uma order
PUT –
CREATE específica. 201 Criada.
PATCH UPDATE – Update Parcial. 200 OK.
DELETE DELETE – Deleta a order. 200 OK.
O verbo HTTP POST é usado para criar uma instância dentro de uma coleção.
O id do recurso a ser criado não precisa ser fornecido.
CURL –X POST \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-d '{"state":"running","id_client":"007"}' \
https://api.fakecompany.com/v1/clients/007/orders
< 201 Created
< Location: https://api.fakecompany.com/orders/1234
O código de retorno é 201 ao invés de 200.
A URI e o id são retornados no header “Location” da resposta.
Se o id for especificado pelo cliente, então o verto HTTP PUT é usado para a
criação da instância na coleção. No entanto essa prática é menos comum.
CURL –X PUT \
-H "Content-Type: application/json" \
-d '{"state":"running","id_client":"007"}' \
https://api.fakecompany.com/v1/clients/007/orders/1234
< 201 Created
O verbo HTTP PATCH (que não existia na especificação original HTTP, sendo
adicionado mais tarde) é usado para fazer um update parcial de uma instância em
uma coleção.
No exemplo abaixo, nós atualizamos o atributos state, mas os demais
atributos continuarão como antes.
CURL –X PATCH \
-H "Content-Type: application/json" \
-d '{"state":"paid"}' \
https://api.fakecompany.com/v1/clients/007/orders/1234
< 200 OK
O verbo HTTP GET é usado para ler a coleção. Na prática, a API geralmente
não retorna todos os itens da coleção (veja paginação).
CURL –X GET \
-H "Accept: application/json" \
https://api.fakecompany.com/v1/clients/007/orders
< 200 OK
< [{"id":"1234", "state":"paid"}, {"id":"5678", "state":"running"}]
O verbo HTTP GET é usado também para ler uma instância em uma coleção.
CURL –X GET \
-H "Accept: application/json" \
https://api.fakecompany.com/v1/clients/007/orders/1234
< 200 OK
< {"id":"1234", "state":"paid"}
RESPOSTAS PARCIAIS
As respostas parciais permitem que o cliente recupere apenas as informações
que ele precisa. Isso se torna vital em contextos de apps móveis (3G) onde o uso
de banda deve ser otimizado.
QUERY STRINGS
PAGINAÇÃO
É necessário planejar com antecedência a paginação dos seus recursos,
desde o início do seu projeto de API. É difícil prever com precisão a evolução da
quantidade de dados a serem retornados. Por isso recomendamos o uso da
paginação default dos seus recursos. Se o seu cliente não especificar a paginação
na chamada, use uma faixa de valores default de, por exemplo, [0-25].
A paginação sistemática também dá consistência aos seus recursos, o que é
muito bom. Tenha em mente o princípio de affordance: quanto menos
documentação para ler, mais fácil a adoção da sua API.
Nos Gigantes da Web:
API Paginação
Parâmetros: before, after, limit, next, previous
"paging": {
"cursors": {
"after": "MTAxNTExOTQ1MjAwNzI5NDE=",
Faceboo "before": "NDMyNzQyODI3OTQw"
},
k "previous": "https://graph.facebook.com/me/albums?
limit=25&before=NDMyNzQyODI3OTQw"
"next": "https://graph.facebook.com/me/albums?
limit=25&after=MTAxNTExOTQ1MjAwNzI5NDE="
}
A PAGINAÇÃO NO REQUEST
Do ponto de vista prático, a paginação é geralmente feita via query-string na
URL. Mas o header HTTP também tem esse mecanismo. Nossa proposta é de
paginar somente via query-string, e de não considerar o HTTP Header Range. A
paginação é uma peça muito importante, e faz sentido que ela apareça na URL,
para clareza e simplicidade (e affordance).
Nós propomos a utilização de uma faixa de valores, sobre o índice da sua
coleção. Por exemplo, recursos do índice 10 até o 25 inclusive, equivalem a: ?
range=10-25.
A PAGINAÇÃO NO RESPONSE
O código de retorno HTTP de um request paginado será 206 Partial Content,
exceto de os valores requisitados resultarem no retorno da coleção completa, que
nesse caso geraria o código de retorno 200 OK.
A resposta da sua API para uma coleção deve obrigatoriamente conter nos
Headers HTTP:
•Content-Range offset – limit / count
•offset: o índice do primeiro elemento retornado
•limit: o índice do último elemento retornado
•count: o número total de elementos que a coleção contém
•Accept-Range resource max
•resource: o tipo da paginação. Deve lembrar o recurso em uso, ex.: client,
order, restaurant, etc.
•max : o número máximo de recursos que podem ser retornado em cada
requisição.
Caso a paginação requisitada não se encaixe nos valores permitidos pela API,
a resposta HTTP deve ser um error code 400, com a descrição explícita do erro no
body.
LINKS DE NAVEGAÇÃO
É fortemente recomendado incluir a tag Link no header HTTP das suas
respostas. Ela permite adicionar, entre outras, os links de navegação, como
próxima página, página anterior, primeira e última páginas, etc.
EXEMPLOS
Nós temos na nossa API uma coleção de 48 restaurantes, para os quais não é
permitido consultar mais que 50 por requisição. A paginação default é 0-50:
CURL –X GET \
-H "Accept: application/json" \
https://api.fakecompany.com/v1/restaurants
< 200 Ok
< Content-Range: 0-47/48
< Accept-Range: restaurant 50
< [...]
Nós recomendamos usar a seguinte notação para retornar os links para outras
faixas. Ela é usada pelo GitHub, e é compatível com a RFC5988. Ela também
permite gerenciar clientes que não suportam vários Link Headers.
CURL –X GET \
-H "Accept: application/json" \
https://api.fakecompany.com/v1/orders?range=48-55
< 206 Partial Content
< Content-Range: 48-55/971
< Accept-Range: order 10
< Link : <https://api.fakecompany.com/v1/orders?range=0-7>; rel="first",
<https://api.fakecompany.com/v1/orders?range=40-47>; rel="prev",
<https://api.fakecompany.com/v1/orders?range=56-64>; rel="next",
<https://api.fakecompany.com/v1/orders?range=968-975>; rel="last"
FILTROS
Filtrar consiste em limitar o número de recursos requisitados, especificando
alguns atributos e seus valores esperados. É possível filtrar uma coleção por vários
atributos ao mesmo tempo, e permitir filtrar vários valores para um atributo.
Por isso nós propomos usar diretamente o nome do atributo com um sinal de
igual e os valores esperados, separados por vírgula.
Exemplo: recuperar os restaurantes tailandeses (thai)
CURL –X GET \
-H "Accept: application/json" \
https://api.fakecompany.com/v1/restaurants?type=thai
ORDENAÇÃO
Ordenar os resultados de uma query numa coleção de recursos requer 2
parâmetros:
• sort: contém os nomes dos atributos que serão usados para ordenar a lista,
separados por vírgula.
• desc: por default a ordem é ascendente (ou crescente). Para uma
ordenação descendente (ou decrescente), é necessário adicionar esse
parâmetro (sem nenhum valor). Em alguns casos específicos pode-se
querer especificar quais atributos serão ascendentes e quais serão
descendentes. Nesse caso, o parâmetro desc deve conter a lista dos
atributos que serão descendentes, ficando os demais ascendentes por
default.
Exemplo: recuperar a lista de restaurantes ordenados alfabeticamente pelo
nome.
CURL –X GET \
-H "Accept: application/json" \
https://api.fakecompany.com/v1/restaurants?sort=name
BUSCA
BUSCA GLOBAL
A busca global deve ter o mesmo comportamento de uma busca específica em
um recurso, exceto que ela é localizada na raíz da API. com isso ela precisa ser
bem detalhada na documentação da API.
Nós recomendamos a notação da Google para buscas globais:
CURL –X GET \
-H "Accept: application/json" \
https://api.fakecompany.com/v1/search?q=running+paid
< [...]
NEGOCIAÇÃO DE CONTEÚDO
Nós recomendamos que sejam gerados diversos formatos de distribuição do
conteúdo na sua API. Podemos usar o “Accept” HTTP Header, que existe para
essa finalidade.
Por default, a API vai retornar os recursos no formato JSON, mas se o request
começar por “Accept: application/xml”, os recursos deverão ser enviados no
formato XML.
É recomendável suportar no mínimo 2 formatos: JSON e XML. A ordem dos
formatos enviada no header “accept” deve ser considerada para definir o formato
da resposta.
Nos casos onde não é possível fornecer o formato requerido, um erro HTTP
406 deve ser enviado (ver Erros – Status Codes)
GET https://api.fakecompany.com/v1/offers
Accept: application/xml; application/json XML préféré à JSON
< 200 OK
< [XML]GET https://api.fakecompany.com/v1/offers
Accept: text/plain; application/json The API cannot provide text
< 200 OK
< [JSON]
CROSS-DOMAIN
CORS
Quando a aplicação (JavaScript SPA) e a API estão hospedadas em domínios
diferentes, por exemplo:
• https://fakeapp.com
• https://api.fakecompany.com
Uma boa prática é usar o protocolo CORS, que é padrão no HTTP.
No servidor, a implementação do CORS geralmente consiste em adicionar
alguns parâmetros de configuração no servidor HTTP (Nginx/Apache/NodeJs, etc.).
No lado do cliente, a implementação é transparente: o browser vai enviar um
request HTTP com o verbo OPTIONS, antes de cada request
GET/POST/PUT/PATCH/DELETE.
Aqui, por exemplo, duas chamadas sucessivas são feitas num browser para
recuperar, via GET, informações de um usuário na API do Google+:
CURL -X OPTIONS \
-H 'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8' \
'https://www.googleapis.com/plus/v1/people/105883339188350220174?
client_id=API_KEY'
CURL -X GET\
'https://www.googleapis.com/plus/v1/people/105883339188350220174?
client_id=API_KEY' \
-H 'Accept: application/json, text/JavaScript, */*; q=0.01'\
-H 'Authorization: Bearer foo_access_token'
JSONP
Na prática, o CORS é mal suportado ou mesmo não suportado pelos browsers
antigos, especialmente IE7, 8 e 9. Se você não tem controle sobre os browsers que
usam a sua API (pela Internet, por clientes finais), é necessário expor um Jsonp da
sua API como contingência para a implementação do CORS.
Na verdade Jsonp é uma solução de contorno ao uso da tag <script/> para
permitir o gerenciamento cross-domain, e possui algumas limitações:
• Não é possível fazer a negociação de conteúdo através do Accept Header
=> um novo endpoint tem que ser publicado, por exemplo com
extensão .jasonp, para permitir que o controller possa determinar que é se
trata de uma requisição jsonp.
• Todos os requests são enviados com o verbo HTTP GET => deve ser usado
um parâmetro method=XXX
◦ Tenha em mente que um web crawler pode causar sérios danos aos
seus dados se não houver mecanismo de autorização para a chamada
do method=DELETE…
• O payload da requisição não pode conter dados => todos os dados
precisam ser enviados como parâmetros do request
Para ser compatível com CORS & Jsonp, por exemplo, sua API deve expor os
seguintes endpoints:
POST /orders and /orders.jsonp?method=POST&callback=foo
GET /orders and /orders.jsonp?callback=foo
GET /orders/1234 and /orders/1234.jsonp?callback=foo
PUT /orders/1234 and /orders/1234.jsonp?method=PUT&callback=foo
HATEOAS
CONCEITO
IMPLEMENTAÇÃO
O HATEOAS é como uma lenda urbana: todos falam nisso, mas ninguém
nunca viu uma implementação de verdade.
O Paypal propõe a seguinte implementação:
[
{
"href": "https://api.sandbox.paypal.com/v1/payments/payment/PAY-
6RV70583SB702805EKEYSZ6Y",
"rel": "self",
"method": "GET"
},
{
"href": "https://www.sandbox.paypal.com/webscr?cmd=_express-checkout&token=EC-
60U79048BN7719609",
"rel": "approval_url",
"method": "REDIRECT"
},
{
"href": "https://api.sandbox.paypal.com/v1/payments/payment/PAY-
6RV70583SB702805EKEYSZ6Y/execute",
"rel": "execute",
"method": "POST"
}
]
Uma chamada para /customers/007 retornaria os detalhes do customer, além
dos links para os recursos relacionados:
GET /clients/007
< 200 Ok
< { "id":"007", "firstname":"James",...,
"links": [
{"rel":"self","href":"https://api.domain.com/v1/clients/007", "method":"GET"},
{"rel":"addresses","href":"https://api.domain.com/v1/addresses/42",
"method":"GET"},
{"rel":"orders", "href":"https://api.domain.com/v1/orders/1234",
"method":"GET"},
...
]
}
Ou ainda:
POST /convert?from=EURato=USD&amount=42
< 200 OK
< {"result" : "54"}
ERROS
ESTRUTURA DO ERRO
Nós recomendamos seguir a seguinte estrutura JSON:
{
"error": "descrição_curta",
"error_description": "descrição longa, legível por humanos",
"error_uri": "URI que leva a uma descrição detalhada do erro no portal do
desenvolvedor"
}
STATUS CODES
Nós recomendamos fortemente usar os códigos de retorno HTTP, já que esses
códigos cobrem todos os casos comuns, e todos os desenvolvedores entendem. É
claro que não é necessário usar toda a coleção de códigos. Normalmente os 10
códigos mais usados são suficientes.
SUCCESS
200 OK é o código de sucesso clássico, e funciona para a maioria dos casos.
É especialmente usado quando o primeiro request GET para um recurso tem
sucesso.
CLIENT ERROR
401 Unauthorized
Eu não conheço o seu id. Diga-me quem você é, e eu vejo sua autorização.
GET /users/42/orders
< 401 Unauthorized
< {"error": "no_credentials", "error_description": "This resource is under
permission, you must be authenticated with the right rights to have access to
it" }
403 Forbidden
Você foi autenticado corretamente, mas não tem privilégios suficientes.
GET /users/42/orders
< 403 Forbidden
< {"error": "not_allowed", "error_description": "You're not allowed to perform
this request"}
HTTP
Description
Status
A requisição está correta, mas ocorreu um problema de execução. O cliente não
tem muito o que fazer a respeito disso. Nós recomendamos sistematicamente
retornar Status 500 nesses casos.
500
Server GET /users
< 500 Internal server error
error < Content-Type: application/json
< {"error":”server_error", "error_description":"Oops! Something went
wrong..."}
FONTES
Design Beautiful REST + JSON APIs
http://www.slideshare.net/stormpath/rest-jsonapis
REST World
http://nodejsparis.bitbucket.org/20140312/rest_world/#/