Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Building Better Web APIs with Rails

Building Better Web APIs with Rails

Presented at Ancient City Ruby 2015

Carlos Souza

March 27, 2015
Tweet

More Decks by Carlos Souza

Other Decks in Technology

Transcript

  1. “We’ve detected the majority of our visitors come from mobile

    devices. 
 
 We need to launch a native app
 as soon as possible!”
  2. resources :projects GET
 POST
 PATCH (PUT) 
 DELETE ... Projects#index

    Projects#create Projects#show Projects#update Projects#destroy ... paths methods actions /projects /projects/:id
  3. 
 get 'active' get 'suspended' post 'activate' post 'suspend' resources

    :projects end end end do member do post 'archive' 
 collection do get 'archived' post 'create_review'
  4. 
 get 'active' get 'suspended' post 'activate' post 'suspend' resources

    :projects end end end do member do post 'archive' 
 collection do get 'archived', to: 'archived_projects#index' , to: 'active_projects#index' , to: 'suspended_projects#index' , to: 'archived_projects#create' , to: 'active_projects#create' , to: 'suspended_projects#create' post 'create_review'
  5. 
 get 'active' get 'suspended' post 'activate' post 'suspend' resources

    :projects end end end do member do post 'archive' 
 collection do get 'archived' resources :reviews, only: :create , to: 'archived_projects#index' , to: 'active_projects#index' , to: 'suspended_projects#index' , to: 'archived_projects#create' , to: 'active_projects#create' , to: 'suspended_projects#create'
  6. Client A API I’m a Rich Java$cript Application 
 and

    I want JSON! Hey, I’m an Enterprise Java Application and I want XML! (Ha Ha, Business!) ¯\_(ツ)_/¯ Oi, soy un browser e quiero HTML! response in JSON respuesta en HTML response in XML Client B Client C content negotiation The process in which client and server determine the best representation for a response
 when many are available.
  7. class ProjectSerializer < ActiveModel::Serializer attributes :id, :title, :amount embed :ids,

    include: true has_many :products end https://github.com/rails-api/active_model_serializers ActiveModel::Serializers
  8. class ProjectSerializer < ActiveModel::Serializer attributes :id, :title, :amount embed :ids,

    include: true has_many :products end https://github.com/rails-api/active_model_serializers ActiveModel::Serializers defaults to JSON-API
  9. module SongsRepresenter include Roar::JSON::JsonApi name :songs property :id property :title

    end class SongRepresenter < Roar::Decorator include Roar::JSON::JsonApi name :songs property :id property :title end https://github.com/apotonick/roar using Mixins using Decorators Roar
  10. module API class ReportsController < ApplicationController def create head 200

    end end end A time-consuming request... takes ~20 seconds Report.generate
  11. module API class ReportsController < ApplicationController def create head 200

    end end end Creating the job ReportJob.perform_later
  12. module API class ReportsController < ApplicationController def create ReportJob.perform_later head

    202 end end end 202 accepted for processing, but processing has not been completed.
  13. • Can easily expire or regenerate tokens. • Any vulnerability

    is limited to API access. • Multiple tokens for each user. • Different access rules can be implemented. API Projects Forum Admin Client A token based auth
  14. token based auth module API class ProjectsController < ApplicationController before_action

    :authenticate_or_request protected def authenticate_or_request authenticate_or_request_with_http_token do |token, opt| User.find_by(auth_token: token) end end end end
  15. end def generate_auth_token .gsub(/\-/,'') end omits the hyphens SecureRandom.uuid class

    User < ActiveRecord::Base before_create :set_auth_token private def set_auth_token return if auth_token.present?
 self.auth_token = generate_auth_token end a47a8e54b11c4de5a4a351734c80a14a 9fa8a147b10c4efca3e8592b3a1c2729 823c1c984d504f66a2e6cbb2eb69e842 ... GENERATING TOKENS
  16. $ curl -IH "Authorization: Token token=16d7d6089b8fe0c5e19bfe10bb156832" \ http://localhost:3000/episodes HTTP/1.1 200

    OK Content-Type: application/json; charset=utf-8 use the -H option token based auth WITH curl
  17. MAJOR
 incompatible changes 
 MINOR
 backwards-compatible changes 
 PATCH
 backwards-compatible

    bug fixes http://semver.org/ Semantic versioning Works great for software libraries
  18. V1 /V1 feature X, feature Y /V2 feature X, feature

    Y, feature Z Compatible changes: • addition of a new format (i.e. JSON, XML ) • addition of a new property on a resource • renaming of an end-point (use 3xx status code!) • Only use major version. • Changes cannot break existing clients. • No need to bump version on compatible changes. versioning services
  19. 410 The requested resource is no longer available 
 at

    the server and no forwarding address 
 is known.
  20. https://www.mnot.net/blog/2012/12/04/api-evolution “The biggest way to avoid new 
 major versions

    is to make as many 
 of your changes backwards-compatible 
 as possible”
  21. API HOW SHOULD WE TEST ? Requesting endpoints and verifying

    responses $ rails g integration_test <doing-something>
  22. testing status code require 'test_helper' class ListingProjectsTest < ActionDispatch::IntegrationTest setup

    { host! 'api.example.com' } test 'returns list of projects' do get '/projects' assert_equal 200, response.status refute_empty response.body end end
  23. testing status code require 'test_helper' class ListingProjectsTest < ActionDispatch::IntegrationTest setup

    { host! 'api.example.com' } test 'returns list of projects' do get '/projects' assert_equal 200, response.status refute_empty response.body end end
  24. testing mime types class ListingProjectsTest < ActionDispatch::IntegrationTest setup { host!

    'api.example.com' } 
 test 'returns projects in JSON' do get '/projects', {}, { 'Accept' => Mime::JSON } assert_equal Mime::JSON, response.content_type end test 'returns projects in XML' do get '/projects', {}, { 'Accept' => Mime::XML } assert_equal Mime::XML, response.content_type end end
  25. testing mime types class ListingProjectsTest < ActionDispatch::IntegrationTest setup { host!

    'api.example.com' } 
 test 'returns projects in JSON' do get '/projects', {}, { 'Accept' => Mime::JSON } assert_equal Mime::JSON, response.content_type end test 'returns projects in XML' do get '/projects', {}, { 'Accept' => Mime::XML } assert_equal Mime::XML, response.content_type end end
  26. class ListingProjectsTest < ActionDispatch::IntegrationTest setup { @user = User.create! }

    setup { host! 'api.example.com' } test 'valid authentication with token' do get '/projects', {}, { 'Authorization' => "Token token=#{@user.auth_token}"} assert_equal 200, response.status assert_equal Mime::JSON, response.content_type end test 'invalid authentication' do get '/projects', {}, { 'Authorization' => "Token token=#{@user.auth_token}fake" } assert_equal 401, response.status end end testing access rules
  27. class ListingProjectsTest < ActionDispatch::IntegrationTest setup { @user = User.create! }

    setup { host! 'api.example.com' } test 'valid authentication with token' do get '/projects', {}, { 'Authorization' => "Token token=#{@user.auth_token}"} assert_equal 200, response.status assert_equal Mime::JSON, response.content_type end test 'invalid authentication' do get '/projects', {}, { 'Authorization' => "Token token=#{@user.auth_token}fake" } assert_equal 401, response.status end end testing access rules
  28. BUILDINg BETTER WITH railS web APIS @caike Carlos Souza Code

    School Thank You! https://www.flickr.com/photos/glimeend/5387716362