Rails API – Quick note

1. Setting up Rails 5

rails new api_app_name --api -d postgresql -T
cd api_app_name
bundle install
rails db:setup

Note:

--api : generate API app only

-d postgresql : using postgresql database

-T : to skip generating Test because we are going to use RSpec for testing.

2. Using RSpec for testing

group :development, :test do

    # Use RSpec for specs
    gem 'rspec-rails', '3.1.0'

    # Use Factory Girl for generating random test data
    gem 'factory_girl_rails'
end
bundle
rails g rspec:install

3. Building your API

rails g scaffold user name email
rails db:migrate
# run the default server on port 3000
rails s

4. Serializing API ouput

gem 'active_model_serializers'
bundle
rails g serializer user
# app/serializers/user_serializer.rb
class UserSerializer < ActiveModel::Serializer
    attributes :id, :name, :email
end

class ApplicationController < ActionController::API
    include ActionController::Serialization

    # ...
end

5. Enabling CORS (Cross-Origin Resource Sharing)

gem 'rack-cors'
bundle
# config/application.rb
module YourApp
    class Application < Rails::Application

    # ...

    config.middleware.insert_before 0, "Rack::Cors" do
        allow do
        origins '*'
        resource '*', :headers => :any, :methods => [:get, :post, :options]
        end
    end

    end
end

6. Versioning your API

# GET http://api.mysite.com/v1/users/

app/controllers/
.
|-- api
|   |-- v1
|       |-- api_controller.rb
|       |-- users_controller.rb
|-- application_controller.rb
# app/controllers/api/v1/api_controller.rb
module Api::V1
    class ApiController < ApplicationController
    # Generic API stuff here
    end
end
# app/controllers/api/v1/users_controller.rb
module Api::V1
    class UsersController < ApiController

    # GET /v1/users
    def index
        render json: User.all
    end

    end
end
# config/routes.rb
constraints subdomain: 'api' do
    scope module: 'api' do
    namespace :v1 do
        resources :users
    end
    end
end

7. Rate Limiting and Throttling

gem 'rack-attack'
bundle
# config/application.rb
module YourApp
    class Application < Rails::Application
    # ...
    config.middleware.use Rack::Attack
    end
end
# config/initializers/rack_attack.rb
class Rack::Attack

    # `Rack::Attack` is configured to use the `Rails.cache` value by default,
    # but you can override that by setting the `Rack::Attack.cache.store` value
    Rack::Attack.cache.store = ActiveSupport::Cache::MemoryStore.new

    # Allow all local traffic
    whitelist('allow-localhost') do |req|
    '127.0.0.1' == req.ip || '::1' == req.ip
    end

    # Allow an IP address to make 5 requests every 5 seconds
    throttle('req/ip', limit: 5, period: 5) do |req|
    req.ip
    end

    # Send the following response to throttled clients
    self.throttled_response = ->(env) {
    retry_after = (env['rack.attack.match_data'] || {})[:period]
    [
        429,
        {'Content-Type' => 'application/json', 'Retry-After' => retry_after.to_s},
        [{error: "Throttle limit reached. Retry later."}.to_json]
    ]
    }
end

8. Authenticating your API

Authorization: Token token="WCZZYjnOQFUYfJIN2ShH1iD24UHo58A6TI"
rails g migration AddApiKeyToUsers api_key:string

class User < ActiveRecord::Base

    # Assign an API key on create
    before_create do |user|
    user.api_key = user.generate_api_key
    end

    # Generate a unique API key
    def generate_api_key
    loop do
        token = SecureRandom.base64.tr('+/=', 'Qrt')
        break token unless User.exists?(api_key: token)
    end
    end
end
#  using the built in authenticate_or_request_with_http_token Rails method.
class ApplicationController < ActionController::Base
    include ActionController::HttpAuthentication::Token::ControllerMethods

    # Add a before_action to authenticate all requests.
    # Move this to subclassed controllers if you only
    # want to authenticate certain methods.
    before_action :authenticate

    protected

    # Authenticate the user with token based authentication
    def authenticate
        authenticate_token || render_unauthorized
    end

    def authenticate_token
        authenticate_with_http_token do |token, options|
            @current_user = User.find_by(api_key: token)
        end
    end

    def render_unauthorized(realm = "Application")
        self.headers["WWW-Authenticate"] = %(Token realm="#{realm.gsub(/"/, "")}")
        render json: 'Bad credentials', status: :unauthorized
    end
end
# test
curl -H "Authorization: Token token=PsmmvKBqQDOaWwEsPpOCYMsy" http://localhost:3000/users

Share:

Leave a Reply

Your email address will not be published. Required fields are marked *