This log covers how to build and test web APIs with Rails. The foundation of a REST API is responses are generated base on a set of comments which performed on certain things.

It is good to keep API under its own subdomain which allows load balancing traffic at the DNS level. It is also good practice to keep web controllers separate from API ones.

constraints subdomain: 'api' do
  namespace :api, path: '/' do
    resource :users    # http://api.yourdomain.com/users
    resource :courses  # http://api.yourdomain.com/courses
  end
end

The web API controllers have also need to be part of the API module (See code snippet below),

# Inside app/controllers/api/users_controller.rb
module API
  class UsersController < ApplicationController
  end
end

# Inside config/initializers/inflections.rb
ActiveSupport::Inflector.inflections(:en) do |inflect|
  inflect.acronym 'API'
end

A resources is a conceptual mapping to a set of entities, not the entity that corresponds to the mapping at any particular point in time.

In short, any information (none) can be named can be a resource.

API

Rails use path segmented expansion which separates arguments in the URI using a slash.

/users
/users/:id
/usres/:id/messages
/users/:id/messages/:id

Content Negotiation is the process in which client and server determine the best representation for a response when many are available. Rails allows switching formats by adding an extension to the URI (It is from Rails only, NOT a standard).

The write way to do is using the accept header to request a media type (Rails support 21 media types by default, Mine::SET)

class UsersController < ApplicationController
  def index
    users = Users.all
    respond_to do |format|
      format.json { render json: users, status: 200 }
    end
  end
end

Version

API Version helps precent major changes from breaking existing clients, which means the new feature does not affect old clients.

Versioning Using the URI

Namespaces help isolate controllers from different versions.

namespace :v1 do
  resources :users  # http://api.yourdomain.com/v1/usres
end
namespace :v2 do
  resources :users  # http://api.yourdomain.com/v2/usres
end

Versioning Using the ACCEPT Header

application/json
application/xml

Use API version as part of the mime type,

application/vnd.apocalypse[.version]+json
  • application, application-specific payload
  • vnd.apocalypse, vendor-specific media type
  • [.version], API version
  • +json, JSON response format
scope defaults: { format: 'json' } do
  scope module: :v1, constraints: ApiVersion.new('v1') do
    responses :users
  end
  # `true` defines default version
  scope module: :v2, constraints: ApiVersion.new('v2', true) do
    responses :users
  end
end

Authentication

It prevent unauthorized access to protected resources. Realms allows resources to be partitioned into different protected spaces.

Using Basic Auth

Credentials must be provided on HTTP requests using the Authorization header. Credentials for Basic Auth (which is in HTTP Spec) are expected to be Base64 encoded. It just like authenticate a user via web except the authorization is passed via HTTP header.

It is the simplest way to implement API authentication, but it is not secure and can be easily decode (which means NEVER use it).

GET /users HTTP/1.1
Host: localhost:3000
Authorization: Basic XXXXXXXXXXX
class UsersController < ApplicationController
  before_action :authenticate
  def index
    usres = User.all
    render json: users, status: 200
  end

  protected
    def authenticate
      authenticate_or_request_with_http_basic do |username, password|
        User.authenticate(username, password)
      end
    end
end

Using Token

API clients user a token identifier for making authenticated HTTP requests via HTTP Authorization header. Compare to Basic Auth,

  • The tokens can be easily recreated without affecting users’ account and password
  • More security
  • Multiple tokens for each user with different clients
  • Greater control for each token with different access rules
class UsersController < ApplicationController
  before_action :authenticate
  def index
    usres = User.all
    render json: users, status: 200
  end

  protected
    def authenticate
      authenticate_or_request_with_http_token do |token, options|
        User.find_by(auth_token: token)
      end
    end
end

HTTP Methods with API

GET

  • Safe, it does not take any action other than retrieval
  • Idempotent, it does not generate any side-effects

POST

Use to create resources on the server. It is neither safe or idempotent.

  • 201 - Created should be the response code
  • Response body should contain a representation of the new resource
  • Location header should contain the location of the new resource

Put & Patch

Use to update for existing resources. PATCH for partial updates.