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