Slide 1

Slide 1 text

BUILDINg BETTER WITH railS Devils Tower, WY web APIS @caike Carlos Souza Code School

Slide 2

Slide 2 text

codeschool.com

Slide 3

Slide 3 text

tryruby.org

Slide 4

Slide 4 text

railsforzombies.org

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

Slide 11

Slide 11 text

endpoints, resources and url-to-controller mappings ROUTES

Slide 12

Slide 12 text

URLs are very important /gists /gists/public /gists/starred

Slide 13

Slide 13 text

No content

Slide 14

Slide 14 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 15

Slide 15 text

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

Slide 16

Slide 16 text

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

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' 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', 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 19

Slide 19 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 20

Slide 20 text

app/controllers/api/projects_controller.rb module Api class ProjectsController < ApplicationController def index ... end end end

Slide 21

Slide 21 text

ActiveSupport::Inflector.inflections(:en) do |inflect| inflect.acronym 'API' end config/initializers/inflections.rb module API class ProjectsController < ApplicationController def index ... end end end

Slide 22

Slide 22 text

conneg content negotiation

Slide 23

Slide 23 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 24

Slide 24 text

/projects.json GET /projects
 Accept: application/json vs. Rails nicety Part of the HTTP spec client requests

Slide 25

Slide 25 text

responders extracted out to 
 responders gem in Rails 4.2 module API class ProjectsController < ApplicationController respond_to :json, :xml ! def index @projects = Project.all ! respond_with(@projects) end end end

Slide 26

Slide 26 text

calls #to_json calls #to_xml respond_to module API class ProjectsController < ApplicationController def index @projects = Project.recent ! respond_to do |format| format.json { render json: @projects, status: 200 } format.xml { render xml: @projects, status: 200 } end end end end

Slide 27

Slide 27 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 28

Slide 28 text

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

Slide 29

Slide 29 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 30

Slide 30 text

AUTHentication preventing unauthorized access to protected resources

Slide 31

Slide 31 text

• Quick and Simple • Reutilizes existing credentials • HTTP spec (RFC 2617) http basic AUTH

Slide 32

Slide 32 text

http basic AUTH module API class ProjectsController < ApplicationController before_action :authenticate_or_request ! protected ! def authenticate_or_request authenticate_or_request_with_http_basic do |user, pwd| User.authenticate(user, pwd) end end end end

Slide 33

Slide 33 text

use the -u option $ curl -I http://carlos:secret@localhost:3000/projects ! HTTP/1.1 200 OK Content-Type: application/json; charset=utf-8 $ curl -Iu 'carlos:secret' http://localhost:3000/projects ! HTTP/1.1 200 OK Content-Type: application/json; charset=utf-8 http basic AUTH with curl

Slide 34

Slide 34 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 35

Slide 35 text

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

Slide 36

Slide 36 text

token for the Digital Ocean API For security purposes, 
 some services will only display the access token once. providing the token

Slide 37

Slide 37 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 38

Slide 38 text

UUID - Universally Unique Identifier GENERATING TOKENS RFC 4122

Slide 39

Slide 39 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 40

Slide 40 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 41

Slide 41 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 com curl

Slide 42

Slide 42 text

versioning resources Version

Slide 43

Slide 43 text

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

Slide 44

Slide 44 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 45

Slide 45 text

410 Gone

Slide 46

Slide 46 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 47

Slide 47 text

TESTS automated verification

Slide 48

Slide 48 text

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

Slide 49

Slide 49 text

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

Slide 50

Slide 50 text

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

Slide 51

Slide 51 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 52

Slide 52 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 53

Slide 53 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 54

Slide 54 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 55

Slide 55 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 56

Slide 56 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 57

Slide 57 text

railsapis.codeschool.com

Slide 58

Slide 58 text

Thank you BUILDINg BETTER WITH railS @caike Carlos Souza Code School web APIS Devils Tower, WY