Created
July 10, 2012 00:09
-
-
Save stevie-chambers/3079964 to your computer and use it in GitHub Desktop.
Real basic REST API server in one script
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env ruby | |
# | |
# [email protected] July 2012 | |
# | |
# I'm playing around with APIs, and sharing my learnings | |
# You can follow my trials and tribulations at http://viewyonder.com/apis | |
# | |
# This is a simple Ruby script to show how a simple API might work. | |
# The resource model is just a simple array of a single class - no back-end store (yet) | |
# | |
# This will take browser/curl on all four CRUD verbs and hopefully give the right HTTP responses :) | |
# | |
# Open the API root for more 'documentation' | |
require 'sinatra' | |
require 'JSON' | |
API_VERSION='2012-07-10' | |
# Our complex resource model :) We have a hash of these, key is id. That's it; that's all | |
class Person | |
attr_accessor :id, :name | |
def initialize(id,name) | |
@id = id | |
@name = name | |
end | |
end | |
steve = Person.new('1','Steve') | |
alan = Person.new('2','Alan') | |
dave = Person.new('3','Dave') | |
team = { '1' => steve, '2' => alan, '3' => dave } | |
# This bad boy is called at the end of every route to push (some of) the client's request headers for debugging. | |
def request_html(request) | |
html = Array.new | |
html << "<h3>Debugging</h3>" | |
html << "<p>Team API Version X-Api-Header - #{API_VERSION}</p>" | |
html << "<p>Request Method - #{request.request_method}</p>" | |
html << "<p>URL - #{request.url}</p>" | |
html << "<p>Accept - #{request.accept}</p>" | |
html << "<p>Body - #{request.body}</p>" | |
html << "<p>Query String - #{request.query_string}</p>" | |
html << "<p>Content Length - #{request.content_length}</p>" | |
html << "<p>Media Type - #{request.media_type}</p>" | |
html << "<p>HEADER - #{request["SOME_HEADER"]}</p>" # Use this later to authentication | |
html << "<p>User Agent - #{request.user_agent}</p>" | |
html | |
end | |
def doc_html | |
html = Array.new | |
html << "<h3>Documentation</h3>" | |
html << "<p>This version uses all the CRUD verbs - in order, POST, GET, PUT, DELETE.</p>" | |
html << "<p>The only resource in the API is /api/team and in there each member has a unique number ID.</p>" | |
html << "<p>This team resource is a simple hash of a simple class called Person with id and name attributes.</p>" | |
html << %{<li>Create a specific resource: curl -X POST -i -d '{ "id": "666", "name" : "Jeramiah" }' http://localhost:4567/api/team/666</li>} | |
html << %{<li>Read a specific resource: curl -X GET -i http://localhost:4567/api/team/666</li>} | |
html << %{<li>Update a specific resource: curl -X PUT -i -d '{ "id": "666", "name" : "Satan" }' http://localhost:4567/api/team/666</li>} | |
html << %{<li>Delete a specific resource: curl -X DELETE -i http://localhost:4567/api/team/666</li></ul>} | |
html | |
end | |
# Sinatra server root, not the api root (that's /api, not /) | |
get '/' do | |
html = Array.new | |
html << %{<h1>Sinatra Web Server</h1>} | |
html << %{<h3>Resources</h3>} | |
html << %{<p><a href="/api" rel="api">Team API Root End Point</a></p>} | |
request_html(request).each { |p| html << p } | |
status 200 # OK | |
content_type 'text/html' | |
body(html) | |
end | |
# The Team API root, so we give some useful info here on how to use, what's done, what's not done. | |
get '/api/?' do | |
html = Array.new | |
html << %{<h1>Team API</h1>} | |
doc_html.each { |p| html << p } | |
html << %{<h3>Resources</h3>} | |
html << %{<p><a href="/api/team" rel="team">Team Resources</a></p>} | |
request_html(request).each { |p| html << p } | |
status 200 # OK | |
content_type 'text/html' | |
body(html) | |
end | |
get '/api/team/?' do | |
html = Array.new | |
html << %{<h1>Team Resources</h1>} | |
doc_html.each { |p| html << p } | |
html << %{<h3>Resources</h3>} | |
team.each do |key, person| | |
html << %{<li><a href="/api/team/#{person.id}" rel="#{person.name}">#{person.name}</a></li>} | |
end | |
html << %{<p><a href="/api" rel="api">Team API Root End Point</a></p>} | |
request_html(request).each { |p| html << p } | |
status 200 # OK | |
content_type 'text/html' | |
body(html) | |
end | |
get '/api/team/:id' do | |
person = team[params[:id]] | |
if person.nil? then | |
html = Array.new | |
html << %{<h1>Team Resource - Error</h1>} | |
html << %{<p>NOT FOUND: Unable to find the team member with ID: #{params[:id]}</p>} | |
html << %{<p><a href="/api/team" rel="team">Team Resources</a></p>} | |
request_html(request).each { |p| html << p } | |
status 404 # Not Found | |
content_type 'text/html' | |
body(html) | |
else | |
html = Array.new | |
doc_html.each { |p| html << p } | |
html << %{<h1>Team Resource - #{person.name}</h1>} | |
html << %{<p>ID - #{person.id}</p>} | |
html << %{<p>Name - #{person.name}</p>} | |
html << %{<p><a href="/api/team" rel="team">Team Resources</a></p>} | |
request_html(request).each { |p| html << p } | |
status 200 # OK | |
content_type 'text/html' | |
body(html) | |
end | |
end | |
delete '/api/team/:id' do | |
person = team[params[:id]] | |
if person.nil? then | |
html = Array.new | |
html << %{<h1>Team Resource</h1>} | |
html << %{<p>NOT FOUND: Unable to find the team member with ID: #{params[:id]}</p>} | |
html << %{<p><a href="/api/team" rel="team">Team Resources</a></p>} | |
request_html(request).each { |p| html << p } | |
status 404 # Not Found | |
content_type 'text/html' | |
body(html) | |
else | |
name = team[params[:id]].name | |
team.delete(params[:id]) | |
html = Array.new | |
html << %{<h1>Team Resource</h1>} | |
html << %{<p>ACCEPTED: Deleted team member #{name} with ID #{params[:id]}</p>}# | |
html << %{<p><a href="/api/team" rel="team">Team Resources</a></p>} | |
request_html(request).each { |p| html << p } | |
status 202 # Accepted | |
content_type 'text/html' | |
body(html) | |
end | |
end | |
post '/api/team/:id' do | |
data = JSON.parse(request.body.string) | |
if data.nil? or !data.has_key?('id') or !data.has_key?('name') then | |
html = Array.new | |
html << %{<h1>Team Resources</h1>} | |
html << %{<p>BAD REQUEST: Unable to create the team member, insufficient data (missing id or name)</p>} | |
html << %{<p><a href="/api/team" rel="team">Team Resources</a></p>} | |
request_html(request).each { |p| html << p } | |
status 400 # Bad Request | |
content_type 'text/html' | |
body(html) | |
else | |
html = Array.new | |
html << %{<h1>Team Resourcer</h1>} | |
if team[data['id']] then | |
html << %{<p>FORBIDDEN: Unable to create the team member, id #{data['id']} already exists</p>} | |
html << %{<p><a href="/api/team/#{data['id']}">Go to see existing team member</a></p>} | |
status 403 # Forbidden | |
else | |
person = Person.new(data['id'],data['name']) | |
team[data['id']] = person | |
html << %{<p>CREATED: id: #{person.id} name: #{person.name}</p>} | |
status 201 # Created | |
end | |
html << %{<p><a href="/api/team" rel="team">Team Resources</a></p>} | |
request_html(request).each { |p| html << p } | |
content_type 'text/html' | |
body(html) | |
end | |
end | |
put '/api/team/:id' do | |
data = JSON.parse(request.body.string) | |
if data.nil? or !data.has_key?('id') or !data.has_key?('name') then | |
html = Array.new | |
html << %{<h1>Team Resources</h1>} | |
html << %{<p>BAD REQUEST: Unable to update the team member, insufficient request data (missing id or name)</p>} | |
html << %{<p><a href="/api/team" rel="team">Team Resources</a></p>} | |
request_html(request).each { |p| html << p } | |
status 400 # Bad Request | |
content_type 'text/html' | |
body(html) | |
else | |
html = Array.new | |
html << %{<h1>Team Resourcer</h1>} | |
if !team[data['id']] then | |
html << %{<p>NOT FOUND: Unable to update the team member, id #{data['id']} doesn't exist</p>} | |
status 404 # Not Found | |
else | |
person = Person.new(data['id'],data['name']) | |
team[data['id']] = person | |
html << %{<p>ACCEPTED: Updated team member id #{person.id} to #{person.name}</p>} | |
status 202 # Accepted | |
end | |
html << %{<p><a href="/api/team" rel="team">Team Resources</a></p>} | |
request_html(request).each { |p| html << p } | |
content_type 'text/html' | |
body(html) | |
end | |
end | |
after do | |
response['X-Api-Header'] = API_VERSION | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment