Top Banner
Using Sinatra to Build REST APIs in Ruby James Higginbotham API Architect @launchany
53

Using Sinatra to Build REST APIs in Ruby

Feb 20, 2017

Download

Software

LaunchAny
Welcome message from author
This document is posted to help you gain knowledge. Please leave a comment to let me know what you think about it! Share it to your friends and learn new things together.
Transcript
Page 1: Using Sinatra to Build REST APIs in Ruby

Using  Sinatra  to  Build  REST  APIs  in  Ruby  

James  Higginbotham  API  Architect  @launchany  

Page 2: Using Sinatra to Build REST APIs in Ruby

Introduc?on  

Page 3: Using Sinatra to Build REST APIs in Ruby

WHAT  IS  SINATRA?  

Page 4: Using Sinatra to Build REST APIs in Ruby

Sinatra  is  a  DSL  for  quickly  crea<ng  web  applica<ons  in  

Ruby  

Page 5: Using Sinatra to Build REST APIs in Ruby

# hi.rb require 'rubygems’ require 'sinatra' get '/' do 'Hello world!’ end

$ gem install sinatra $ ruby hi.rb == Sinatra has taken the stage ... >> Listening on 0.0.0.0:4567 $ curl http://0.0.0.0:4567 Hello World

Page 6: Using Sinatra to Build REST APIs in Ruby

HOW  DOES  SINATRA  WORK?  

Page 7: Using Sinatra to Build REST APIs in Ruby

Rou?ng:  Verb  +  PaCern  +  Block   post ’/' do .. block .. end

Page 8: Using Sinatra to Build REST APIs in Ruby

Rou?ng:  Named  Params   get '/:id' do model = MyModel.find( params[:id] ) ... end

Page 9: Using Sinatra to Build REST APIs in Ruby

Rou?ng:  Splat  Support   get '/say/*/to/*' do # matches /say/hello/to/world params['splat'] # => ["hello", "world"] ... end get '/download/*.*' do # matches /download/path/to/file.xml params['splat'] # => ["path/to/file", "xml"] ... end

Page 10: Using Sinatra to Build REST APIs in Ruby

Rou?ng:  Regex  Support   get /\A\/hello\/([\w]+)\z/ do "Hello, #{params['captures'].first}!” ... end

Page 11: Using Sinatra to Build REST APIs in Ruby

Rou?ng:  Op?onal  Parameters   get '/posts.?:format?' do # matches "GET /posts" and # any extension "GET /posts.rss", "GET /posts.xml" etc. end

Page 12: Using Sinatra to Build REST APIs in Ruby

Rou?ng:  URL  Query  Parameters   get '/posts' do # matches "GET /posts?title=foo&author=bar" title = params['title'] author = params['author'] # uses title and author variables; # query is optional to the /posts route End

Page 13: Using Sinatra to Build REST APIs in Ruby

Rou?ng:  Condi?onal  Matching   get '/', :host_name => /^admin\./ do "Admin Area, Access denied!" end get '/', :provides => 'html' do haml :index end get '/', :provides => ['rss', 'atom', 'xml'] do builder :feed end

Page 14: Using Sinatra to Build REST APIs in Ruby

Rou?ng:  Custom  Condi?ons   set(:probability) { |value| condition { rand <= value } } get '/win_a_car', :probability => 0.1 do "You won!" end get '/win_a_car' do "Sorry, you lost." End

Page 15: Using Sinatra to Build REST APIs in Ruby

Returning  Results   # 1. String containing the body and default code of 200 get '/' do 'Hello world!’ end # 2. Response code + body get '/' do [200, 'Hello world!’] end # 3. Response code + headers + body get '/' do [200, {'Content-Type' => 'text/plain'}, 'Hello world!’] end

Page 16: Using Sinatra to Build REST APIs in Ruby

BUILDING  ON  RACK  

Page 17: Using Sinatra to Build REST APIs in Ruby

Hello  World  with  Rack   # hello_world.rb require 'rack' require 'rack/server’ class HelloWorldApp def self.call(env) [200, {}, 'Hello World’] end end Rack::Server.start :app => HelloWorldApp

Page 18: Using Sinatra to Build REST APIs in Ruby

Rack  env   # hello_world.rb require 'rack' require 'rack/server’ class HelloWorldApp def self.call(env) [200, {}, "Hello World. You said: #{env['QUERY_STRING']}"] end end Rack::Server.start :app => HelloWorldApp

Page 19: Using Sinatra to Build REST APIs in Ruby

Typical  env  { "SERVER_SOFTWARE"=>"thin 1.4.1 codename Chromeo", "SERVER_NAME"=>"localhost", "rack.input"=>#<StringIO:0x007fa1bce039f8>, "rack.version"=>[1, 0], "rack.errors"=>#<IO:<STDERR>>, "rack.multithread"=>false, "rack.multiprocess"=>false, "rack.run_once"=>false, "REQUEST_METHOD"=>"GET", "REQUEST_PATH"=>"/favicon.ico", "PATH_INFO"=>"/favicon.ico", "REQUEST_URI"=>"/favicon.ico", "HTTP_VERSION"=>"HTTP/1.1", "HTTP_HOST"=>"localhost:8080", "HTTP_CONNECTION"=>"keep-alive", "HTTP_ACCEPT"=>"*/*”, ...

Page 20: Using Sinatra to Build REST APIs in Ruby

Typical  env  (con’t)   ... "HTTP_USER_AGENT"=> "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_4) AppleWebKit/536.11 (KHTML, like Gecko) Chrome/20.0.1132.47 Safari/536.11", "HTTP_ACCEPT_ENCODING"=>"gzip,deflate,sdch", "HTTP_ACCEPT_LANGUAGE"=>"en-US,en;q=0.8", "HTTP_ACCEPT_CHARSET"=>"ISO-8859-1,utf-8;q=0.7,*;q=0.3", "HTTP_COOKIE"=> "_gauges_unique_year=1; _gauges_unique_month=1", "GATEWAY_INTERFACE"=>"CGI/1.2", "SERVER_PORT"=>"8080", "QUERY_STRING"=>"", "SERVER_PROTOCOL"=>"HTTP/1.1", "rack.url_scheme"=>"http", "SCRIPT_NAME"=>"", "REMOTE_ADDR"=>"127.0.0.1", ... }

Page 21: Using Sinatra to Build REST APIs in Ruby

The  Rack::Request  Wrapper   class HelloWorldApp def self.call(env) request = Rack::Request.new(env) request.params # contains the union of GET and POST params request.xhr? # requested with AJAX require.body # the incoming request IO stream if request.params['message'] [200, {}, request.params['message']] else [200, {}, 'Say something to me!'] end end end

Page 22: Using Sinatra to Build REST APIs in Ruby

Rack  Middleware  

u  Rack  allows  for  chaining  mul?ple  call()  methods  

u We  can  do  anything  we  want  within  each  call()  u  This  includes  separa?ng  behavior  into  reusable  classes  (e.g.  across  Sinatra  and  Rails)  

u  SRP  (Single  Responsibility  Principle)  – Each  class  has  a  single  responsibility  – Our  app  is  composed  of  mul?ple  classes  that  each  do  one  thing  well  

Page 23: Using Sinatra to Build REST APIs in Ruby

Rack::Builder  for  Middleware  # this returns an app that responds to call cascading down # the list of middlewares. app = Rack::Builder.new do use Rack::Etag # Add an ETag use Rack::ConditionalGet # Support Caching use Rack::Deflator # GZip run HelloWorldApp # Say Hello end Rack::Server.start :app => app # Resulting call tree: # Rack::Etag # Rack::ConditionalGet # Rack::Deflator # HelloWorldApp

Page 24: Using Sinatra to Build REST APIs in Ruby

Using  the  Rackup  Command  

u  Combines  all  of  these  concepts  into  a  config  u Will  start  a  web  process  with  your  Rack  app  u  Central  loca?on  for  requires,  bootstrapping  u  Enables  middleware  to  be  configured  as  well  u Default  filename  is  config.ru  u Used  to  bootstrap  Rails  

Page 25: Using Sinatra to Build REST APIs in Ruby

Using  Rackup  # config.ru # HelloWorldApp defintion # EnsureJsonResponse defintion # Timer definition use Timer use EnsureJsonResponse run HelloWorldApp

$ rackup –p 4567

Page 26: Using Sinatra to Build REST APIs in Ruby

Using  Mul?ple  Sinatra  Apps  

u  Rackup  allows  for  moun?ng  mul?ple  Sinatra  Apps  

u  This  allows  for  more  modular  APIs  u  Recommend  one  Sinatra  app  per  top-­‐level  resource  

Page 27: Using Sinatra to Build REST APIs in Ruby

Moun?ng  Mul?ple  Sinatra  Apps   # config.ru require 'sinatra' require 'app/auth_api' require 'app/users_api' require 'app/organizations_api' map "/auth" do run AuthApi end map "/users" do run UsersApi end map "/organizations" do run OrganizationsApi end

Page 28: Using Sinatra to Build REST APIs in Ruby

Important:  Require  !=  Automa?c  

u Must  manage  your  own  requires  u No  free  ride  (like  with  Rails)  u  This  means  order  of  requires  is  important!  

Page 29: Using Sinatra to Build REST APIs in Ruby

WHAT  IS  A  REST  API?  

Page 30: Using Sinatra to Build REST APIs in Ruby

Mul?ple  API  Design  Choices  

u  RPC-­‐based  – Uses  HTTP  for  transport  only  – Endpoints  are  not  unique,  only  the  payload  – No  HTTP  caching  available  – e.g.  POST  /getUserDetails,  POST  /createUser  

u  Resource-­‐based  – Unique  URLs  for  resources  and  collec?ons  – HTTP  caching  available  – e.g.  GET  /users/{userId}  and  GET  /users  

Page 31: Using Sinatra to Build REST APIs in Ruby

Hypermedia  REST  

u An  architectural  style,  with  constraints  u A  set  of  constraints,  usually  on  top  of  HTTP  u Not  a  standard;  builds  on  the  standard  of  HTTP  

u Mul?ple  content  types  (e.g.  JSON,  XML,  CSV)  u  The  response  is  a  representa?on  of  the  resource  state  (data)  plus  server-­‐side  state  in  the  form  of  ac<ons/transi<ons  (links)  

Page 32: Using Sinatra to Build REST APIs in Ruby

BUILDING  AN  API  USING  SINATRA  

Page 33: Using Sinatra to Build REST APIs in Ruby

Resource  Lifecycle  using  Sinatra  get '/users' do .. list a resource collection (and search) .. end get '/users/:id' do .. resource instance details .. end post '/users' do .. create new resource .. end put '/users/:id' do .. replace resource .. End delete ’/users/:id' do .. annihilate resource .. end

Page 34: Using Sinatra to Build REST APIs in Ruby

List  Resources  Example  get '/users' do # 1. capture any search filters using params[] email_filter = params[:email] # 2. build query and fetch results from database if email_filter users = User.where( email: email_filter ).all else users = User.all # 3. marshal results to proper content type (e.g. JSON) [200, users.to_json] end

Page 35: Using Sinatra to Build REST APIs in Ruby

List  Resources  Example  get '/users' do # 1. capture any search filters using params[] email_filter = params[:email] # 2. build query and fetch results from database if email_filter users = User.where( email: email_filter ).all else users = User.all # 3. marshal results to proper content type (e.g. JSON) [200, users.to_json] # Q: Which ORM should we use with Sinatra? # Q: Can we customize the results format easily? end

Page 36: Using Sinatra to Build REST APIs in Ruby

USEFUL  GEMS  

Page 37: Using Sinatra to Build REST APIs in Ruby

Selec?ng  an  ORM  

u Ac?veRecord  u DataMapper  u  Sequel  (my  favorite)  – Flexible  as  it  supports  Datasets  and  Models  

Page 38: Using Sinatra to Build REST APIs in Ruby

Sequel  Datasets  Example  require 'sequel' DB = Sequel.sqlite # memory database DB.create_table :items do primary_key :id String :name Float :price end items = DB[:items] # Create a dataset items.insert(:name => 'abc', :price => rand * 100) items.insert(:name => 'def', :price => rand * 100) items.insert(:name => 'ghi', :price => rand * 100) puts "Item count: #{items.count}" puts "The average price is: #{items.avg(:price)}”

Page 39: Using Sinatra to Build REST APIs in Ruby

Sequel  Model  Example  require 'sequel' DB = Sequel.sqlite # memory database class Post < Sequel::Model end post = Post[123] post = Post.new post.title = 'hello world' post.save

Page 40: Using Sinatra to Build REST APIs in Ruby

Select  a  Marshaling  Library  

u Ac?veModel::Serializers  (AMS)    – Works  with  Kaminari  and  WillPaginate  – Supported  by  Rails  core  team  – One-­‐way  JSON  genera?on  only  

u  Roar+Representable  (my  favorite)  – Works  with  and  without  Rails  – Bi-­‐direc?onal  marshaling  – Supports  JSON,  XML,  YAML,  hash  

Page 41: Using Sinatra to Build REST APIs in Ruby

Representable  module SongRepresenter include Representable::JSON property :title property :track collection :composers end class Song < OpenStruct end song = Song.new(title: "Fallout", track: 1) song.extend(SongRepresenter).to_json > {"title":"Fallout","track":1} song = Song.new.extend(SongRepresenter).from_json(%{ {"title":"Roxanne"} }) > #<Song title="Roxanne">

