1. 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 Rails provides 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 of Shopify 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.
2. 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.
3. 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.
3.1. 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:
- Set
ApplicationControllerto inherit fromActionController::APIinstead ofActionController::Base, which excludes the modules for browser-related functionality. - Configure the generators to skip generating views, helpers, and assets when you scaffold a new resource.
- Exclude Rack middleware related to browser applications, such as support for cookies.
3.2. 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"
}
]
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 the download page for instructions.
3.3. Building JSON using JBuilder
For more fine-grained control over the JSON structure, or to build complex JSON
objects, you can use the JBuilder 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.
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"
}
]
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.
3.4. XML Responses
While JSON is the default, Rails API applications can also render XML. Rails
includes the builder 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 are
excluded 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>
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.
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.
3.4.1. 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>
3.5. 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.
4. 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 the Rails on Rack guide.
4.1. Session Middleware
The Rack middleware for
Rails sessions is excluded by default
for API applications. To add it back in, you need to manually
configure 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.
5. 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.
5.1. 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 the API documentation.
5.2. 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:
ActionController::MimeResponds
Userespond_toin controllers for multi-format responses.AbstractController::Translation
Support for thelandtlocalization and translation methods.ActionController::HttpAuthentication::Basic::ControllerMethods
Basic HTTP Authentication.ActionController::HttpAuthentication::Digest::ControllerMethods
Authentication using HTTP digests.ActionController::HttpAuthentication::Token::ControllerMethods
Authentication using tokens.ActionView::Layouts
Support for layouts when rendering. This might be useful with JBuilder.ActionController::Cookies
Support for cookies, which includes support for signed and encrypted cookies. This requires theActionDispatch::CookiesRack middleware.