Build a Sinatra API Using TDD, Heroku, and Continuous Integration with Travis

Share this article

sinatra_travis_heroku

This post was inspired by this brilliant video, where Konstantin Haase, the maintainer of Sinatra builds a fully working app and deploys it on Heroku with tests on Travis (where he works).

I decided to do a similar example that walks through each step of building an API service that demonstrates a typical Sinatra development cycle workflow – write your tests, write your code, push the code to GitHub, and check it works when deployed to Heroku with continuous integration on Travis.

The post will go from start to finish and cover the following:

  • Writing tests using MiniTest
  • Building a Sinatra API that returns information in JSON format
  • Using Bundler to manage dependencies
  • Using Git to manage version control
  • Hosting Code on GitHub
  • Deploying and host the site on Heroku
  • Performing Continuous Integration with Travis CI

Define the Problem

The application I want to build is called Number Cruncher. It will return information about numbers, such as its factors, whether it is odd or even, and if it’s prime.

The first thing I want to do is monkey-patch the Integer class to add a method that returns the factors of a number as an array. I also want to add a prime? method to test if a number is prime. Once I’ve done this, I’ll use Sinatra to provide an API that will return information about a number in JSON format.

  • Number Cruncher is running live on Heroku
  • You can see the Continuous Integration tests running on Travis
  • The code is on GitHub

Testing

Before we write any code, we should write some tests that cover the description above. First of all, create a file called ‘number_cruncher.rb’ and another called ‘test.rb’. Then add the following MiniTest setup code to ‘test.rb’:

ENV['RACK_ENV'] = 'test'
require 'minitest/autorun'
require 'rack/test'
require_relative 'number_cruncher.rb'

include Rack::Test::Methods

def app
  Sinatra::Application
end

Now, we’ll write a spec to test some of the features described above. Add the following code to the bottom of ‘test.rb’:

describe "Number Cruncher" do

  it "should return the factors of 6" do
    6.factors.must_equal [1,2,3,6]
  end

  it "should say that 2 is prime" do
    assert 2.prime?
  end

  it "should say that 10 is not prime" do
    refute 10.prime?
  end

  it "should return json" do
    get '/6'
    last_response.headers['Content-Type'].must_equal 'application/json;charset=utf-8'
  end 

  it "should return the correct info about 6 as json" do
    get '/6'
    six_info = { number: 6, factors: 6.factors, odd: 6.odd?, even: 6.even?, prime: 6.prime? }
    six_info.to_json.must_equal last_response.body
  end

end

The first test is for the factors method that I plan to add to the Integer class. We check to see that if the integer 6 calls the factors method followed by if the factors of 6 are returned as an array.

Next, test the prime? method that will also be added to the Integer class. First of all, check to see if 2 returns true and then if 10 returns false.

The last two tests are specific to the API. The fourth test checks to see if the app is returning JSON. This is done by performing a get request with ‘6’ as a parameter. The last_response.headers are checked for a content-type of ‘application/json;charset=utf-8’. In the last test, we check that the correct info is returned by comparing a JSON string to the last_response.body method.

To run our tests, enter the following in a terminal prompt:

$ ruby test.rb

This produces the following output:

# Running:
EEEEE
Finished in 0.008729s, 572.8333 runs/s, 0.0000 assertions/s. 
5 runs, 0 assertions, 0 failures, 5 errors, 0 skips

All five of our tests produce errors, which is expected since we haven’t written any code yet. Let’s see if we can get those tests passing.

Number Cruncher Code

Start by adding the factors and prime? methods to the Integer class. Add the following code to ‘number_cruncher.rb’:

class Integer
  def factors
    square_root = self**0.5
    (1..square_root).map{ |n| [n,self/n] if self/n*n == self }.compact.flatten.sort
  end

  def prime?
    self.factors.size == 2 ? true : false
  end
end

Try running the tests again.

$ ruby test.rb

This produces the following output:

5 runs, 3 assertions, 0 failures, 2 errors, 0 skips

