API
We prefer to write APIs with rails-api over other libraries, such as grape because:
- rails-api comes with Rails since version 5.0.
- It’s easier to learn and maintain - the interface is the same as Rails.
- Good integration with api-documentation tools, such as swagger.
- Routing is defined in
config/routes.rb
file, instead of inside each endpoint like other libraries do. - API endpoints are tested as regular Rails controllers.
- Possibility to reuse existing Rails helper for authentication and authorization.
Defining routes
- Place your API into a separate namespace.
- Use resources nesting for scoped resources.
example
namespace :api do
resources :users, only: [:index, :show] do
resources :posts, only: [:index, :show, :create]
end
end
Writing endpoints
- Just like writing regular Rails controllers, API controllers should be RESTful.
- Have base api controller, from which other controllers will inherit.
- Define common
before_actions
, methods, helpers in base api controller.
Example
# app/controllers/api/application_controller.rb
module Api
class ApplicationController < ActionController::API
before_action :authenticate_user!
end
end
# app/controllers/api/users_controller.rb
module Api
class UsersController < ApplicationController
def index
render json: User.all, serializer: UserSerializer
end
def show
# ...
end
end
end
Serializing objects
- Prefer to use ActiveModelSerializers for objects serialization.
- Place serializes used only for API into api namespace.
Example
module Api
class UserSerializer < ActiveModel::Serializer
attributes :first_name, :last_name
has_many :posts, serializer: PostSerializer
end
end
Testing
- Api controllers are tested in the same way as regular controllers.
- Consider adding helper methods for simpler json comparison.
Example
# spec/support/api_helper.rb
module ApiHelper
def json_body
JSON.parse(body)
end
end
# spec/controllers/api/users_controller_spec.rb
require 'rails_helper'
describe Api::UsersController do
include ApiHelper
describe '#index' do
before { create(:user) }
it 'successfully renders users' do
get :index
expect(response).to be_success
end
it 'includes user in body' do
get :index
expect(json_body).to eq([{ 'first_name' => 'John', 'last_name' => 'Doe' }])
end
end
end
Versioning
- Do not version APIs, depend on client versions instead.
- Maintain a backward compatibility policy.
- Branch code depending on a backward compatibility policy.
- When adding a new functionality create a relevant method in a backward compatibility policy and use that method when checking which functionality (new or deprecated) should be used.
- When checking which functionality (new or deprecated) should be used, place the new functionality inside
else
block. - Remove a deprecated functionality once it takes too much effort to maintain it.
- When removing a deprecated functionality remove relevant
if
blocks and backward compatibility policy’s methods. - Whenever a deprecated functionality is removed, the lowest supported version of the application should be incremented.
Example policy:
class BackwardsCompatibilityPolicy
attr_reader :version, :platform
def initialize(version, platform)
self.version = version
self.platform = platform
end
def lesser_than_version?(acceptable_version)
(BackwardsCompatibilityPolicy.format_version(version) <=>
BackwardsCompatibilityPolicy.format_version(acceptable_version)) < 0
end
# Method name should indicate the feature that is being deprecated
def deprecated_feature_name?
lesser_than_version?('3.1.1')
end
def self.format_version(version)
version.to_s.split('.').map(&:to_i)
end
private
attr_writer :version, :platform
end
Tips
- Prefer postman over curl for debugging.
Documenting
When the documentation for the API is needed we use Swagger. Swagger allows creating a specification for all of the API resources and operations in a human and machine readable format. Swagger provides three core components: Swagger UI, Swagger Codegen, and Swagger Editor.
Swagger Editor and Specification
Swagger Editor is a tool which allows a developer to create a detailed API specification. Swagger specification describes API endpoints, parameters, responses, authentication schemes, etc. The created specification can be used by other tools (for example this specification is used when creating a visual documentation with Swagger UI or when creating a server stub with Swagger Codegen). When creating Rails API we use swagger-docs which allows defining specification details directly inside controllers using Ruby DSL. However, if the API that is being developed doesn’t need to be exposed Swagger is used only if a project requires doing so. If the API is being developed by the external team we ask them to use Swagger and create a specification file for us.
Swagger UI
Swagger UI is a tool which consumes a swagger specification file and produces an easy to read documentation. This is particularly useful when working with external teams because the created visual documentation allows developers to visualize and interact with the API without having to know any of the implementation logic.
Swagger Codegen
Swagger Codegen is a tool which generates client SDKs and server stubs out of swagger specification file. Having a stubbed server is very useful when building client applications that depend on APIs which are developed by external teams because it allows us to build client applications without any knowledge of the server side implementation. Usually when we need a server stub we generate either a Sinatra server or a Node.js server. When working with external team and the mismatch between the swagger specification and the actual API is noticed we inform the external team about the issue and ask them to update the specification as soon as possible.