Just to note that REST api means: Statelessness (no data between requests), Resource identification per request (update only one row, get could have more rows), Representational state transfer (returned representation JSON is enough to identify and manipulate row). REST enable cacheability so we can scale to large number of components.

Once API is exposed, you should not modify it, except for critical bugfixes. Use namespace

namespace :api, default: { format: :json } do
  namespace :v1 do
    resources :expenses, only: [:index, :create, :update, :destroy]

If you use jbuilder, than create folder app/views/api/v1/expenses for view and controller Api::V1::ExpensesController

JSONAPI Resources JR

jsonapi-resources give us implementation of JSONAPI

rails g jsonapi:resource Api::V1::Post
rails g jsonapi:controller Api::V1::Post

namespace :api do
  namespace :v1 do
    jsonapi_resources :posts

# config/initializers/jsonapi_resources.rb:
JSONAPI.configure do |config|
  # built in paginators are :none, :offset, :paged
  config.default_paginator = :paged
  config.default_page_size = 50
  config.maximum_page_size = 1000

  # Do this if you use UUID's instead of Integers for id's
  config.resource_key_type = :uuid

Each resource can be configured with:

  • attributes attribute
    • there are also id and type. Computed fields can use @model but also need to be defined with attribute
    • fetchable_fields all attributes are fetchable, and if you want to remove some than override method
    • self.updatable_fields, self.creatable_fields to override use class methods
      class ContactResource < JSONAPI::Resource
      attributes :name_first, :name_last, :full_name
      def full_name
        "#{@model.name_first}, #{@model.name_last}"
      def self.updatable_fields(context)
        super - [:full_name]
      # class method can be defines inside class <<
      class << self
        def creatable_fields(context)
          super - [:full_name]
  • has_many
  • filters, filter


You can use plugin Postman packaged app or Postman REST client to send API requests.

If you want to send json request from Postman than use header Accept: application/json. If server responds with json, it can set header Content-type: Application/json.

Nice extension is JSON Formatter that will render json response in readable way.

Curl commands:

curl commands


When an user has one image (image is part of the user) than it is fine to do PUT /users/1/image when you want to update or create image. PUT should be idempotent (also DELETE so you can call it several times).

If user have multiple images, than do not use nested, but use POST /users/1/images to create and PUT /images/1 to update image.

Use nouns

Only verb should be HTTP method (GET, POST, PUT, DELETE). Url should contain only nouns that represent resource. So instead POST /users/1/send-message it’s better to use POST /users/1/messages with Content-Type: application/json { "message": "Hello" }.

If you need to send messages to multiple users, you can create new endpoint and send data there POST /group-messages; Content-Type: application/json; { [ { "user" : { "id" : 1 }, "message" : "Hello 1" },...] }

Use url params for search/filter GET /places?lat=123&lon=123.

For authorization use headers POST /users/1/message; Authorization: Bearer 123.

HTTP body can contain json data.

Response status codes:



  • 200 OK (for GET)
  • 201 Created successful POST, login
  • 202 Accepted Accepted but is being processed async
  • 204 No Content :no_content success delete, log out, sign out

For errors we could respond with head :not_found (and hide sensitive information) but its better to send the reason and some info (render json: { error: 'Some problem', error_status: 401, error_code: 1234 }, status: 401)

  • 303 See Other when session create (login) email does not exists
  • 400 Bad Request :bad_request when we have ParameterMissing
  • 401 Unauthorized :unauthorized used for wrong password on login form, or when there is no current_user but it should exists.
    • note that it should not send :unauthorized when user is changing password and type incorect old one (he still should be logged in, and not automatically logged out since unauthorized request occurs).
  • 403 Forbidden :forbidden when current user exists but is forbidden from accessing this data
  • 404 Not Found :not_found url is not valid router or resource does not exist
  • 410 Gone data has been deleted, deactivated …
  • 422 Unprocessable Entity :unprocessable_entity change password form but current password is bad, or form validation errors if ! @user.update() render json: @user.errors.full_messages.join(','), status: :unprocessable_entity, or when creating new resource but uniqueness validation (already exists) prevents

Server errors

  • 500 Internal Server Error unexpected happened on server side
  • 503 Service Unavailable api is overloaded or in maintenance mode

Common responses: Rails does not come with :unauthorized exception so you can create one

# app/models/authorization_exception.rb
class AuthorizationException < Exception

and you can use for example in login

