123456789_123456789_123456789_123456789_123456789_

DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON https://guides.rubyonrails.org.

Using Rails for API-only Applications

In this guide you will learn:


What is an API Application?

An API application speaks a data-interchange format such as JSON or XML, rather than HTML which is used to paint a user interface. Its purpose is to integrate with another piece of software, as opposed to being used by a human.

While Railsprovides a mechanism to build an API alongside your full-stack application, a Rails application that only speaks JSON (or another similar format) can be a useful configuration. The same Rails API could service the JavaScript frontend in the browser, as well as the native mobile apps and other integrations. Basecamp is a good example of the former approach where HTML is used for web browsers, but a JSON API is also provided for external software integrations. Certain parts ofShopify uses the latter approach in where a frontend framework consumes JSON resources from an API.

This guide covers Rails' support for building an API application without any HTML rendering or browser-related features.

Why Use Rails for APIs?

When using Rails as an API, we remove the components from the view layer which generate HTML. Nothing else changes.

Rails still provides a feature-rich environment to develop server applications with its security features, MVC architecture, and resourceful routing. Tools such as Active Record, Active Job, Active Storage, Action Mailer, and native caching are all still available in API applications.

Most of Rails' features are still relevant even when building an API-only application.

Using Rails for API-only Applications

Rails can be configured to be used only as an API, which will exclude the HTML rendering and browser-related features. Some features can be added back in as required.

Creating a New API Application

You can generate a new API-only Rails app using the --api flag:

$ rails new my_api_app --api

This will:

Scaffolding API Resources

The bin/rails generate scaffold command will skip HTML views for API applications:

$ bin/rails g scaffold Group name:string

This will create the GroupsController demonstrated below along with an empty Group model.

# app/controllers/groups_controller.rb

class GroupsController < ApplicationController
  before_action :set_group, only: %i[ show update destroy ]

  # GET /groups
  def index
    @groups = Group.all

    render json: @groups
  end

  # GET /groups/1
  def show
    render json: @group
  end

  # POST /groups
  def create
    @group = Group.new(group_params)

    if @group.save
      render json: @group, status: :created, location: @group
    else
      render json: @group.errors, status: :unprocessable_content
    end
  end

  # PATCH/PUT /groups/1
  def update
    if @group.update(group_params)
      render json: @group
    else
      render json: @group.errors, status: :unprocessable_content
    end
  end

  # DELETE /groups/1
  def destroy
    @group.destroy!
  end

  private
    # Use callbacks to share common setup or constraints between actions.
    def set_group
      @group = Group.find(params.expect(:id))
    end

    # Only allow a list of trusted parameters through.
    def group_params
      params.expect(group: [:name])
    end
end

The controller is set up to render JSON by default. Any objects passed to render json: will be converted to JSON using to_json.

Assuming we have two Groups in our database named Rails Founders and Rails Contributors, the /groups endpoint will generate the following JSON response.

$ curl -s localhost:3000/groups | jq
[
  {
    "id": 1,
    "name": "Rails Founders",
    "created_at": "2026-03-24T13:39:22.357Z",
    "updated_at": "2026-03-24T13:39:22.357Z"
  },
  {
    "id": 2,
    "name": "Rails Contributors",
    "created_at": "2026-03-24T13:39:25.386Z",
    "updated_at": "2026-03-24T13:39:25.386Z"
  }
]

NOTE: The above example, and more examples further down, use the jq command line utility to format JSON output. If it isn't installed on your system, consult thedownload page for instructions.

Building JSON using JBuilder

For more fine-grained control over the JSON structure, or to build complex JSON objects, you can use theJBuilder gem. It isn't installed by default — you'll need to uncomment it in your Gemfile and run bundle install.

JBuilder provides a handy DSL for building JSON objects, and requires a view file:

# app/views/groups/index.json.jbuilder

json.array! @groups do |group|
  json.group_name(group.name)
end

Using this technique, we still have a view layer, only simpler than if it was HTML — and we still get the benefits of MVC separation.

NOTE: When the jbuilder gem is present in your Gemfile, bin/rails generate scaffold will automatically create .json.jbuilder views along with the controller and model.

Ensure the controller's index action implicitly renders the JBuilder template, and doesn't call render:

def index
  @groups = Group.all
end

The /groups endpoint will now only return the group names:

$ curl -s localhost:3000/groups | jq
[
  {
    "group_name": "Rails Founders"
  },
  {
    "group_name": "Rails Contributors"
  }
]

WARNING: Accessing localhost:3000/groups in a browser will cause an ::ActionController::UnknownFormat error. Browsers include an Accept HTTP header requesting an HTML response, so Rails will look for an HTML template which doesn't exist, causing the error.

