SlideShare a Scribd company logo
Using	
  Sinatra	
  to	
  Build	
  
REST	
  APIs	
  in	
  Ruby	
  
James	
  Higginbotham	
  
API	
  Architect	
  
@launchany	
  
Introduc?on	
  
WHAT	
  IS	
  SINATRA?	
  
Sinatra	
  is	
  a	
  DSL	
  for	
  quickly	
  
crea<ng	
  web	
  applica<ons	
  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
HOW	
  DOES	
  SINATRA	
  WORK?	
  
Rou?ng:	
  Verb	
  +	
  PaCern	
  +	
  Block	
  
post ’/' do
.. block ..
end
Rou?ng:	
  Named	
  Params	
  
get '/:id' do
model = MyModel.find( params[:id] )
...
end
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
Rou?ng:	
  Regex	
  Support	
  
get /A/hello/([w]+)z/ do
"Hello, #{params['captures'].first}!”
...
end
Rou?ng:	
  Op?onal	
  Parameters	
  
get '/posts.?:format?' do
# matches "GET /posts" and
# any extension "GET /posts.rss", "GET /posts.xml" etc.
end
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
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
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
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
BUILDING	
  ON	
  RACK	
  
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
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
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"=>"*/*”,
...
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",
...
}
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
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	
  
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
Using	
  the	
  Rackup	
  Command	
  
uï”â€Ż Combines	
  all	
  of	
  these	
  concepts	
  into	
  a	
  conïŹg	
  
uï”â€Ż Will	
  start	
  a	
  web	
  process	
  with	
  your	
  Rack	
  app	
  
uï”â€Ż Central	
  loca?on	
  for	
  requires,	
  bootstrapping	
  
uï”â€Ż Enables	
  middleware	
  to	
  be	
  conïŹgured	
  as	
  well	
  
uï”â€Ż Default	
 Â ïŹlename	
  is	
  conïŹg.ru	
  
uï”â€Ż Used	
  to	
  bootstrap	
  Rails	
  
Using	
  Rackup	
  
# config.ru
# HelloWorldApp defintion
# EnsureJsonResponse defintion
# Timer definition
use Timer
use EnsureJsonResponse
run HelloWorldApp
$ rackup –p 4567
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	
  
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
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!	
  
WHAT	
  IS	
  A	
  REST	
  API?	
  
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	
  
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)	
  
BUILDING	
  AN	
  API	
  USING	
  SINATRA	
  
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
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
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
USEFUL	
  GEMS	
  
Selec?ng	
  an	
  ORM	
  
uï”â€Ż Ac?veRecord	
  
uï”â€Ż DataMapper	
  
uï”â€Ż Sequel	
  (my	
  favorite)	
  
– Flexible	
  as	
  it	
  supports	
  Datasets	
  and	
  Models	
  
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)}”
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
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	
  
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">
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"}]}"
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	
  
MATURING	
  YOUR	
  SINATRA	
  APPS	
  
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	
  
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
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
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
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	
  	
  
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	
  
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	
  	
  
Thanks	
  Ya’ll	
  
James	
  Higginbotham	
  
james@launchany.com	
  
hCp://launchany.com	
  	
  
@launchany	
  
	
  
Design	
  Beau?ful	
  APIs:	
  
hCp://TheApiDesignBook.com	
  	
  
QUESTIONS	
  

More Related Content

PDF
Apache Spark - Basics of RDD | Big Data Hadoop Spark Tutorial | CloudxLab
PDF
Spark graphx
PDF
Introduction to Apache Spark
PDF
HBase Advanced - Lars George
PDF
Java 8 Lambda Expressions & Streams
PDF
Apache Spark Overview
PDF
Struts2
KEY
Redis overview for Software Architecture Forum
Apache Spark - Basics of RDD | Big Data Hadoop Spark Tutorial | CloudxLab
Spark graphx
Introduction to Apache Spark
HBase Advanced - Lars George
Java 8 Lambda Expressions & Streams
Apache Spark Overview
Struts2
Redis overview for Software Architecture Forum

What's hot (20)

PDF
C* Summit 2013: The World's Next Top Data Model by Patrick McFadin
PDF
Introduction to apache spark
PDF
Paris Redis Meetup Introduction
PDF
Cassandra Introduction & Features
PPTX
Laravel Beginners Tutorial 1
PDF
HBase Storage Internals
PDF
MySQL GTID 시작하Ʞ
PDF
The Proxy Wars - MySQL Router, ProxySQL, MariaDB MaxScale
PPTX
PostgreSQL.pptx
PPTX
Introduction to Storm
PPTX
MongoDB
PPTX
Apache Spark Architecture
PDF
Introduction to HBase
PPT
Java Collections Framework
PDF
Big Data: Getting started with Big SQL self-study guide
PPTX
Non relational databases-no sql
PPTX
Introduction to Apache Spark
PDF
Dependency injection in Java, from naive to functional
PDF
PUC SE Day 2019 - SpringBoot
PPTX
C* Summit 2013: The World's Next Top Data Model by Patrick McFadin
Introduction to apache spark
Paris Redis Meetup Introduction
Cassandra Introduction & Features
Laravel Beginners Tutorial 1
HBase Storage Internals
MySQL GTID 시작하Ʞ
The Proxy Wars - MySQL Router, ProxySQL, MariaDB MaxScale
PostgreSQL.pptx
Introduction to Storm
MongoDB
Apache Spark Architecture
Introduction to HBase
Java Collections Framework
Big Data: Getting started with Big SQL self-study guide
Non relational databases-no sql
Introduction to Apache Spark
Dependency injection in Java, from naive to functional
PUC SE Day 2019 - SpringBoot
Ad

Similar to Using Sinatra to Build REST APIs in Ruby (20)

KEY
Sinatra for REST services
PDF
Web Development with Sinatra
PDF
Sinatra Rack And Middleware
PDF
Finding Frank - Spotify API.pdf
PDF
Sinatra and JSONQuery Web Service
PDF
Swing when you're winning - an introduction to Ruby and Sinatra
PDF
Sinatra Introduction
PPTX
Sinatra
PDF
Sinatra: ĐżŃ€ĐŸŃˆĐ»ĐŸĐ”, Đ±ŃƒĐŽŃƒŃ‰Đ”Đ” Đž ĐœĐ°ŃŃ‚ĐŸŃŃ‰Đ”Đ”
ODP
Ruby off Rails---rack, sinatra and sequel
PDF
20150118 歾怋 Sinatra ć„œéŽćčŽ
KEY
Wider than rails
PPTX
Sinatra
PDF
Rails Sojourn: One Man's Journey - Wicked Good Ruby Conference 2013
PDF
Web Clients for Ruby and What they should be in the future
PDF
Strangers In The Night: Ruby, Rack y Sinatra - Herramientas potentes para con...
PPT
Lecture 3 - Comm Lab: Web @ ITP
PDF
Functional Web Apps with WebMachine Framework - Mikhail Bortnyk
PDF
Functional Web Apps with WebMachine Framework
PPTX
From Ruby to Node.js
Sinatra for REST services
Web Development with Sinatra
Sinatra Rack And Middleware
Finding Frank - Spotify API.pdf
Sinatra and JSONQuery Web Service
Swing when you're winning - an introduction to Ruby and Sinatra
Sinatra Introduction
Sinatra
Sinatra: ĐżŃ€ĐŸŃˆĐ»ĐŸĐ”, Đ±ŃƒĐŽŃƒŃ‰Đ”Đ” Đž ĐœĐ°ŃŃ‚ĐŸŃŃ‰Đ”Đ”
Ruby off Rails---rack, sinatra and sequel
20150118 歾怋 Sinatra ć„œéŽćčŽ
Wider than rails
Sinatra
Rails Sojourn: One Man's Journey - Wicked Good Ruby Conference 2013
Web Clients for Ruby and What they should be in the future
Strangers In The Night: Ruby, Rack y Sinatra - Herramientas potentes para con...
Lecture 3 - Comm Lab: Web @ ITP
Functional Web Apps with WebMachine Framework - Mikhail Bortnyk
Functional Web Apps with WebMachine Framework
From Ruby to Node.js
Ad

More from LaunchAny (20)

PPTX
Refining Your API Design - Architecture and Modeling Learning Event
PPTX
Event-Based API Patterns and Practices
PDF
Event-based API Patterns and Practices - AsyncAPI Online Conference
PDF
GlueCon 2019: Beyond REST - Moving to Event-Based APIs and Streaming
PDF
Austin API Summit 2019 - APIs, Microservices, and Serverless: The Shape of Th...
PDF
APIStrat Keynote: Lessons in Transforming the Enterprise to an API Platform
PPTX
Austin API Summit 2018: Are REST APIs Still Relevant Today?
PDF
GlueCon 2018: Are REST APIs Still Relevant Today?
PPTX
Lessons in Transforming the Enterprise to an API Platform
PPTX
APIStrat 2017: API Design in the Age of Bots, IoT, and Voice
PDF
API Design in the Age of Bots, IoT, and Voice
PDF
APIStrat 2016: Moving Toward a Modular Enterprise
PDF
API:World 2016 - Applying Domain Driven Design to APIs and Microservices
PPTX
Moving Toward a Modular Enterprise - All About the API Conference 2016
PPTX
Designing APIs and Microservices Using Domain-Driven Design
PPTX
Applying Domain-Driven Design to APIs and Microservices - Austin API Meetup
PDF
APIs Are Forever - How to Design Long-Lasting APIs
PDF
API Thinking - How to Design APIs Through Systems Design
PDF
Swagger 2.0: Latest and Greatest
PDF
Gluecon 2015 Recap
Refining Your API Design - Architecture and Modeling Learning Event
Event-Based API Patterns and Practices
Event-based API Patterns and Practices - AsyncAPI Online Conference
GlueCon 2019: Beyond REST - Moving to Event-Based APIs and Streaming
Austin API Summit 2019 - APIs, Microservices, and Serverless: The Shape of Th...
APIStrat Keynote: Lessons in Transforming the Enterprise to an API Platform
Austin API Summit 2018: Are REST APIs Still Relevant Today?
GlueCon 2018: Are REST APIs Still Relevant Today?
Lessons in Transforming the Enterprise to an API Platform
APIStrat 2017: API Design in the Age of Bots, IoT, and Voice
API Design in the Age of Bots, IoT, and Voice
APIStrat 2016: Moving Toward a Modular Enterprise
API:World 2016 - Applying Domain Driven Design to APIs and Microservices
Moving Toward a Modular Enterprise - All About the API Conference 2016
Designing APIs and Microservices Using Domain-Driven Design
Applying Domain-Driven Design to APIs and Microservices - Austin API Meetup
APIs Are Forever - How to Design Long-Lasting APIs
API Thinking - How to Design APIs Through Systems Design
Swagger 2.0: Latest and Greatest
Gluecon 2015 Recap

Recently uploaded (20)

PDF
T3DD25 TYPO3 Content Blocks - Deep Dive by André Kraus
PDF
Why TechBuilder is the Future of Pickup and Delivery App Development (1).pdf
PDF
Internet Downloader Manager (IDM) Crack 6.42 Build 42 Updates Latest 2025
PPTX
history of c programming in notes for students .pptx
PPTX
Oracle E-Business Suite: A Comprehensive Guide for Modern Enterprises
PDF
How to Choose the Right IT Partner for Your Business in Malaysia
PDF
SAP S4 Hana Brochure 3 (PTS SYSTEMS AND SOLUTIONS)
PPTX
Agentic AI Use Case- Contract Lifecycle Management (CLM).pptx
PDF
Which alternative to Crystal Reports is best for small or large businesses.pdf
PDF
Design an Analysis of Algorithms I-SECS-1021-03
PPTX
L1 - Introduction to python Backend.pptx
PDF
System and Network Administration Chapter 2
PDF
PTS Company Brochure 2025 (1).pdf.......
PDF
medical staffing services at VALiNTRY
PDF
Internet Downloader Manager (IDM) Crack 6.42 Build 41
PPTX
Agentic AI : A Practical Guide. Undersating, Implementing and Scaling Autono...
PPTX
CHAPTER 12 - CYBER SECURITY AND FUTURE SKILLS (1) (1).pptx
PPTX
Introduction to Artificial Intelligence
PDF
Upgrade and Innovation Strategies for SAP ERP Customers
PDF
Odoo Companies in India – Driving Business Transformation.pdf
T3DD25 TYPO3 Content Blocks - Deep Dive by André Kraus
Why TechBuilder is the Future of Pickup and Delivery App Development (1).pdf
Internet Downloader Manager (IDM) Crack 6.42 Build 42 Updates Latest 2025
history of c programming in notes for students .pptx
Oracle E-Business Suite: A Comprehensive Guide for Modern Enterprises
How to Choose the Right IT Partner for Your Business in Malaysia
SAP S4 Hana Brochure 3 (PTS SYSTEMS AND SOLUTIONS)
Agentic AI Use Case- Contract Lifecycle Management (CLM).pptx
Which alternative to Crystal Reports is best for small or large businesses.pdf
Design an Analysis of Algorithms I-SECS-1021-03
L1 - Introduction to python Backend.pptx
System and Network Administration Chapter 2
PTS Company Brochure 2025 (1).pdf.......
medical staffing services at VALiNTRY
Internet Downloader Manager (IDM) Crack 6.42 Build 41
Agentic AI : A Practical Guide. Undersating, Implementing and Scaling Autono...
CHAPTER 12 - CYBER SECURITY AND FUTURE SKILLS (1) (1).pptx
Introduction to Artificial Intelligence
Upgrade and Innovation Strategies for SAP ERP Customers
Odoo Companies in India – Driving Business Transformation.pdf

Using Sinatra to Build REST APIs in Ruby

  • 1. Using  Sinatra  to  Build   REST  APIs  in  Ruby   James  Higginbotham   API  Architect   @launchany  
  • 4. Sinatra  is  a  DSL  for  quickly   crea<ng  web  applica<ons  in   Ruby  
  • 5. # 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
  • 6. HOW  DOES  SINATRA  WORK?  
  • 7. Rou?ng:  Verb  +  PaCern  +  Block   post ’/' do .. block .. end
  • 8. Rou?ng:  Named  Params   get '/:id' do model = MyModel.find( params[:id] ) ... end
  • 9. 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
  • 10. Rou?ng:  Regex  Support   get /A/hello/([w]+)z/ do "Hello, #{params['captures'].first}!” ... end
  • 11. Rou?ng:  Op?onal  Parameters   get '/posts.?:format?' do # matches "GET /posts" and # any extension "GET /posts.rss", "GET /posts.xml" etc. end
  • 12. 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
  • 13. 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
  • 14. 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
  • 15. 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
  • 17. 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
  • 18. 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
  • 19. 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"=>"*/*”, ...
  • 20. 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", ... }
  • 21. 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
  • 22. 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  
  • 23. 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
  • 24. Using  the  Rackup  Command   uï”â€Ż Combines  all  of  these  concepts  into  a  conïŹg   uï”â€Ż Will  start  a  web  process  with  your  Rack  app   uï”â€Ż Central  loca?on  for  requires,  bootstrapping   uï”â€Ż Enables  middleware  to  be  conïŹgured  as  well   uï”â€Ż Default Â ïŹlename  is  conïŹg.ru   uï”â€Ż Used  to  bootstrap  Rails  
  • 25. Using  Rackup   # config.ru # HelloWorldApp defintion # EnsureJsonResponse defintion # Timer definition use Timer use EnsureJsonResponse run HelloWorldApp $ rackup –p 4567
  • 26. 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  
  • 27. 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
  • 28. 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!  
  • 29. WHAT  IS  A  REST  API?  
  • 30. 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  
  • 31. 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)  
  • 32. BUILDING  AN  API  USING  SINATRA  
  • 33. 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
  • 34. 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
  • 35. 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
  • 37. Selec?ng  an  ORM   uï”â€Ż Ac?veRecord   uï”â€Ż DataMapper   uï”â€Ż Sequel  (my  favorite)   – Flexible  as  it  supports  Datasets  and  Models  
  • 38. 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)}”
  • 39. 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
  • 40. 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  
  • 41. 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">
  • 42. 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"}]}"
  • 43. 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  
  • 45. 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  
  • 46. 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
  • 47. 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
  • 48. 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
  • 49. 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    
  • 50. 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  
  • 51. 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    
  • 52. Thanks  Ya’ll   James  Higginbotham   james@launchany.com   hCp://launchany.com     @launchany     Design  Beau?ful  APIs:   hCp://TheApiDesignBook.com   Â