RailsApiKit

A lightweight Rails toolkit for building standardized, structured API responses with serialization, error handling, filtering, sorting, and pagination.

Features

ApiKit offers a collection of lightweight modules that integrate seamlessly with your Rails controllers:

  • Object serialization - Powered by Active Model Serializers
  • Error handling - Standardized error responses for parameters, validation, and generic errors
  • Fetching - Support for relationship includes and sparse fieldsets
  • Filtering & Sorting - Advanced filtering and sorting powered by Ransack
  • Pagination - Built-in pagination support with links and metadata

Installation

Requirements:

  • Ruby 3.3.0 or higher
  • Rails 8.0 or higher

Add this line to your application's Gemfile:

gem "rails_api_kit"

And then execute:

$ bundle install

Quick Start

1. Enable Rails Integration

Add this to an initializer:

# config/initializers/rails_api_kit.rb
require "rails_api_kit"

ApiKit::RailsApp.install!

This registers the media type and renderers.

2. Basic Usage

class UsersController < ApplicationController
  include ApiKit::Filtering
  include ApiKit::Pagination

  def index
    allowed_fields = [ :first_name, :last_name, :created_at ]

    api_filter(User.all, allowed_fields) do |filtered|
      api_paginate(filtered.result) do |paginated|
        render api_paginate: paginated
      end
    end
  end

  def show
    user = User.find(params[:id])
    render api: user
  end
end

3. Create Serializers

class UserSerializer < ActiveModel::Serializer
  attributes :id, :first_name, :last_name, :email, :created_at, :updated_at

  has_many :posts
end

Core Modules

ApiKit::Filtering

Provides powerful filtering and sorting using Ransack:

class UsersController < ApplicationController
  include ApiKit::Filtering

  def index
    allowed_fields = [ :first_name, :last_name, :email, :posts_title ]

    api_filter(User.all, allowed_fields) do |filtered|
      render api: filtered.result
    end
  end
end

Example requests:

# Filter by first name containing "John"
GET /users?filter[first_name_cont]=John

# Sort by last name descending, then first name ascending
GET /users?sort=-last_name,first_name

# Complex filtering with relationships
GET /users?filter[posts_title_matches_any]=Ruby,Rails&sort=-created_at

Sorting with Expressions

Enable aggregation expressions for advanced sorting:

def index
  allowed_fields = [ :first_name, :posts_count ]
  options = { sort_with_expressions: true }

  api_filter(User.joins(:posts), allowed_fields, options) do |filtered|
    render api: filtered.result.group("users.id")
  end
end
# Sort by post count
GET /users?sort=-posts_count_sum

ApiKit::Pagination

Handles pagination with standardized links:

class UsersController < ApplicationController
  include ApiKit::Pagination

  def index
    api_paginate(User.all) do |paginated|
      render api_paginate: paginated
    end
  end

  private

  def api_meta(resources)
    {
      pagination: api_pagination_meta(resources),
      total: resources.respond_to?(:count) ? resources.count : resources.size
    }
  end
end

Example requests:

# Get page 2 with 20 items per page
GET /users?page[number]=2&page[size]=20

ApiKit::Fetching

Supports relationship inclusion and sparse fieldsets:

class UsersController < ApplicationController
  include ApiKit::Fetching

  def index
    render api: User.all
  end

  private

  def api_include
    # Whitelist allowed includes
    super & [ "posts", "profile" ]
  end
end

Example requests:

# Include related posts
GET /users?include=posts

# Sparse fieldsets
GET /users?fields[user]=first_name,last_name

ApiKit::Errors

Standardized error responses for common Rails exceptions:

class UsersController < ApplicationController
  include ApiKit::Errors

  def create
    user = User.new(user_params)

    if user.save
      render api: user, status: :created
    else
      render api_errors: user.errors, status: :unprocessable_entity
    end
  end

  def update
    user = User.find(params[:id])

    if user.update(user_params)
      render api: user
    else
      render api_errors: user.errors, status: :unprocessable_entity
    end
  end

  private

  def user_params
    params.require(:data).require(:attributes).permit(:first_name, :last_name, :email)
  end

  def render_api_internal_server_error(exception)
    # Custom exception handling (e.g., error tracking)
    # Sentry.capture_exception(exception)
    super(exception)
  end
end

Handled exceptions:

  • StandardError → 500 Internal Server Error
  • ActiveRecord::RecordNotFound → 404 Not Found
  • ActionController::ParameterMissing → 422 Unprocessable Entity

Advanced Configuration

Custom Serializer Resolution

class UsersController < ApplicationController
  def index
    render api: User.all, serializer_class: CustomUserSerializer
  end

  private

  def api_serializer_class(resource, is_collection)
    ApiKit::RailsApp.serializer_class(resource, is_collection)
  rescue NameError
    "#{resource.class.name}Serializer".constantize
  end
end

Custom Page Size

def api_page_size(pagination_params)
  per_page = pagination_params[:size].to_i
  return 30 if per_page < 1 || per_page > 100
  per_page
end

Serializer Parameters

def api_serializer_params
  {
    current_user: current_user,
    include_private: params[:include_private].present?
  }
end

Configuration

Environment Variables

  • PAGINATION_LIMIT - Default page size (default: 30)

Dependencies

This gem leverages these excellent libraries:

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/iakbudak/api_kit

This project follows the Contributor Covenant code of conduct.

Development

After checking out the repo:

bundle install
bundle exec rspec  # Run tests
bundle exec rubocop  # Check code style

License

The gem is available as open source under the terms of the MIT License.