curl sets Accept to */* telling Rails that any response format is acceptable, hence the JSON is implicitly rendered. You can optionally add render formats: :json in your controller action to force Rails to render JSON (using JBuilder in this case) irrespective of the Accept header.

XML Responses

While JSON is the default, Rails API applications can also render XML. Rails includes thebuilder</a> gem which provides a DSL to build XML data structures.

# app/views/groups/index.xml.builder

xml.groups do
  @groups.each do |group|
    xml.group do
      xml.title group.name
    end
  end
end

You'll also need to add two modules to your Application Controller. These areexcluded by default for API applications. The jbuilder gem automatically adds them back in, but builder does not.

class ApplicationController < ActionController::API
  include ActionView::Rendering
  include ActionController::ImplicitRender
end

Ensure your controller action is rendering implicitly (alternatively, you can use render formats: :xml):

def index
  @groups = Group.all
end
def index
  @groups = Group.all
  render formats: :xml
end

The /groups endpoint will now render XML!

$ curl -s localhost:3000/groups
<groups>
  <group>
    <title>Rails Founders</title>
  </group>
  <group>
    <title>Rails Contributors</title>
  </group>
</groups>

NOTE: If ::ActionView::Rendering is not included, attempting to render a template of any format will silently return a 204 No Content response. It will not raise an error.

WARNING: Similar to JSON responses, accessing localhost:3000/groups in a web browser will raise an ::ActionController::UnknownFormat because Rails will look for an HTML template based on the Accept header.

Use render formats: :xml in the controller action to force XML responses.

ERB

If you don't wish to use builder, you can use ERB to write your XML views.

<%# app/views/groups/index.xml.erb -%>

<groups>
  <% @groups.each do |group| %>
  <group>
    <name><%= group.name %></name>
  </group>
  <% end %>
</groups>

Reconfiguring an Existing Application

Reconfigure an existing fully-featured Rails application to function as API-only by setting config.api_only:

# config/application.rb

# ...
module MyRailsApp
  class Application < Rails::Application
    # ...
    config.api_only = true
  end
end

This will also change [config.debug_exception_response_format][] to :api. To preserve rendering errors as HTML pages, set it to :default in your config/environments/development.rb.

Lastly, change your ApplicationController's super-class:

class ApplicationController < ActionController::API
end

That's everything you need to do to reconfigure your app to work solely as an API.

Rack Middleware in API Applications

A Rails API application builds the following middleware stack by default:

use ActionDispatch::HostAuthorization
use Rack::Sendfile
use ActionDispatch::Static
use ActionDispatch::Executor
use ActionDispatch::ServerTiming
use ActiveSupport::Cache::Strategy::LocalCache::Middleware
use Rack::Runtime
use ActionDispatch::RequestId
use ActionDispatch::RemoteIp
use Rails::Rack::Logger
use ActionDispatch::ShowExceptions
use ActionDispatch::DebugExceptions
use ActionDispatch::ActionableExceptions
use ActionDispatch::Reloader
use ActionDispatch::Callbacks
use ActiveRecord::Migration::CheckPending
use Rack::Head
use Rack::ConditionalGet
use Rack::ETag
run MyApp::Application.routes

Browser-specific middleware such as ::ActionDispatch::Cookies, ::ActionDispatch::Flash, and ::ActionDispatch::ContentSecurityPolicy::Middleware are excluded for API applications.

They can be manually added back in if you wish. For further information on Rack middleware, see theRails on Rack guide.

Session Middleware

The Rack middleware forRails sessions is excluded by default for API applications. To add it back in, you need to manuallyconfigure your session store before adding the middleware to the stack, then pass session_options when including the store's middleware.

# config/initializers/session_store.rb

Rails.application.config.tap do |config|
  # Configure the session to use cookies
  config.session_store :cookie_store, key: "_my_app_session"

  # Required for all session management regardless of the session store
  config.middleware.use ActionDispatch::Cookies

  # Add the middleware for the cookie store to the stack
  config.middleware.use \
    config.session_store, config.session_options
end

You can set up other session stores such as ::ActionDispatch::Session::CacheStore or ::ActionDispatch::Session::MemCacheStore using the same technique.

API Controller Modules

The base class for API-only controllers, [ActionController::API][], includes a subset of the modules included by [ActionController::Base][] which is used in full-stack Rails applications.

Included Modules

Some of the included modules are:

| | | | ---------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | [ActionController::UrlFor][] | Makes url_for and similar helpers available. | | [ActionController::Redirecting][] | Adds support for redirect_to. | | [ActionController::ApiRendering][] | Adds basic support for rendering. | | [ActionController::Renderers::All][] | Adds support for :json, :xml and similar renderers. | | [ActionController::ConditionalGet][] | Adds support for stale?. | | [ActionController::StrongParameters][] | Adds support for parameters filtering in combination with Active Model mass assignment. | | [ActionController::Caching][] | Enables controller-level caching techniques such as Russian Doll caching | | [ActionController::DataStreaming][] | Adds support for send_file and send_data. | |ActionController::Instrumentation][] | Adds support for the instrumentation hooks defined by Action Controller (see [the instrumentation guide for more information regarding this). | | [ActionController::ParamsWrapper][] | Wraps the parameters hash into a nested hash, so that you don't have to specify root elements when sending POST requests. | | [ActionController::Head][] | Adds support for returning a response with no content, only headers. |

View the full list of included modules in your Rails console:

irb> {ActionController::API.ancestors}.select { it.name.include? "Controller" }

Further information on each module is available in theAPI documentation.

Excluded Modules

View the modules excluded from [ActionController::API][], but included in [ActionController::Base][] in your Rails console:

irb> {ActionController::Base.ancestors} - {ActionController::API.ancestors}

Any excluded modules can be included manually in your ApplicationController:

class ApplicationController < ActionController::API
  include AbstractController::Translation
end

Some useful modules you may wish to include are: