Create a Weather App With Sinatra and AngularJS - Part 1

Ruby is an awesome language for getting things done in a simple, to-the-point sort of way. So as one of my side projects I’ve decided to make a simple weather app (mobile web app) to play with a few APIs and stretch my skills. Coming from a PHP background and having been working mostly on backend technologies for the past year there was a lot I needed to learn to get this working. Follow along and I’ll walk you through the steps of how to create a weather app using AngularJS as a front end that’ll grab data from a Sinatra API.

Part 1: Setting up your environment (Hello World!)

This is the first in a series of posts on creating a RESTful API and API client using Ruby and JavaScript. Part 1 of the series will take you through the steps you need to get a simple API written in Sinatra to respond with ‘Hello World!’.

Choosing the right tools

Before you start any project you need to choose the right tools for the job. To do that you have to start off thinking about your needs. This is what we’ll need in this project and why:

SinatraSinatra is a DSL (domain-specific language) that lets you create web applications in Ruby. There are two main reasons I chose Sinatra. First, despite knowing PHP inside and out I wanted to do this in Ruby since I knew I could do it quicker and with less code to maintain. The other question that will come up is “why didn’t you use Rails”? Rails is too powerful for what we need in this case. We only need a couple of routes and a few simple functions to get the weather so there’s no reason to run a full blown Rails app when we can contain our entire API to a couple of script files. Sinatra is a very light and easy framework to work with.

Angular JS – We’ll be using Angular for the front end. Because our app is going to be focused on mobile users we want it to be quick and responsive. If we can create a very small front end then we can have the app download once and from then on whenever an API call is made the client will only need to download a small bit of JSON data. Using Angular means we won’t have to deal with page reloads and downloading new markup and content each time. It’ll be quick, responsive, and even work offline.

The APIs we’ll be using are IPinfo.io, Forecast.io, and the Google Maps API. We’ll be using these for Geolocation and to grab the weather.

Set up your environment

Before we begin let’s set up our environment. We’ll be working with a codebase for our API and one for our front end client. All of this should also be in version control. I use Git for version control. So first let’s set up our directory structure. On my local machine (running OS X 10.9) I put all my development work in the ~/Sites/ folder. Create a directory structure that looks like this:

1
2
3
4
5
6
7
~/Sites/
    |
    - weatherapp/
        |
        - api/
        - client/
        - design/

You do this by opening the terminal and running:

1
2
3
cd ~/Sites # replace '~/sites' with whatever folder you like putting web projects in
mkdir weather app && cd $_ # creates a 'weatherapp' folder and puts you into it
mkdir {api,client,design} # will create all three folders in one shot

Setting Up Version Control

I’m not going to walk you through setting up the Git repositories here but I recommend that each folder inside of weatherapp get its own git repository or become submodules of a main weatherapp.git repo. I started off with the weatherapp folder being a Git repository but soon found that I wanted the api and client folders to be maintained separately for logical reasons and for deployment purposes as well. Your own opinions and mileage may vary on this.

Create the API

Before we get into the fun front-end stuff we’re going to create our Sinatra API. Drop into your api folder and get your Ruby environment ready.

Prepare your Ruby environment

In this case I’m going to be using Ruby 2.0 and some Rubygems. I always create a new Gemset whenever I start a new Ruby project so I can be sure that the Gems I install and use will not conflict with Gems needed for other projects. I use RVM which is quite common. If you’re not using RVM to manage your Ruby versions and gemsets then you can still follow along but I can’t make any guarantees about the reliability of the code.

Manage Ruby Version and Gemset

First, let’s make sure we have the Ruby we want installed and then create the Gemset we need (a gemset, again, is just a set of Rubygems that are separated from the others on your system by a name you assign them – they’re just used so you can maintain different versions and collections of Rubygems on a per-project basis).

First, install Ruby 2.0 with

1
rvm install 2.0.0 && rvm use 2.0.0

That command will install Ruby 2.0 on your system. Once that’s finished we’ll create a new Gemset with

1
rvm gemset create weatherapp && rvm gemset use weatherapp

Great. Now we have the right Rubygemset installed and we’re using Ruby 2.0. Now, we’ll need to remember to tell RVM to use Ruby 2 and our “weatherapp” gemset each time we work on this project. What a pain. So instead of having to remember, let’s create a couple of files that will automatically switch us to using Ruby 2 and the correct gemset. Run the following commands in your terminal to get started:

1
2
touch .ruby-gemset # Creates a file named .ruby-gemset
touch .ruby-version # Create a file named .ruby-version

These two files tell whichever program you use to manage multiple Ruby versions (RVM in our case) which version of Ruby to use when working in this folder and which gemset to use.