Page 42: Using Sinatra to Build REST APIs in Ruby

Roar  +  Representable  module SongRepresenter include Roar::JSON include Roar::Hypermedia property :title property :track collection :composers link :self do "/songs/#{title}" end end song = Song.new(title: "Fallout", track: 1) song.extend(SongRepresenter).to_json > {"title":"Fallout","track":1,"links": [{"rel":"self","href":"/songs/Fallout"}]}"

Page 43: Using Sinatra to Build REST APIs in Ruby

Tools  for  Tes?ng  Your  API  

u Unit  –  RSpec  – Models,  helpers  

u  Integra?on  –  RSpec  – Make  HTTP  calls  to  a  running  Sinatra  process  – Controller-­‐focused  

u Acceptance/BDD  –  RSpec,  Cucumber  – Make  HTTP  calls  to  a  running  Sinatra  process  – Use-­‐case/story  focused  

Page 44: Using Sinatra to Build REST APIs in Ruby

MATURING  YOUR  SINATRA  APPS  

Page 45: Using Sinatra to Build REST APIs in Ruby

Addi?onal  Gems  

u  faraday  –  HTTP  client  with  middleware  for  tes?ng  and  3rd  party  API  integra?on  

u  xml-­‐simple  –  Easy  XML  parsing  and  genera?on  u  faker  –  Generates  fake  names,  addresses,  etc.  u  uuidtools  –  uuid  generator  when  incremen?ng  integers  aren’t  good  enough  

u  bcrypt  –  Ruby  binding  for  OpenBSD  hashing  algorithm,  to  secure  data  at  rest  

Page 46: Using Sinatra to Build REST APIs in Ruby

Addi?onal  Gems  (part  2)  

u  rack-­‐conneg  –  Content  nego?a?on  support  

get '/hello' do response = { :message => 'Hello, World!' } respond_to do |wants| wants.json { response.to_json } wants.xml { response.to_xml } wants.other { content_type 'text/plain' error 406, "Not Acceptable" } end end curl -H "Accept: application/json" http://localhost:4567/hello

Page 47: Using Sinatra to Build REST APIs in Ruby

Addi?onal  Gems  (part  3)  

u  hirb  –  Console  formaing  of  data  from  CLI,  Rake  tasks  

irb>> Tag.last +-----+-------------------------+-------------+ | id | created_at | description | +-----+-------------------------+-------------+ | 907 | 2009-03-06 21:10:41 UTC | blah | +-----+-------------------------+-------------+ 1 row in set

Page 48: Using Sinatra to Build REST APIs in Ruby

Reloading  with  Shotgun  Gem  

u No  automa?c  reload  of  classes  with  Sinatra  u  Instead,  use  the  shotgun  gem:  

u Note:  Only  works  with  Ruby  MRI  where  fork()  is  available  (POSIX)  

$ gem install shotgun $ shotgun config.ru

Page 49: Using Sinatra to Build REST APIs in Ruby

Puma  +  JRuby  

u  Ruby  MRI  is  geing  beCer  u  JVM  is  faster  (2-­‐5x),  very  mature  (since  1997)  u High  performance  garbage  collectors,  na?ve  threading,  JMX  management  extensions  

u  JDBC  libraries  very  mature  and  performant  for  SQL-­‐based  access  

u  Puma  is  recommended  over  unicorn  for  JRuby    

Page 50: Using Sinatra to Build REST APIs in Ruby

From  Sinatra  to  Padrino  

u  Padrino  provides  Rails-­‐like  environment  for  Sinatra  

u  Build  in  Sinatra,  move  to  Padrino  when  needed  

u Generators,  pluggable  modules,  admin  generator  

Page 51: Using Sinatra to Build REST APIs in Ruby

Resources  

u  Sinatra  Docs:    hCp://www.sinatrarb.com/intro.html    

u  Introduc?on  to  Rack:  hCp://hawkins.io/2012/07/rack_from_the_beginning/    

u  Sequel  Gem:  hCps://github.com/jeremyevans/sequel    

u  Roar/Representable:  hCps://github.com/apotonick/roar    hCps://github.com/apotonick/representable    

Page 52: Using Sinatra to Build REST APIs in Ruby

Thanks  Ya’ll  

James  Higginbotham  [email protected]  hCp://launchany.com    

@launchany    

Design  Beau?ful  APIs:  hCp://TheApiDesignBook.com    

Page 53: Using Sinatra to Build REST APIs in Ruby

QUESTIONS