# app/controllers/api/v2/sessions_controller.rb
  def subscriber_login
    # this will raise ActiveRecord::RecordNotFound
    subscriber = Subscriber.find_by! username: params[:username]
    # instead of bang we could use find_by and manually raise, result is the same
    raise"Couldn't find Subscriber") unless subscriber
    raise'Password is not correct') unless subscriber.authenticate(params[:password])
    render json: { auth_token: JwtAuth.encode_subscriber_id( }

and rescue from those exceptions and show json message

# app/controllers/application_controller.rb
  rescue_from JWT::DecodeError do |e|
    render json: { error_message: e.message, error_status: :bad_request}, status: :bad_request

  rescue_from ActiveRecord::RecordNotFound do |e|
    render json: { error_message: e.message, error_status: :not_found }, status: :not_found

  rescue_from ActiveRecord::RecordInvalid do |e|
    render json: { error_message: e.message, error_status: :unprocessable_entity }, status: :unprocessable_entity

  # used with params.permit(:domain).fetch(:domain)
  rescue_from ActionController::ParameterMissing do |e|
    render json: { error_message: e.message, error_status: :bad_request }, status: :bad_request

  rescue_from AuthorizationException do |e|
    render json: { error_message: e.message, error_status: :unauthorized }, status: :unauthorized

In tests you can put byebug below rescue_from ActiveRecord::RecordNotFound do |e| so you can # => myapp
puts {|r| r.match }

Usually in case of validation error, I put only all error messages in one field error_message for example :

# app/controllers/users_controller.rb

  def send_password
    alert = "Failed to send new password"
    format.json { render json: { error_message: alert, error_status: :unprocessable_entity }, status: :unprocessable_entity }

  def create
    @user =
      render json: @user, status: :created
      render json: { error_message: @user.errors.full_messages.to_sentence },
             error_status: :unprocessable_entity

  def update
    unless @user.update(user_params)
      render json: { error_message: @user.errors.full_messages.to_sentence },
             error_status: :unprocessable_entity

In angular, I use connectionInterceptor to set that data.error in case of other errors like: server offline.

In case of success, I use message

  notice = "Successfully updated"
  format.json { render json: { message: notice, data: user.to_json } }

Generate JSON

default ActiveModel JSON Serializer can be customized with render json: @phones.as_json(only: [:id], expect: [:created_at], include: :posts) but for larger API you need to have centralized serializers.


To generate json you can use active_model_serializers documents

cat >> Gemfile << HERE_DOC
# serializer for api
gem 'active_model_serializers', '~>0.10.0'

rails g serializer user

If you rely on rails convention (empty def show;end, implicit render json and not specify render @user) than serializer will not be used. If you specify render json: @user or render json: @users than it will use UserSerialier. If you have some other structure like render json: { user: @user } than serializer will not be used. You can manully call render json: @user, serializer: UserSerialier or render json: @users, each_serializer: UserSerialier

# app/serializers/application_serializer.rb
class ApplicationSerializer< ActiveModel::Serializer
  include Rails.application.routes.url_helpers
# app/serializers/user_serializer.rb
def UserSerializer < ApplicationSerializer
  attributes :id, :name

Associations are handled in reflections

You can call, options).to_json For grape you can use

json api

cat >> config/initializers/active_model_serializers.rb << HERE_DOC
ActiveModelSerializers.config.adapter = :json_api


jBuilder is already included in rails and is nice if you already have some views.

API Documentation

If you use rspec then go with rspec_api_documentation, or swagger

Test and Documentation from rspec

rspec_api_documentations/dsl and generate api docs rake docs:generate and gnome-open docs/: You need to put tests in /spec/acceptance in order to get generate docs working Configuration

# spec/support/rspec_api_documentation.rb
RspecApiDocumentation.configure do |config|
  config.docs_dir = Rails.root.join('public', 'api')
  config.format = :json
  # for json post or patch requests, request body needs to be encoded to json
  # so use this condif instead of let(:raw_post) { params.to_json }
  config.request_body_formatter = { |params| params.to_json }

  config.curl_host = ''

  used_headers = ['Content-Type', 'Authentication']
  ignored_headers = ['Cookie', 'Host']
  config.curl_headers_to_filter = ignored_headers
  config.request_headers_to_include = used_headers
  config.response_headers_to_include = used_headers

  # By default examples and resources are ordered by description. Set to true
  # keep the source order.
  config.keep_source_order = true


  • resource synonim for describe, but you need to use resource if you want to generate docs and have
  • get, post, patch, delete
    • you can validate response_status and response_body which is JSON.parse(response.body)
  • parameter used to define params. You need let(:param1) { 'my_email' } in order to show it in example body or curl
  • example_request is the same as calling example (synonim for it) and do_request
  • send :name to get value of name
  • no_doc
  • explanation can be inside resource or it block

If you got 415 error UNSUPPORTED_MEDIA_TYPE than set header. If you got is not a valid resource code 101, than you mispelled type top level attribute (for example users) on resource object. If you got "no implicit conversion of nil into Hash" than problem is that we need to declare attribute on the resource. If you for does not contain a key code 109, than you need to add :id to both “data” and “url” and use different name, for example this is duplication for update resources:

  patch '/v1/organizations/:organization_id' do
    let(:organization) { create :organization, name: 'My Organization' }
    let(:organization_id) { }
    let(:id) { }
    parameter :id, 'Id of the organization.', required: true

If you use parameter status than you need to disable dsl

# config/support/rspec_api_documentation.rb
RspecApiDocumentation.configure do |config|

HTTP codes are standard (200 get, 201 created, 204 deleted).

You can debug with:

tail -f log/test.log