In order to know what to put in our .ruby-version file we need to know the exact version of Ruby we have installed. The best, and most accurate, way to do this when using RVM is to run rvm list. This will give you a list of installed Rubies and show which one you are currently using. It will look something like this:

1
2
3
4
5
6
7
8
 * ruby-1.9.3-p286 [ x86_64 ]
   ruby-1.9.3-p429 [ x86_64 ]
   ruby-2.0.0-p195 [ x86_64 ]
=> ruby-2.0.0-p247 [ x86_64 ]

# => - current
# =* - current && default
#  * - default

The key at the bottom shows you how to read the output. In this case my default Ruby would be ruby-1.9.3-p286 and the one I’m currently using is ruby-2.0.0-p247. If the command shows that you aren’t currently using a version of Ruby 2.0 or higher, then copy the ruby from the list and paste it into the command shown below. In my case, the command would be:

1
rvm use ruby-2.0.0-p247

The “-pXXX” is the patch level of the current Ruby. You don’t need to know what that means right now but what you do need to know is that multiple versions of the same Ruby can exist at the same time. You can have multiple Ruby 2.0.0’s installed each with a different patch level. Because of this, you’ll want to be as specific as possible when copying the Ruby version into your .ruby-version file. In my case I would open the file and on the first line paste in:

1
ruby-2.0.0-p247

Now save that file and any time you cd into that directory you’ll automatically be switched to using that Ruby. Make sure to enter the version of Ruby you have installed which is listed in the output of rvm list. Do not just blindly use the one I’m using. At this point, when you’re ready to run your app on a different server you’ll need to make sure that exact version of Ruby is installed. This allows us to avoid problems caused by small differences in the different Ruby versions. To do this, you’d simply copy the contents of your .ruby-version file and run rvm install [your version of ruby here] on the server and half the battle of making sure your development and production environments match up is taken care of.

Now we need to make sure that the app uses the same gemset no matter where it’s run. To do this you’ll need to open up the .ruby-gemset file we created earlier and type in weatherapp. Now save the file.

The line weatherapp corresponds to the name of the gemset we created earlier and are currently using. When you deploy your app to a server you’ll need to make sure you create a new gemset of the same name. To do this you’ll first need to switch over to the version of Ruby your app required (or install it if it doesn’t exist) then run rvm gemset create [gemset name]. If the Ruby version is installed on the server and the gemset already exists then your app will automatically use them when you deploy it.

Back on our local machine, the .ruby-gemset and .ruby-version files allow us to do the same thing. Now each time you cd into your project’s folder you’ll end up automatically being switched to the correct version of Ruby and the corresponding gemset.

Now that all the housekeeping is taken care of, let’s start some real coding.

Creating the Sinatra app

Our Sinatra app will connect to several API’s and return data from them. In order to create this API we need to be able to handle incoming requests to a web server, parsing JSON, and sending out requests to an API. To do this we’ll install three Rubygems for the app’s functionality: Sinatra, RestClient, and JSON. We’ll also install a couple that’ll allow us to work more easily: Thin and Rerun.

We’ll want to manage these gems in a way that makes it easy to set up the app in different environments like our server and our local machine. To do this we’ll first create a Gemfile. A Gemfile is file that lists all the dependencies your app requires. It allows you to track the Gems needed and their versions across multiple machines. It does for Rubygems what RVM’s .ruby-version and .ruby-gemset files do for creating a consistent Ruby environment. To create the file run touch Gemfile then open it and paste in the following code:

Gemfile
1
2
3
4
5
6
7
8
9
10
source 'http://rubygems.org'

gem 'json', '1.7.7'
gem 'rest-client', '1.6.7'
gem 'sinatra', '1.4.5'

group :development do
  gem 'rerun', '0.9.0'
  gem 'thin', '1.6.2'
end

Before we continue let’s go through what each line does. The first line (source 'http://rubygems.org') declares where we should look for the gem files we’re going to install. Then each line beginning with gem 'gem-name', 'version-number' specify what gem to install and what version of it to install. See the block that begins with group? That’s how we tell Bundler (the program that’s already installed with RVM) that the gems listed in that block are all related. In our case the block is for gems we only need to run on our local machine but won’t be needed when we deploy our app live. When we run the bundle command on our server we’ll tell it to ignore the :development block so we only get the gems we need in production.

Once you’ve saved the Gemfile in the root of your project you’ll want to install the gems. Run bundle install and all the gems listed in the Gemfile will be installed into the weatherapp gemset. Thin is a webserver we’ll be using to run our API locally and Rerun will allow us to make changes to our Ruby code and have the changes reflected immediately without having to stop and restart our server.

The main app file

Okay, now I promise that all the housekeeping is taken care of. What you’ll want to do now is create a file called app.rb in the root of your API folder. This will be the main file that runs our API. Open the file and paste in the following code:

