Write a post

How to Configure Your First Rails REST API

Published Jul 15, 2016Last updated Jan 18, 2017
How to Configure Your First Rails REST API

Creating Your First Rails REST API

Most services these days who offer an API do so through a RESTful interface. Any time an API client wants to interact with an API (e.g. Stripe, Twitter, Twilio), it makes a REST request to the server. This request is made using one of the standard HTTP verbs (GET, POST, PATCH, PUT, DELETE, etc.) to a particular route (e.g. https://my-app.com/appointments/:id), and the server, specifically its controllers, issues a response.

Normally on a Rails application, the controllers respond to one of those RESTful requests with a view (an .html.erb template, say). This is fine in the standard rails app MVC context, in which your views are directly tied to your model and controller logic in the same app. In an API app, however, the client is no longer in the same app, but instead in a front-end web application (Angular, EmberJS, etc.), a mobile phone app, or even another server that needs to make a request to your server to obtain or modify data.

For this to work, you need to configure your controllers to respond to a request using a standard data format such as JSON or XML. APIs most commonly use JSON these days, but XML has its advantages depending on your platform.

Respond to JSON

In this example we’ll respond to client requests using JSON. The first step is to replace your render/redirect functions with render: json. Compared to outputting HTML, this is structured data available in a universal format.

Normally, a method in your controller may look something like this:

class PostsController < ApplicationController
  ...
    def index
    	@posts = Post.all
    end
    ...
end

This would render an index.html.erb template and pass along the @posts instance variable. In our API, we're going to instead respond with JSON. This then looks like the following:

class PostsController < ApplicationController 
  ...
    def index
    	posts = Post.all
        render json: {status: 'SUCCESS', message: 'Loaded all posts', data: posts}, status: :ok
    end 
    ...
end

Notice that the posts variable does not have an @ in front of it, because we're not passing this variable to the view, but directly into our JSON rendering. We can put whatever JSON we want in the curly braces, but it is a standard practice to indicate a status, a message, and the data itself. The last argument is the HTTP status code (in this case, :ok, which yields 200). Note that we are putting an ActiveRecord object (posts) into the JSON rendering directly without serializing it! Later versions of Rails happen to be clever enough to automatically serialize for us.

If it’s data from one of your own classes and not ActiveRecord, you’re going to have to turn the object into a Ruby hash in your class.

What is the process of “opening up an API” to the world?

"Opening up an API" is a commonly used phrase but kind of misleading. Your API routes are still routes on your server just like any other routes. The difference is, you’re advertising that it’s open for use beyond just your company or project, and (hopefully) are implementing an authentication/authorization framework for these users.

All that said, it’s best practice to generate a set of routes specifically for your API. Why? You’ll probably want to scope out different sets of behaviors and privileges on your API users, and have version control for your API.

Create API Routes

#config.rb
...
namespace :api do 
  namespace :v1 do 
    	resources :posts
    end 
end 
...

The nested namespace blocks will automatically form a /api/v1 path preceding the resources that it contains. In this case the path to the controller action above becomes GET /api/v1/posts

Turn off forgery protection

You may notice that when you generate a new Rails app you see a method that looks like this:

#app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
  protect_from_forgery with: :exception
    ...
end

This protect_from_forgery method is to prevent cookie attacks. In an API, this is less relevant because you are not utilizing cookies and Javascript; and each session only lasts as long as the request takes to complete.

Thus, we create a separate API controller that's only for API routes:

#app/controllers/api_controller.rb
class ApiController < ApplicationController 
  skip_before_action :verify_authenticity_token
end

The skip filter above makes sure that the Rails server isn't left searching for the token for forgery protection. For security reasons however, ONLY ALLOW API CONTROLLERS TO INHERIT FROM THIS CONTROLLER. All other controllers that render traditional .html.erb templates from your app/views folder should inherit from your ApplicationController class.

Replace Devise routes and classes

For most Rails apps, Devise is the authentication framework of choice. Devise automatically creates a set of routes for authentication. This is great for a monolithic app, but it’s not suitable for an API, because again, we’re not utilizing cookies, and don’t really sign in and sign out people in the way that it would typically work with a web browser.

So what do we do? Use tokens! I normally prefer to use JWTs, but for now we’ll demonstrate the use of simple tokens to authenticate users.

Token-based authentication

Simple tokens work as an authentication method by representing a user who is trying to log in. They are typically a better choice than Basic Auth (simply using username and password) because it gives extra flexibility for the server to provide or revoke access, without requiring changes to the username/password. Any time access controls need to be changed, the token can simply be changed instead. So finally, how does the authentication work?

  1. Simply, a user has a unique token stored in the server's database.

  2. The token is presented typically in the header of the HTTP request, and the server reads it.

  3. The server tries to match the token with a user in the database. If it matches, the user is authenticated. If not, it sends the user a 401: Unauthorized HTTP code and doesn't allow the controller action to proceed.

Create tokens

For simple tokens, I prefer to use the has_secure_token gem. Follow the installation instructions to generate 24 character tokens automatically every time a new record in your User model is created. After running the migration, you'll notice that there is a :token attribute on your User model.

Use token in client request

Now that you have the token, you should place it in the header of your HTTP request from your client. The client is the web app, mobile app, or any other consumer of your API who is making a request to your server. Here's a sample request from the client, written in CURL notation:

curl -X GET -H "X-USER-TOKEN: 07906c47276da7c016c8b8g3" -H "Content-Type: application/json" "https://your-domain.com/api/v1/posts"

The X-USER-TOKEN parameter is the key of our token. You can name this what you want, but I've put in an X- prefix to indicate that it is a non-standard HTTP header. The Content-Type declaration is also important for telling the server that you're using JSON is the body of your response (as opposed to XML, etc.).

Matching the user and token for authentication

Next we create a method in our ApiController that attempts to match the token to a user, or otherwise unauthorizes the request.

#app/controllers/api_controller.rb
class ApiController < ApplicationController 
  ...
  private
    def authenticate_user
      user_token = request.headers['X-USER-TOKEN']
      if user_token
        @user = User.find_by_token(user_token)
        #Unauthorize if a user object is not returned
        if @user.nil?
          return unauthorize
        end
      else
        return unauthorize
      end
    end

    def unauthorize
      head status: :unauthorized
      return false
    end
end

As you can see, we extract the token from the header, and query the user's table to see if a user, who contains the submitted token, exists. If either a user does not exist or a token is not provided, it runs the unauthorize method. If you're not familiar with the head method, it's a simple method provided by Rails to simply send an HTTP status code (in this case 401: unauthorized), without giving a full bodied JSON response (we could've done a render: json method like at the top of this article if we wanted to).

Putting in the token authentication feature

The last step is to make sure that the actions of every controller that inherits from the ApiController actually authenticates! We do this by putting a before filter at the top of the ApiController:

#app/controllers/api_controller.rb
class ApiController < ApplicationController 
  before_action :authenticate_user
    ...
end

CORS

Unlike in a regular app; where the requests are taking place on the same domain as the server; it’s possible that your users, your mobile phones, etc., are all coming from different domains (origins). To allow for this behavior to happen, you need to install the Rack CORS gem and follow the instructions there.

And just like that, Voila! You’re set up for a Rails API!

Discover and read more posts from Kristoph Matthews
get started
Enjoy this post?

Leave a like and comment for Kristoph

Localizing time in a Traditional Rails app with Moment.js
How To Build a Blog using Froala-WYSIWYG
Export Records to CSV Files using Rails