That’s better – our first three tests pass, but the last two still fail, because we haven’t implemented the API yet. We’ll use Sinatra to do this. This involves requiring the sinatra and json gems, so add the following to ‘number_cruncher.rb’:

require 'sinatra'
require 'json'

We will also need a route that people can use to acess the API. This is a simple GET request that accepts the a number as a parameter. It will return information about the number:

get '/:number' do
  content_type :json
  number = params[:number].to_i
  { number: number, factors: number.factors, odd: number.odd?, even: number.even?, prime: number.prime? }.to_json
end

First, change the content type to JSON using the handy content_type helper method that Sinatra provides. Then grab the number from the params hash and convert it to an integer (everything entered in the route is a string). Then create a hash of information about the number including its factors. The hash includes if the number is odd or even and whether it is prime using our new Integer methods. The hash is converted to JSON using the to_json method provided by the json gem. Because this is the last line of the route handler, it will be returned automatically.

Let’s run the tests again:

$ ruby test.rb

This time, we get the following output:

5 runs, 5 assertions, 0 failures, 0 errors, 0 skips

That’s great, our code works! We can run it through it’s paces by checking out some facts about the fifth Fermat number using curl. First, start a server:

$ ruby number_cruncher.rb

Now, open another terminal window and enter:

$ curl http://localhost:4567/4294967297

This gives us the following information, which backs up what Euler found out in 1732:

{"number":4294967297,"factors":[1,641,6700417,4294967297],"odd":true,"even":false,"prime":false}

Version Control with Git

It’s always useful to keep your code under version control. Git is virtually ubiquitous in the Ruby community and for good reason (it’s awesome!). First, make sure that Git is installed on your system, then run the following commands:

$git init

This should give you a message similar to the one below:

Initialized empty Git repository in /path/to/number cruncher/.git/

Use the add . command to add all the files in the directory:.

git add .
git commit -m 'initial commit'

You’ll get a message similar to the one below, detailing which files have been changed:

[master (root-commit) 6674d0c] initial commit
 2 files changed, 56 insertions(+)
 create mode 100644 number_cruncher.rb
 create mode 100644 test.rb

Since we are using Git for our version control, it makes sense to host our code on GitHub. If you haven’t created an account, then head over there and set one up. There is a useful gem called ‘hub’ for interacting with GitHub via the terminal. To install it just enter the following to a terminal:

$ gem install hub

Now you should be able to create a new repository for you code using the following command:

$ hub create number_cruncher

This should give you the following output:

Updating origin
created repository: daz4126/number_cruncher

Push the code to our new GitHub repository, which is done like so:

$ git push origin master

If all goes to plan, you’ll see something similar to the following output:

Counting objects: 4, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (4/4), done.
Writing objects: 100% (4/4), 914 bytes, done.
Total 4 (delta 0), reused 0 (delta 0)
To git@github.com:daz4126/number_cruncher.git
 * [new branch]      master -> master

Bundler

Next we’re using Bundler to manage our dependencies. This involves creating a file called ‘Gemfile’ that contains the following code:

source 'https://rubygems.org'
gem "sinatra"
gem "json"
gem "rack-test", :group => :test

To use Bundler, use the following command in the terminal:

$ bundle install

You should see the following information as Bundler gets and installs all the necessary gems:

Fetching gem metadata from https://rubygems.org/...........
Fetching gem metadata from https://rubygems.org/..
Resolving dependencies...
Using json (1.8.0) 
Using rack (1.5.2) 
Using rack-protection (1.5.0) 
Using rack-test (0.6.2) 
Using tilt (1.4.1) 
Using sinatra (1.4.3) 
Using bundler (1.3.5) 
Your bundle is complete!
Use `bundle show [gemname]` to see where a bundled gem is installed.

Since we’ve made some changes to our code, we need to add and commit these to git before pushing the updated code to GitHub. This is easily achieved with the following three commands in the terminal:

$ git add .
$ git commit -m 'Added Gemfile and bundled gems'
$ git push origin master

Deploy to Heroku

