Key Takeaways
- Utilize Test-Driven Development (TDD) with MiniTest to ensure your Sinatra API functions correctly before deployment, enhancing reliability and reducing bugs.
- Manage project dependencies efficiently using Bundler, ensuring consistent environments across different development stages and simplifying updates.
- Employ Git for version control and GitHub for hosting the codebase, facilitating collaboration and tracking changes effectively.
- Deploy your Sinatra API to Heroku for easy scaling and maintenance, leveraging Heroku’s robust ecosystem for seamless application hosting.
- Integrate Continuous Integration practices with Travis CI to automatically build, test, and deploy your code, ensuring high code quality and streamlining the development process.
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 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.