To change domain which is used in tests you can define full url in get, post… like get "http://#{API_DOMAN}/posts" do (note that we need to use http prefix so it does not add it after Using RspecApiDocumentation.configure do |config| and config.curl_host = '' will change domain only on curl example, not in test, so it will be curl -g "my.domain.comwww.api.domain/posts". Another way is to use header 'Host', API_DOMAIN so you do not need to use full url. note that here we do not need protocol http, we just need host domain

# spec/acceptance/api/v2/posts_spec.rb
require 'rspec_api_documentation/dsl'


resource 'Posts' do
  header 'Accept', 'application/json'
  header 'Authentication', :auth_token

  # use header
  header 'Host', API_DOMAIN
  # or use full http path
  get "#{API_DOMAN}/posts" do

  let(:user) { create :user }
  let(:auth_token) { JwtAuth.encode_user_id }
  let(:post) { create :post }
  before do

JWT Json web tokens

gem 'jwt'

t = JWT.encode( {a: 1}, 'secret')
=> "eyJhbGciOiJIUzI1NiJ9.eyJhIjoxfQ.LrlPmSL4FxrzAHJSYbKzsA997COXdYCeFKlt3zt5DIY"
>> JWT.decode t, 'secret' #=> [{"a"=>1}, {"alg"=>"HS256"}]

Testing application is in ~/rails/temp/rails_5.2.3/ branch devise_jwt_token. You can add custom devise strategy so both devise http and jwt authentication can work api_token_strategy

# app/strategies/jwt_strategy.rb
class JwtStrategy < Warden::Strategies::Base
  def valid?
    # A copy of JwtStrategy has been removed from the module tree but is still active
    # JwtAuth.token_from_request_headers request.headers
    # so we need to copy same method here
    request.headers['Authentication'].to_s.split(' ').last

  def authenticate!
    user = User.find_by(id: JwtAuth.decoded_user_id(request.headers))
    if user
      success! user
      fail! 'Invalid email or password'

# app/services/jwt_auth.rb
class JwtAuth
  SECRET = Rails.application.secrets.secret_key_base

  def self.encode_user_id(user_id)
    payload = { user_id: user_id }
    JWT.encode(payload, SECRET)

  def self.decoded_user_id(headers)
    token = token_from_request_headers headers
    payload = JWT.decode(token, SECRET)[0]

  def self.token_from_request_headers(headers)
    headers['Authentication'].to_s.split(' ').last

# config/initializers/devise.rb
  config.warden do |manager|
    manager.default_strategies(scope: :user).unshift :jwt_strategy

# config/initializers/warden.rb
Warden::Strategies.add(:jwt_strategy, JwtStrategy)

Swagger ui grape

# Gemfile
gem 'grape-swagger'
gem 'grape-swagger-ui'

# app/api/v1/root.rb
require 'grape-swagger'
module API
  module V1
    class Root < Grape::API
      mount Events

# config/initializers/assets.rb
config.assets.precompile += %w(swagger_ui.js swagger_ui.css swagger_ui_print.css swagger_ui_screen.css)

Swagger docs

sed -i Gemfile -e '/group :development do/a  \
  # use github until it is merged
  gem 'swagger-docs', git: ''

Basepath is important since other json files will be looked there.

param first argument is: :query, :path or :form

  swagger_controller :customer_sessions, "Customer Sessions"

  swagger_api :create do
    summary "Customer sign in"
    notes "Sign in form"
    param :form, :email, :string, :required, :Email"

To generate json, you need to run rake swagger:docs or overide precompile task

# lib/tasks/precompile_overrides.rake
namespace :assets do
  task :precompile do

Usually for precompile is disabled for production, so you need to enable it in config/environments/production.rb

Copy dist content to your public/apidocs and update all paths in public/apidocs/index.html to match your /apidocs, and change js url = "/apidocs/api-docs.json";

  • default sort is alphabetical for path and methods (delete, get, path…). You can provide a function for apisSorter operationsSorter but too complicated. Order in server response is not supported.

Appman swagger


Apipie rails

If you are using minitest there is not automatic way to generate docs, so you can use apipie-rails for documentation.

echo "gem 'apipie-rails'" >> Gemfile
bundle install
rails g apipie:install # this will generate config/initializers/apipie.rb and update routes

Jus add option config.translate = false


If grape is used than we can rescue from all exceptions, but that needs to be before mount methods and only for StandardError exceptions

# app/api/v1/root.rb
module API
  module V1
    class Root < Grape::API
      rescue_from Koala::Facebook::AuthenticationError, MyappExceptions::Base do |e|
                        data: {
                          message: e.message,
                          stack: e.backtrace,
                          error: e
        )["#{e.class} #{e.message}"], 500, 'Content-type' => 'text/error').finish
      # now we can mount ...
      mount Users
# app/models/myapp_exceptions.rb
module MyappExceptions
  # use this base class so it is easier to rescue only that
  # grape rescue only from standard error
  class Base < StandardError
  class UserNotFound < Base



  • model skill level as string, but consider using array. For example if someone wants to cover all skill levels.

In email search for Chris Kottom Lesson 9: Token-Based Authentication with JWTs Accessing Google oauth to list my videos