Now it’s time to deploy our code to a live server using the Heroku service. For this, we will need to create a “rackup” file. Create a file called ‘config.ru’ that contains the following code:

require './number_cruncher'
run Sinatra::Application

To test that this works, run the command that Heroku will use in a terminal:

$ bundle exec rackup

This has the effect of starting a server, and you’ll see some output similar to that shown below:

[2013-08-29 13:27:36] INFO  WEBrick 1.3.1
[2013-08-29 13:27:36] INFO  ruby 2.0.0 (2013-06-27) [i686-linux]
[2013-08-29 13:27:36] INFO  WEBrick::HTTPServer#start: pid=4188 port=9292

Once again, we have made some changes to the code, so do the add, commit and push dance with Git:

$ git add .
$ git commit -m 'Added config.ru'
$ git push origin master

Now it’s time to create the app in Heroku. If you haven’t created an account, then head over there and get one. You also need to make sure that you have the Heroku toolbelt installed. Once that is done, enter the following command in a terminal:

$ heroku create

Git is used to deploy code to Heroku using push, like so:

$ git push heroku master

If all goes well, you’ll see something similar to the output below:

Creating number-cruncher... done, stack is cedar
http://number-cruncher.herokuapp.com/ | git@heroku.com:number-cruncher.git
Git remote heroku added

Make a note of the URL created for your app and then test it out by paying it a visit, with your favorite number as a parameter:

http://number-cruncher.herokuapp.com/10

Continuous Integration with Travis

Continuous Integration is the practice of running tests on one centralized repository of code. Travis provides this service by running tests on the code in a GitHub repository every time the code is pushed to GitHub. This means that you can always check on the status of your code. You can even configure it to deploy your code once a build is successful!

To get started, install the travis gem:

$ gem install travis

You also need to sign in to Travis using your GitHub account.

Travis works by running a rake task, so create a file called ‘Rakefile’ that contains the following code:

task(:default) { require_relative 'test' }

This basically tells it to run the ‘test.rb’ file when the Rakefile is run. You can test if this works by entering the following into a terminal:

$ rake

You’ll see the same output as when we run our tests:

Run options: --seed 58513
# Running tests:
.....
Finished tests in 0.092591s, 54.0009 tests/s, 54.0009 assertions/s.
5 tests, 5 assertions, 0 failures, 0 errors, 0 skips

You also need to add ‘rake’ to our Gemfile, edit it to look like the following:

source 'https://rubygems.org'
ruby '2.0.0'
gem "sinatra"
gem "json"

group :test do
  gem "rack-test"
  gem "rake"
end

You need to run bundle install again, since we’ve changed our Gemfile:

$ bundle install

Now, we are ready to set up Travis for our project. This is done by entering the following command in a terminal:

$ travis init ruby --rvm 2.0.0

This produces the following output:

repository not known to Travis CI (or no access?)
triggering sync: ........ done
.travis.yml file created!
daz4126/number_cruncher: enabled :)

A file called ‘.travis.yml’ will be created that contains information about our app. A build on Travis is initialized when you push to your Github account, so do that now:

$ git add .
$ git commit -m 'Created a Rakefile and set up Travis'
$ git push origin master

To check the status of the build, run the following command in a terminal:

$ travis show

Travis can take a while to run a build, so you might get a message like the following:

no build yet for daz4126/Sinatra-API

But be patient, make a cup of tea, then come back and try again:

$ travis show

Hopefully, this time you get confirmation that the build has passed successfully:

Job #1.1: Created a Rakefile and set up Travis
State:         passed
Type:          push
Branch:        master
Compare URL:   https://github.com/daz4126/number_cruncher/compare/4f55d5f9cd32...86c181d96f5d
Duration:      30 sec
Started:       2013-09-28 18:22:56
Finished:      2013-09-28 18:23:26
Allow Failure: false
Config:        rvm: 2.0.0

You’ll also get a nice email from the lovely folks at Travis to confirm the successful build.

That’s All Folks!

