public
Created

An example of how to write a simple JSON REST API with a simplistic router directly on top of Rack (i.e. without the use of a framework like Sinatra).

  • Download Gist
rack-rest-api-example.rb
Ruby
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95
#################################################################
#
# File: lib/api.rb
#
#################################################################
 
require 'user' # ActiveRecord model
 
# Connect to the db
ActiveRecord::Base.establish_connection(ENV['DATABASE_URL'])
 
class Api
def self.routes
[
{method: "GET", path: %r{^/users/(?<id>\d+)}, api_method: :find},
{method: "POST", path: %r{^/users}, api_method: :create},
{method: "PUT", path: %r{^/users/(?<id>\d+)}, api_method: :update},
{method: "GET", path: %r{^/authenticate}, api_method: :authenticate}
]
end
 
def self.find(params)
User.find(params[:id])
end
def self.create(params)
User.create!(params[:user])
end
def self.update(params)
User.update_attributes!(params[:user])
end
def self.authenticate(params)
User.authenticate(params[:email], params[:password])
end
end
 
 
#################################################################
#
# File: config.ru
#
#################################################################
 
require "rubygems"
require "bundler/setup"
 
require "json"
 
$: << File.join(File.dirname(__FILE__), "lib")
require "api"
 
# Simplistic router based on path and request method
class Router
def self.find_match(request_method, path, routes)
routes.each do |route|
if match = match_route(request_method, path, route)
return match
end
end
return nil
end
 
def self.match_route(request_method, path, route)
if route[:method] == request_method && match_data = route[:path].match(path)
route_match = route.slice(:api_method)
if match_data.names.present?
route_match[:params] = match_data.names.inject({}) { |params, name| params[name] = match_data[name]; params }
else
route_match[:params] = {}
end
route_match
else
nil
end
end
end
 
run(->(env) {
request = Rack::Request.new(env)
if route_match = Router.find_match(request.request_method, request.path, Api.routes)
begin
api_params = HashWithIndifferentAccess.new(request.params.merge(route_match[:params]))
response_data = Api.send(route_match[:api_method], api_params)
[200, {'Content-Type' => 'application/json'}, StringIO.new(response_data.to_json)]
rescue ActiveRecord::RecordNotFound => e
[404, {'Content-Type' => 'text/plain'}, StringIO.new(e.message)]
rescue ActiveRecord::RecordInvalid => e
[422, {'Content-Type' => 'text/plain'}, StringIO.new(e.record.full_error_messages)]
end
else
[404, {'Content-Type' => 'text/plain'}, StringIO.new('file not found')]
end
})

Please sign in to comment on this gist.

Something went wrong with that request. Please try again.