app.rb
1
2
3
4
5
6
7
8
9
#
# WeatherAppp
# A Weather API
# v0.0.1
#

require 'sinatra'
require 'json'
require 'rest_client'

The first 5 lines are comments. If you’re not familiar with Ruby, comments begin with a hash # symbol. After that we have three require statements. These statements tell the Ruby interpreter that we’ll be using code in the Sinatra, JSON, and RestClient gems as part of our app. We can also include separate Ruby files we’ve created too and we will later on.

Below the require statements add the following:

app.rb (continued)
1
2
3
4
get '/' do
  content_type :json
  { message: 'Hello World!' }.to_json
end

The code above is the basis for what Sinatra is. What the get block is doing is saying “When there is an HTTP GET request to the root directory (whateverDomain.com/) set the content type to JSON and then respond with a message saying ‘Hello World!’”. You could change get to any HTTP verb like POST, PUT, DELETE, PATCH, or UPDATE but in our case we only need a simple GET request.

The .to_json at the end of the last line in the block converts the string to JSON so that API clients can read it.

Want to see it in action? If you’re on a Mac or Linux you’ll have everything you need already. If you’re on Windows you’ll need to install cURL or use your browser for the next part.

In your terminal, still in the API’s directory, run rerun 'ruby app.rb'. This will start the app. rerun is the gem we installed earlier that automatically reloads our app when it detects changes to its files. The 'ruby app.rb' part of the command tells Rerun that we want it to start and stop this command automatically. If we weren’t using Rerun we would have simply typed ruby app.rb and the Ruby interpreter would have run our app. The only difference when using Rerun is that we don’t need to manually start and stop the app each time we change the code.

So now that the app is running open a new terminal window or tab and run the command curl -i http://localhost:4567/. This command is using the cURL utility to fetch the contents of that URL (http://localhost:4567 is the host and port that Sinatra apps will run on locally). You should have gotten something like the following as a response:

app.rb (continued)
1
2
3
4
5
6
7
8
9
HTTP/1.1 200 OK
Content-Type: application/json
X-Cascade: pass
Content-Length: 25
X-Content-Type-Options: nosniff
Connection: keep-alive
Server: thin 1.6.2 codename Doc Brown

{"message":"Hello World!"}

That’s our API at work! It responded with what we told it to. Now, open up app.rb again and make some changes. First, I want you to change Hello World! to whatever your name is. Then, below the first get block, add the following code:

app.rb (Continued)
1
2
3
4
not_found do
  content_type :json
  halt 404, { error: 'URL not found' }.to_json
end

Your app should still be running and the changes should be reloaded because of Rerun. So now, in the same terminal window you used for the first cURL request, run curl -i http://localhost:4567/. You should get a response of “Hello {YOUR NAME HERE}”. That’s Rerun in action.

But what about that new block we added? What does that do exactly? I’m sure you could guess that it handles 404 errors but how? Well, not_found is a special method in Sinatra for running blocks of code whenever you don’t have a route set up for the URL that someone asks for. You could redirect them to the homepage or run any number of different tasks here but in our case what we’re doing is once again setting the content type to JSON (because we’re running an API) and then responding with an error message. The halt 404 stops execution of any request block and responds with whatever you put after it. In this case we’ve written 404 which is how we tell Sinatra to respond with an HTTP status of 404. Everything that comes after the comma is the response itself. We just wrote a simple ruby string and made sure it was converted to JSON in the response.

To see the 404 block in action, run curl -i http://localhost:4567/bad-url. You’ll get a reponse that looks like this:

app.rb (Continued)
1
2
3
4
5
6
7
8
9
HTTP/1.1 404 Not Found
Content-Type: application/json
X-Cascade: pass
Content-Length: 25
X-Content-Type-Options: nosniff
Connection: keep-alive
Server: thin 1.6.2 codename Doc Brown

{"error":"URL not found"}

To stop the server and end the Ruby program go to the terminal window that you started Rerun in and press Control + C at the same time. This will end the execution of anything running in that window.

Conclusion

At this point we have a very simple API written in Ruby with help from Sinatra that responds to a few URLs with JSON. In part 2 we’ll add on to the API and make it do a few things that are more useful. You’ll learn how to make external API calls, parse JSON, set some Sinatra settings, and learn about using before blocks in Sinatra to set headers specifically meant to help in the creation of APIs. And remember, if you’re looking for a great place to host your code online while you learn, Digital Ocean is perfect for hosting experimental code for cheap. They bill hourly and you can get a VPS up and running for just $5 a month.

Digital Ocean Affordable VPS hosting

Dev Guide, Web development

« Fix Heartbleed Bug (Ubuntu) Phonegap for iOS: The Definitive Guide to Using Custom Fonts »

Comments