Slide 1

Slide 1 text

BUILDINg BETTER WITH railS web APIS @caike Carlos Souza Code School

Slide 2

Slide 2 text

No content

Slide 3

Slide 3 text

No content

Slide 4

Slide 4 text

codeschool.com

Slide 5

Slide 5 text

No content

Slide 6

Slide 6 text

perfect rails web app Deadline Budget Features Tests

Slide 7

Slide 7 text

“We need a snappier experience”

Slide 8

Slide 8 text

“We’ve detected the majority of our visitors come from mobile devices. 
 
 We need to launch a native app
 as soon as possible!”

Slide 9

Slide 9 text

“Does our web app expose an API ?”

Slide 10

Slide 10 text

• Routes • CONNEG • AUTH • VERSION • Tests five things

Slide 11

Slide 11 text

endpoints, resources and url-to-controller mappings ROUTES

Slide 12

Slide 12 text

URLs are very important /projects /projects/archived /projects/:id/archive

Slide 13

Slide 13 text

resources :projects GET
 POST
 PATCH (PUT) 
 DELETE ... Projects#index Projects#create Projects#show Projects#update Projects#destroy ... paths methods actions /projects /projects/:id

Slide 14

Slide 14 text

resources :projects paths /projects/archived
 /projects/:id/archive
 /projects /projects/:id ?????

Slide 15

Slide 15 text

resources :projects end end end member do post 'archive' 
 collection do get 'archived' do

Slide 16

Slide 16 text


 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'

Slide 17

Slide 17 text


 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'

Slide 18

Slide 18 text


 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'

Slide 19

Slide 19 text

conneg content negotiation

Slide 20

Slide 20 text

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.

Slide 21

Slide 21 text

JBuilder json.content format_content(@message.content) json.(@message, :created_at, :updated_at) json.author do json.name @message.creator.name.familiar json.url url_for(@message.creator, format: :json) end https://github.com/rails/jbuilder

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

JSON-API A standard for building APIs in JSON. "it's your anti-bikeshedding weapon"

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

No content

Slide 27

Slide 27 text

創建資源

Slide 28

Slide 28 text

تسا هدش داجیا عبانم

Slide 29

Slide 29 text

Resource è stato creato

Slide 30

Slide 30 text

創建資源 تسا هدش داجیا عبانم Resource è stato creato

Slide 31

Slide 31 text

201request fulfilled and new resource created

Slide 32

Slide 32 text

A Status Code is worth a thousand words Author Unknown

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

class ReportJob < ActiveJob::Base queue_as :default def perform(*args) end end ActiveJob Report.generate

Slide 35

Slide 35 text

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

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

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.

Slide 38

Slide 38 text

AUTHentication preventing unauthorized access to protected resources

Slide 39

Slide 39 text

• 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

Slide 40

Slide 40 text

token for the Backpack API providing the token Typically available out-of-band on a user settings page

Slide 41

Slide 41 text

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

Slide 42

Slide 42 text

UUID - Universally Unique Identifier GENERATING TOKENS RFC 4122

Slide 43

Slide 43 text

We can use the Ruby standard library SecureRandom.uuid f4ea855f-d303-43e6-bee3-94581c0ecb21 90ab3255-ce33-4022-8349-b7979655b07c 371c760d-2539-41b9-b665-98c255d4c323 ... GENERATING TOKENS

Slide 44

Slide 44 text

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

Slide 45

Slide 45 text

$ 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

Slide 46

Slide 46 text

versioning resources Version

Slide 47

Slide 47 text

MAJOR
 incompatible changes 
 MINOR
 backwards-compatible changes 
 PATCH
 backwards-compatible bug fixes http://semver.org/ Semantic versioning Works great for software libraries

Slide 48

Slide 48 text

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

Slide 49

Slide 49 text

No content

Slide 50

Slide 50 text

410 The requested resource is no longer available 
 at the server and no forwarding address 
 is known.

Slide 51

Slide 51 text

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”

Slide 52

Slide 52 text

TESTS automated verification

Slide 53

Slide 53 text

API Unit Tests testing apis Not the time nor place to test business logic

Slide 54

Slide 54 text

API • Status Code • Mime Types • Authentication WHAT SHOULD WE TEST ?

Slide 55

Slide 55 text

API HOW SHOULD WE TEST ? Requesting endpoints and verifying responses $ rails g integration_test

Slide 56

Slide 56 text

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

Slide 57

Slide 57 text

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

Slide 58

Slide 58 text

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

Slide 59

Slide 59 text

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

Slide 60

Slide 60 text

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

Slide 61

Slide 61 text

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

Slide 62

Slide 62 text

http://railsapis.codeschool.com/

Slide 63

Slide 63 text

ruby5.codeschool.com

Slide 64

Slide 64 text

[email protected] Want to Podcast ? Get in touch! fivejs.codeschool.com iosbytes.codeschool.com

Slide 65

Slide 65 text

BUILDINg BETTER WITH railS web APIS @caike Carlos Souza Code School Thank You! https://www.flickr.com/photos/glimeend/5387716362