using sinatra to build rest apis in ruby

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

Upload: launchany

Post on 20-Feb-2017

4.932 views

Category:

Software


4 download

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