Skip to main content

81 Our First API

Episode 162 · December 20, 2016

We build a basic API and talk about the differences between a regular Rails controller and an API

APIs


Transcripts

What's up guys, this episode, we're going to build our very first API, and talk about all the little details that you will need to be concerned with when you're building out your own API's. First off, let's start by creating a new application called weather. What we'll do is we'll build a little bit of a weather service. So you might imagine that your iPhone has to hit the server to get the current temperature when it displays that, so we'll be building that server for recording and extracting the current temperature for these locations. So our application is going to require a couple things. They'll have a location model, every location will have a name. Ideally, you would also add geolocation to this, so that your location can be searched upon, so wherever you are currently at on your phone, you could search for the closest city or something like that and return the temperature for that, but for our case, we're going to keep this simple and start with just a name. Then we also need a model for the actual temperature and status of the location, so we'll just have like a recording model

rails g model Recording location:references temp:integer status

rake db:migrate

We can open this up, and maybe go into our db/seeds.rb, to set up some example data. So here we might say l = location, and l.recordings.create(temp: 32, status: "cloudy"). So maybe we also add some historical data in here, so maybe it was a little warmer before. Maybe it got a little colder, to 28 degrees, and went really cold to 22, and it was kind of alternating between rainy and cloudy, but then it became sunny and 22 degrees out.

db/seeds.rb

l = Location.create(name: "New York City")
l.recordings.create(temp: 32, status: "cloudy")
l.recordings.create(temp: 34, status: "rainy")
l.recordings.create(temp: 30, status: "rainy")
l.recordings.create(temp: 28, status: "cloudy")
l.recordings.create(temp: 22, status: "sunny")

We can have our data like this, obviously, you would want to record the actual time stamps for each of these days, or temperatures or whatever, if this is checked hourly or in a minute basis. You can go and also set the "Created at" on this, we're just going to create all of these at once, and we'll order them by id rather than "Created at", so that we can see them in order. We need to set a location.rb to has_many :recordings, then we can hop into our terminal, and rake db:seed to add those to our database, and if we load up our Rails console, we should be able to see Location.last.recordings.last, and that should give us that temperature of 22 and sunny, and it does. So that means that our database is all set up, so now we need to go into our application and start building our API. The way we're going to do this, is by going into our config/routes.rb, and we're going to add a namespace in here for the API, and the reason why we want to use a namespace here, is because we can then define our resources routes for locations, and underneath them, we can also say: resources :recordings, so that you could grab, say historical data or whatever, and that will separate those out from resources :locations down here, which might be for the browser to load up the html page for it instead. So our API can be designed in a separate folder, and that will serparate out the controllers, and the views for it, in case you're using jBuilder to render the JSON for the API.

This allows us to have those two separate sections of our routes that are specific to the API, and we can contain that all in the same Rails app, which can be nice. Now the other thing that I want to add here is another namespace though for version one, so that we can begin versioning our API. There are a lot of different ways that you can go about versioning. Stripe has a really interesting one where it records the current version of the API, and then it saves it to your count so that it automatically remembers that version, which is pretty neat. A more typical, simpler approach. The reason why you would want to version your API is that you may not control all of the code in one app, so when you deploy your Rails app, your mobile app might be separate, and if it hasn't been updated at the exact same second as your Rails app, then your users will probably see breaking stuff. So that might mean that logins don't work, or they can't pull the weather or any of that stuff, and that would be really bad. So you need to do it in versions, so that you can say: Ok, this version has this functionality, the mobile app can implement that, but when we want to change it, we roll out a new version, and then once that is rolled out, then our mobile app can opt in using the new version. Now this is important for your own kind of separate teams, so maybe your mobile app is a separate development team than your Rails app, you can deploy those independently, but this also becomes incredibly valuable when you have other random people building against your API. So if you have a public API that anyone can use, you don't want to go break their code, so you need to version it, so that they can choose to upgrade versions, and you can communicate to them while we rolled out version three, so we're turning off version one at this date, so make sure that you upgrade if you no longer want to support it. Versioning is going to be important, we'll talk about that more in the future. Stripe has some really really interesting approaches to that, which I want to talk about when we get around to talking about versioning more, but this is all we really need to do to create our routes. So let's go into builing our resources.

