Create a lightweight REST service using Sinatra

Exposing data using a REST service is an easy task which can be achieved using almost any programming language. In the past I've used ASP.NET MVC WebApi to achieve this.

But as soon as you've to run your service on different platforms, CLR isn't the best option. Programming languages like Ruby and Python or Node.js are stronger when it comes to cross platform support.

I love the Ruby syntax and that's why I'll show how to build a small REST Service exposing tasks using Ruby's Sinatra framework.Sinatra doesn't require a fix or predefined project structure. I'd like to organise all the artefacts a little bit. That's why I've chosen the following structure

First let's review the model. I've built the model using ruby's datamapper gem.

#models/task.rb

class Task  
  include DataMapper::Resource

  property :id,           Serial
  property :title,        String
  property :completed,    Boolean
  property :description,  String
end  

Next and perhaps most important part is of course the routing for the service.

# routes/tasks.rb
get '/api/tasks' do  
  Task.all.to_json
end

get '/api/tasks/:id' do  
  t = Task.get(params[:id])
  if t.nil?
    halt 404
  end
  t.to_json
end

post '/api/tasks' do  
  body = JSON.parse request.body.read
  t = Task.create(
    title:    body['title'],
    director: body['director'],
    year:     body['year']
  )
  status 201
  t.to_json 
end

put '/api/tasks/:id' do  
  body = JSON.parse request.body.read
  t = Task.get(params[:id])
  if t.nil?
    halt 404
  end
  halt 500 unless t.update(
    title:      body['title'],
    director:   body['director'],
    year:       body['year'] 
  )
  t.to_json
end

delete '/api/tasks/:id' do  
  t = Task.get(params[:id])
  if t.nil?
    halt 404
  end
  halt 500 unless t.destroy
end  

As you can see it's pretty easy and straight forward to create all required kinds of routs for a common REST service. Next let's review the main entry file

# main.rb

require 'json'  
require 'sinatra'  
require 'data_mapper'  
require 'dm-migrations'

configure :development do  
  DataMapper::Logger.new($stdout, :debug)
  DataMapper.setup(:default, ENV['DATABASE_URL'] || "sqlite3://#{Dir.pwd}/development.db")
end 

require './models/init'  
require './routes/init'

DataMapper.finalize  

The main.rb itself is just loading all the dependencies. Right after that the DataMapper is configured to work with my local sqlite3 database. Last important part is actually loading the init files from both sub directories (models and routes). These init files are then again loading all specific models and routes.

Installing the dependencies

First you need of course ruby, gem and sqlite3 itself on your system. There are plenty of samples and articles out there describing how to install all these components on any kind of platform.

Installing DataMapper

Installing DataMapper and the required adapter for sqlite3 is also straight forward. Go and check out their website for detailed installation instructions

Using Bundler

Bundler is a dependency manager for ruby like nuget for clr or npm for Node.js. I use gemrat in order to automatically update the gemfile (defines all the dependencies) from the terminal.

$ gemrat json
$ gemrat sinatra
$ gemrat data_mapper
$ gemrat dm-sqlite-adapter

Automate things using a Rakefile

DataMapper is offering some great hooks for automating database generation or updating the database based on your model classes.

# Rakefile
require 'dm-migrations'

desc "migrates the db"  
task :migrate do  
  require './main'
  DataMapper.auto_migrate!
end

desc "upgrades the db"  
task :upgrade do  
  require './main'
  DataMapper.auto_upgrade! 
end  

Starting the REST service

Starting the REST service can be done by executing the following command

$ ruby main.rb

Summary / SourceCode

As you can see things are really easy in Ruby with Sinatra and DataMapper. In order to access the entire sample go and check my github repository at https://github.com/ThorstenHans/Sinatra.REST.Sample

Comments

comments powered by Disqus