We’ve come to the end of this tutorial where we completed a full development cycle that can be repeated every time we want to add a new piece of code: Write tests, write code, commit changes to Git, push code to GitHub, test code using Travis, and deploy to Heroku. Rinse and repeat.

I hope you found it useful. I would love to hear any feedback about your own workflow in the comments below.

Frequently Asked Questions (FAQs) about Building Sinatra API using TDD, Heroku, and Continuous Integration with Travis

What is Sinatra and why should I use it for building APIs?

Sinatra is a lightweight and flexible web application library and domain-specific language that is written in Ruby. It is an excellent choice for building APIs due to its simplicity and minimalistic approach. Unlike other web frameworks, Sinatra does not follow the typical MVC (Model-View-Controller) pattern, which makes it more straightforward and faster for creating simple web applications or APIs. It provides just what you need to get your API up and running without any unnecessary complexities.

How does Test-Driven Development (TDD) benefit my Sinatra API development process?

Test-Driven Development (TDD) is a software development approach where you write a test before you write your code. In the context of Sinatra API development, TDD ensures that your API is working as expected and helps you catch any errors or bugs early in the development process. It encourages simpler designs and inspires confidence, as you know that your code is tested and works correctly.

What is Heroku and how does it help in deploying my Sinatra API?

Heroku is a cloud platform as a service (PaaS) that lets companies build, deliver, monitor, and scale applications. When it comes to deploying your Sinatra API, Heroku simplifies the process by handling the complexities of the deployment process. It provides a platform that is easy to use, scalable, and flexible, allowing you to focus on your application’s development rather than worrying about infrastructure management.

How does Continuous Integration with Travis work with Sinatra API and Heroku?

Continuous Integration (CI) is a development practice where developers integrate code into a shared repository frequently. Travis CI is a hosted continuous integration service used to build and test software projects hosted on GitHub. When you’re developing a Sinatra API and deploying on Heroku, Travis CI can automatically build and test your changes in your app whenever your code is pushed to GitHub. If your tests pass, Travis CI can automatically deploy your app to Heroku.

How can I ensure the quality of my Sinatra API?

Ensuring the quality of your Sinatra API involves writing clean, maintainable code, using TDD for robust testing, and implementing continuous integration for regular code checks. It’s also important to follow best practices for API development, such as proper error handling, consistent naming conventions, and effective logging.

What are the key differences between Sinatra and other Ruby frameworks like Rails?

Sinatra is a lightweight and flexible framework, making it a great choice for simple web applications or APIs. Unlike Rails, which follows the MVC pattern and comes with many built-in features, Sinatra is minimalistic and does not impose much structure, giving you more flexibility and control over your application.

How can I handle errors in my Sinatra API?

Sinatra provides several ways to handle errors in your API. You can use the “error” method to define custom error handlers, use the “halt” method to immediately stop a request and return a specific response, or use the “not_found” method to handle requests for undefined routes.

How can I scale my Sinatra API on Heroku?

Heroku provides several features to help you scale your Sinatra API. You can use the Heroku Dashboard to increase or decrease the number of dynos (lightweight containers that run your app), or use the Heroku Autoscaling feature to automatically scale your web dynos based on response time metrics.

How can I secure my Sinatra API?

Securing your Sinatra API involves several practices, including validating and sanitizing input data, implementing authentication and authorization, using HTTPS for secure communication, and regularly updating your dependencies to avoid known vulnerabilities.

How can I monitor the performance of my Sinatra API on Heroku?

Heroku provides several tools for monitoring your Sinatra API’s performance, including Heroku Metrics, which provides information about your app’s dyno load, response times, and throughput. You can also use add-ons like New Relic APM for more detailed performance monitoring and analysis.

Darren JonesDarren Jones
View Author

Darren loves building web apps and coding in JavaScript, Haskell and Ruby. He is the author of Learn to Code using JavaScript, JavaScript: Novice to Ninja and Jump Start Sinatra.He is also the creator of Nanny State, a tiny alternative to React. He can be found on Twitter @daz4126.

travis
Share this article
Read Next
Get the freshest news and resources for developers, designers and digital creators in your inbox each week