Right before we do that, let's take a look at rake routes, this is going to show us that our URL's api/v1/locations/:location_id/recording(.:format), and the namespace actually creates folders for our controllers and our views for these in the API. So that means that our controllers are going to be a little bit different than normal, we're going to create a directory inside of app/controllers, and we'll call it app/controllers/api, and then we'll make a directory called app/controllers/api/v1/locations_controller.rb, and this will be the file that we will edit. So let me open that up, and we'll have our API v1 locations controller. This will just inherit from application controller as normal, and we will have our show action, this would request for a specific locations temperature, so we'll use that, we will have before_action :set_location, just like you normally would expect in a regular rails controller. We'll have set_location, this is going to grab it by the id, so we'll have @location = location.find(params[:id]), and then really all we need to do is build our view for this. So we need to make a file called

app/views/api/v1/locations/show.json.jbuilder

which is built into rails, and it gives you the ability to say json. attribute name that you want, so you could say

json.id @location.id 
json.name @location.name 

and then you can have anything you want, like a block, and this could be the current recording temperature, so we might say here

json.current do 
    json.temp @location.recordings.last.temp 
    json.status @location.recordings.last.status 
end 

This is a way for you to kind of define your JSON output visually in a structure, which is kind of nice, and this structure allows us to actually put our JSON formatting into the views folder, which can kind of organize this a little bit nicer for us in some cases. Now you could also do this and just say render json and create a hash in here and return the exact same format of the hash, so say:

def show 
    render json: {
        id: @location.id,
        name: @location.name
    }

where you could replace the format with just a regular old ruby hash. The nice part about this is that rails knows that we are looking for the JSON type of file format when we request this, so we can just define it in our views folder, and it will figure out how to render that for us. Now let's start our rails server, and try this out in the browser. Now the reason why this works nicely in our browser is because we don't actually have any authentication yet, so we're not passing in a token, we can just load it up in our browser and see our JSON as we created it.

As you can see, jBuilder effectively just built a hash, and then it's in json format, and our browser can then parse that out into an object in JavaScript. What's really neat about this is that now that we have this URL, we can go into anything we want, we could go into Python or Swift or Android or ruby or JavaScript, and we can hit that URL, and we can grab that data. So even if we were in bash, and we wanted to run curl, we can grab the data in a curl request, and then parse that string response as JSON, and then our bash code could even have access to that. So you can build your own stuff to consume this API now, and if it ever changed, maybe the format changes, so we don't have current anymore or something. Then you could update the version, and that's really the only difference that you would need to change. And the other cool thing about this, is if you notice, this code is nothing special to API's, the only thing that we did, was we introduced a version, but the rest of this code is very very much your standard Rails controller code, the output of the show action is in JSON jBuilder format, which is fairly common, if you happen to write JavaScript, to make an AJAX request to load up some data dynamically rather than HTML. So this is almost exactly what you would normally write, and I wanted to point that out, because there's a lot of confusion between how do I write regular Rails code, I need to write an API, and it seems like it's a different thing. It's really not a different thing, it just has chosen to use a little bit different authentication, which we haven't talked about yet, it uses versioning, and it uses JSON, and this is really not a whole lot more that's specific to API's they are really pretty much the same, as building your normal Rails app.

With that said, there are important details that we do need to take into account to build an easy to work with API. For example, if we were to remove .json as the format, we're going to ge an error "unknown format". The reason for that is because, well, we didn't specify the extention in the URL. If our API is always going to return JSON, or is the default, we might as well force our controller to make sure that it uses JSON all the time.

One of the ways we can do that, is we can build our own API controller

app/controllers/api_controller.rb

class ApiController < ApplicationController 
    before_action :set_default_format 

    private 

        def set_default_format 
            request.format = :json 
        end
end 

And override whatever is in the url. That way, we always have that format, and we can say that ApiController cotroller is the class that we inherit from now from all of our API controllers.

That way is is kind of acting as the parent, and enforcing all these kind of defaults upon all of your API controllers. That is nice, because now we can request this without .json, and we're going to get the expected result every time. The trouble with that is course, if you wanted to support other formats, like XML, it is not going to work, but you can just simply tweak this code to say: Well if it's XML, leave it, if it's anything other than XML, leave it, if it's anything other than XML, force it to JSON, and that will work.

That's it for this episode, we are going to be diving into authentication, formatting your JSON in a better format than just kind of arbitrary formats, we'll talk about more versioning, error handling, and a lot more in the next episode. So I will talk to you then.

Transcript written by Miguel

Discussion