JSON API Resoucers: Padronizando API no Rails

Aaron Pedro
Jaguaribe Tech
Published in
5 min readAug 15, 2019

Atualmente contamos com diversos padrões de comunicação para aplicações distribuídas, como REST e GraphQL. JSON API, consiste numa especificação que procura padronizar a forma como o cliente requisita, adiciona e modifica recursos e como o servidor deve construir a resposta. O principal objetivo da especificação é prover os dados necessários com o menor número de requisições possível, definindo convenções de nomeação, negociação entre pares, e etc.

Uma outra vantagem ao adotar a especificação, é o fato de que diferentes clientes da sua aplicação tem diferentes necessidades e métodos de realizar requisições. Um exemplo recorrente é quando uma aplicação tem diferentes clientes web e mobiles consumindo uma api e a forma de manipulação dos recursos é discrepante entre estes clientes. Assim, ao adotar a especificação é possível padronizar as requisições de forma que o cliente tenha a liberdade de requisitar os campos específicos do recurso.

JSON-API Resoucers

JSON-API Resoucers consiste em uma gem que implementa a específicação JSON-API em aplicações Rails com o mínimo de configuração.

Demonstração

Criaremos uma Aplicação simples, chamada Jagua, para demonstrar como a GEM funciona. Jagua consiste em um gerenciador de contatos implementado por uma API que aplica a especificação JSON API.

Criando a aplicação:

rails new Jagua — skip-javascript
rake db:create
gem ‘jsonapi-resources’
bundle

Aqui estamos criando a aplicação, em seguida criando o Banco de dados, instalando a gem e instalando as dependências com o Bundle. Esses são os passos mínimos necessários para iniciar o desenvolvimento de uma aplicação Rails segundo a especificação JSON-API.

Em seguida, faça as seguintes alterações em application_controller.rb:

class ApplicationController < ActionController::Base
include JSONAPI::ActsAsResourceController

# Prevent CSRF attacks by raising an exception.
# For APIs, you may want to use :null_session instead.
protect_from_forgery with: :null_session
end

Criando Models

Use o rails generator para criar um model para contatos e um para números de telefone:

rails g model Contact name_first:string name_last:string email:string twitter:string

Edite o model criado, adicionando o relacionamento com phone_numbers.

class Contact < ActiveRecord::Base
has_many :phone_numbers
### Validations
validates :name_first, presence: true
validates :name_last, presence: true
end

Crie o model PhoneNumber:

rails g model PhoneNumber contact_id:integer name:string phone_number:string

Adicione o relacionamento:

class PhoneNumber < ActiveRecord::Base
belongs_to :contact
end

Por fim, executa as migrations.

rake db:migrate

Criando Controllers

Use the rails generator to create empty controllers. These will be inherit methods from the ResourceController so they will know how to respond to the standard REST methods.

Use o Rails Generator para criar controllers vazios. Eles herdarão métodos do ResoucerController então saberão como responder à métodos REST padrão.

rails g controller Contacts — skip-assets
rails g controller PhoneNumbers — skip-assets

Precisamos de um diretório para agrupar os recursos. Então será criada uma pasta resouces abaixo do diretório app.

mkdir app/resources

Criando os recursos

Crie um novo arquivo para cada recurso, eles devem ser nomeados de forma padronizada, seu nome deve ser o singular em snake case do nome do model com sufixo _resoucer.rb. Para Conctacts será contact_resoucer.rb. Assim, será criado dois arquivos resouce:

contact_resource.rb:

class ContactResource < JSONAPI::Resource
attributes :name_first, :name_last, :email, :twitter
has_many :phone_numbers
end

phone_number_resource.rb:

class PhoneNumberResource < JSONAPI::Resource
attributes :name, :phone_number
has_one :contact
filter :contact
end

Agora, só precisamos adicionar as rotas para os novos recursos:

jsonapi_resources :contacts
jsonapi_resources :phone_numbers

E vamos testar!

Mas já?

Pelo terminal, execute a aplicação:

rails server

Com o comando curl, poderemos requisitar nossa api, assim, pelo terminal poderemos criar um novo contato, chamado John:

curl -i -H “Accept: application/vnd.api+json” -H ‘Content-Type:application/vnd.api+json’ -X POST -d ‘{“data”: {“type”:”contacts”, “attributes”:{“name-first”:”John”, “name-last”:”Doe”, “email”:”john.doe@boring.test”}}}’ http://localhost:3000/contacts

Então é recebido a seguinte resposta em seguida:

HTTP/1.1 201 Created
X-Frame-Options: SAMEORIGIN
X-Xss-Protection: 1; mode=block
X-Content-Type-Options: nosniff
Content-Type: application/vnd.api+json
Etag: W/”809b88231e24ed1f901240f47278700d”
Cache-Control: max-age=0, private, must-revalidate
X-Request-Id: e4a991a3–555b-42ac-af1e-f103a1007edc
X-Runtime: 0.151446
Server: WEBrick/1.3.1 (Ruby/2.2.2/2015–04–13)
Date: Thu, 18 Jun 2015 18:21:21 GMT
Content-Length: 363
Connection: Keep-Alive
{“data”:{“id”:”1",”type”:”contacts”,”links”:{“self”:”http://localhost:3000/contacts/1"},"attributes":{"name-first":"John","name-last":"Doe","email":"john.doe@boring.test","twitter":null},"relationships":{"phone-numbers":{"links":{"self":"http://localhost:3000/contacts/1/relationships/phone-numbers","related":"http://localhost:3000/contacts/1/phone-numbers"}}}}}

Agora é possível adicionar um numero de telefone para o John.

curl -i -H “Accept: application/vnd.api+json” -H ‘Content-Type:application/vnd.api+json’ -X POST -d ‘{ “data”: { “type”: “phone-numbers”, “relationships”: { “contact”: { “data”: { “type”: “contacts”, “id”: “1” } } }, “attributes”: { “name”: “home”, “phone-number”: “(603) 555–1212” } } }’ http://localhost:3000/phone-numbers

E a resposta será semelhante à seguinte:

HTTP/1.1 201 Created
X-Frame-Options: SAMEORIGIN
X-Xss-Protection: 1; mode=block
X-Content-Type-Options: nosniff
Content-Type: application/vnd.api+json
Etag: W/”b8d0ce0fd869a38dfb812c5ac1afa94e”
Cache-Control: max-age=0, private, must-revalidate
X-Request-Id: 63920c97–247a-43e7–9fe3–87ede9e84bb5
X-Runtime: 0.018539
Server: WEBrick/1.3.1 (Ruby/2.2.2/2015–04–13)
Date: Thu, 18 Jun 2015 18:22:13 GMT
Content-Length: 363
Connection: Keep-Alive
{“data”:{“id”:”1",”type”:”phone-numbers”,”links”:{“self”:”http://localhost:3000/phone-numbers/1"},"attributes":{"name":"home","phone-number":"(603) 555–1212"},”relationships”:{“contact”:{“links”:{“self”:”http://localhost:3000/phone-numbers/1/relationships/contact","related":"http://localhost:3000/phone-numbers/1/contact"},"data":{"type":"contacts","id":"1"}}}}}

Agora você pode requisitar todos os contatos.

curl -i -H “Accept: application/vnd.api+json” “http://localhost:3000/contacts"

Resposta:

TTP/1.1 200 OK
X-Frame-Options: SAMEORIGIN
X-Xss-Protection: 1; mode=block
X-Content-Type-Options: nosniff
Content-Type: application/vnd.api+json
Etag: W/”512c3c875409b401c0446945bb40916f”
Cache-Control: max-age=0, private, must-revalidate
X-Request-Id: b324bff8–8196–4c43–80fd-b2fd1f41c565
X-Runtime: 0.004106
Server: WEBrick/1.3.1 (Ruby/2.2.2/2015–04–13)
Date: Thu, 18 Jun 2015 18:23:19 GMT
Content-Length: 365
Connection: Keep-Alive
{“data”:[{“id”:”1",”type”:”contacts”,”links”:{“self”:”http://localhost:3000/contacts/1"},"attributes":{"name-first":"John","name-last":"Doe","email":"john.doe@boring.test","twitter":null},"relationships":{"phone-numbers":{"links":{"self":"http://localhost:3000/contacts/1/relationships/phone-numbers","related":"http://localhost:3000/contacts/1/phone-numbers"}}}}]}

Perceba que o número de telefones estão inclusos nos links, porém não são passados seus detalhes. Porém, é possível inclui-los utilizando o include:

curl -i -H “Accept: application/vnd.api+json” “http://localhost:3000/contacts?include=phone-numbers"

E também definir campos para o contato:

curl -i -H “Accept: application/vnd.api+json” “http://localhost:3000/contacts?include=phone-numbers&fields%5Bcontacts%5D=name-first,name-last&fields%5Bphone-numbers%5D=name"
Isso tudo com quase 0 configuração.

Conclusões

A JSON API RESOURCES oferece um mecanismo eficiente para criação de APIs padronizadas. Foi possível perceber como é simples criar uma API padronizada e versátil a partir apenas de seus models e algumas convenções, o que faz do JSON API RESOUCERS uma opção rápida e simples quando se trata de padronização de APIs, em contrapartida do GraphQL, que necessita de uma configuração mais complexa por parte do servidor.

Referências:

--

--