You're starting a new Rails project. You have a decision to make.

API only or full stack?

The answer isn't obvious. Both are valid. Both have trade-offs.

Let me help you choose.


The Short Answer

Build Best For Skip If
Full Stack Most web apps, solo developers, MVPs You need a mobile app or separate frontend team
API Only Mobile apps, separate frontend, microservices You want simplicity and faster development

For most projects, start with full stack. You can always extract an API later. Going the other direction is harder.


Part 1: What's the Difference?

Full Stack Rails

Rails serves both the backend and the frontend.

Browser → Rails (routes, controllers, views, assets) → Database

Everything in one place. One language. One deployment.

API Only Rails

Rails serves only JSON endpoints. A separate frontend consumes them.

Browser → React/Vue/Svelte → Rails API → Database

Two codebases. Two languages (maybe). Two deployments.


Part 2: Full Stack Rails

What You Get

# One controller handles everything
class PostsController < ApplicationController
  def index
    @posts = Post.all
  end
end
<%# One view renders the HTML %>
<% @posts.each do |post| %>
  <h2><%= post.title %></h2>
  <p><%= post.body %></p>
<% end %>

No API contracts. No CORS. No JSON serializers. Just Rails.

The Pros

Benefit Why It Matters
Faster development No separate frontend to build
One language Ruby everywhere
No API maintenance Change a model, update the view, done
Hotwire/Turbo Modern reactivity without JavaScript frameworks
Simpler deployment One codebase, one server
Easier onboarding New devs learn one stack

The Cons

Drawback When It Hurts
Coupled frontend and backend You want to add a mobile app
Server-rendered pages You need highly interactive UI
Limited to web You can't reuse backend for other clients

Who Should Use Full Stack

You, if:

  • Building a traditional web app (SaaS dashboard, internal tool, CRUD app)
  • Working alone or on a small team
  • Need to ship fast
  • Don't need a mobile app yet
  • Want simplicity over flexibility

Part 3: API Only Rails

What You Get

# rails new myapp --api

This generates a lean Rails app without middleware for views, cookies, or sessions (unless you add them).

# app/controllers/api/v1/posts_controller.rb
class Api::V1::PostsController < ApplicationController
  def index
    posts = Post.all
    render json: posts
  end
end

Your frontend (React, Vue, Svelte, mobile) calls this endpoint.

The Pros

Benefit Why It Matters
Reusable API One backend serves web, mobile, third parties
Frontend freedom Use any framework you want
Clear separation Frontend and backend teams work independently
Lighter footprint Less middleware, faster responses
JSON optimization Built for API needs

The Cons

Drawback When It Hurts
Two codebases Twice the maintenance
API versioning Breaking changes require coordination
Authentication complexity JWT tokens, CORS, CSRF
More boilerplate Serializers, documentation, testing
Slower development Building two apps instead of one

Who Should Use API Only

You, if:

  • Building a mobile app (iOS and Android)
  • Have a separate frontend team
  • Need to serve multiple clients (web, mobile, partner API)
  • Already committed to React or Vue
  • Building a microservice

Part 4: The Hybrid Approach

You don't have to choose one or the other. Rails excels at gradual integration.

Start Full Stack, Add API Endpoints

Most successful Rails apps start full stack. Then they add API endpoints as needed.

# config/routes.rb
Rails.application.routes.draw do
  # Full stack routes
  resources :posts

  # API routes for mobile app
  namespace :api do
    namespace :v1 do
      resources :posts, only: [:index, :show]
    end
  end
end

One codebase. Two interfaces.

Use the Same Controllers for Both

class PostsController < ApplicationController
  def index
    @posts = Post.all

    respond_to do |format|
      format.html # index.html.erb
      format.json { render json: @posts }
    end
  end
end

Same logic. Different formats.

Add React Later with react-rails

# Gemfile
gem "react-rails"
rails generate react:install
<%# app/views/posts/index.html.erb %>
<%= react_component("PostList", { posts: @posts }) %>

You get React without a separate API.


Part 5: Making the Decision

Ask yourself these questions.

Question 1: Do you need a mobile app now?

Answer Verdict
Yes, immediately Start with API only
Yes, eventually Start full stack, add API later
No Start full stack

Question 2: Do you have a separate frontend team?

Answer Verdict
Yes API only
No Full stack

Question 3: Is your UI highly interactive (like Figma)?

Answer Verdict
Yes Consider API only or Hotwire
No Full stack

Question 4: Are you building a prototype or MVP?

Answer Verdict
Yes Full stack (ship faster)
No Consider API only

Question 5: Does your team already know React?

Answer Verdict
Yes, deeply API only or hybrid
No Full stack

Part 6: API Only Setup Guide

If you decide API only, here's how to start.

Create the App

rails new myapp --api --database=postgresql

Key Differences from Full Stack

# config/application.rb
module Myapp
  class Application < Rails::Application
    # API only means no view middleware
    config.api_only = true

    # No cookies or sessions by default
    # Add them if needed:
    # config.middleware.use ActionDispatch::Cookies
    # config.middleware.use ActionDispatch::Session::CookieStore
  end
end

Add Authentication (JWT)

# Gemfile
gem "jwt"
gem "bcrypt"
# app/controllers/api/v1/auth_controller.rb
class Api::V1::AuthController < ApplicationController
  def login
    user = User.find_by(email: params[:email])

    if user&.authenticate(params[:password])
      token = JWT.encode({ user_id: user.id }, Rails.application.secret_key_base)
      render json: { token: token, user: { id: user.id, email: user.email } }
    else
      render json: { error: "Invalid credentials" }, status: :unauthorized
    end
  end
end

CORS Configuration

# Gemfile
gem "rack-cors"
# config/initializers/cors.rb
Rails.application.config.middleware.insert_before 0, Rack::Cors do
  allow do
    origins "https://myfrontend.com", "http://localhost:3001"

    resource "*",
      headers: :any,
      methods: [:get, :post, :put, :patch, :delete, :options, :head],
      credentials: true
  end
end

Serializers for JSON

# Gemfile
gem "active_model_serializers"
rails generate serializer post
# app/serializers/post_serializer.rb
class PostSerializer < ActiveModel::Serializer
  attributes :id, :title, :body, :created_at
  belongs_to :user
end
# app/controllers/api/v1/posts_controller.rb
def index
  posts = Post.all
  render json: posts, each_serializer: PostSerializer
end

Part 7: Full Stack Setup Guide

If you decide full stack, here's how to optimize.

Create the App

rails new myapp --database=postgresql --css=bootstrap --javascript=importmap

Hotwire for Interactivity

Hotwire gives you SPA-like behavior without JavaScript frameworks.

<%# app/views/posts/index.html.erb %>
<%= turbo_frame_tag "posts" do %>
  <% @posts.each do |post| %>
    <div id="<%= dom_id post %>">
      <h2><%= post.title %></h2>
      <%= link_to "Edit", edit_post_path(post) %>
    </div>
  <% end %>
<% end %>
<%# app/views/posts/edit.html.erb %>
<%= turbo_frame_tag "posts" do %>
  <%= render "form", post: @post %>
<% end %>

Stimulus for JavaScript Behaviors

// app/javascript/controllers/clipboard_controller.js
import { Controller } from "@hotwired/stimulus"

export default class extends Controller {
  copy() {
    navigator.clipboard.writeText(this.element.dataset.text)
  }
}
<button data-controller="clipboard" data-clipboard-text="Copied!" data-action="click->clipboard#copy">
  Copy
</button>

Turbo Streams for Real-time Updates

<%# app/views/posts/create.turbo_stream.erb %>
<%= turbo_stream.append "posts" do %>
  <%= render @post %>
<% end %>

<%= turbo_stream.replace "post_form" do %>
  <%= render "form", post: Post.new %>
<% end %>

Part 8: Real-World Examples

Example 1: Basecamp (Full Stack)

Basecamp is the original Rails full stack app. They invented Hotwire. Their philosophy:

  • One codebase
  • Server-rendered HTML
  • Stimulus for sprinkles
  • Turbo for speed

They serve millions of users. No React needed.

Example 2: GitHub (Full Stack with API)

GitHub is full stack Rails. They also have a robust API.

Their approach:

  • Most features are server-rendered
  • API endpoints exist for mobile apps and third parties
  • Same controllers serve HTML and JSON

Example 3: Shopify (API for Checkout)

Shopify is full stack for the admin. But their checkout is API-driven.

Why?

  • Checkout must work across themes and apps
  • Mobile SDKs need consistent APIs
  • Partners build custom experiences

Example 4: A Mobile App Startup (API Only)

A startup building an iOS and Android app chooses API only.

Why?

  • One backend serves both mobile apps
  • Web app comes later (or never)
  • Mobile team owns the frontend

Part 9: Migration Path

Starting full stack doesn't lock you in. You can add API endpoints later.

Phase 1: Full Stack

class PostsController < ApplicationController
  def index
    @posts = Post.all
  end
end

Phase 2: Add JSON Responses

class PostsController < ApplicationController
  def index
    @posts = Post.all

    respond_to do |format|
      format.html
      format.json { render json: @posts }
    end
  end
end

Phase 3: Extract API Namespace

# config/routes.rb
namespace :api do
  namespace :v1 do
    resources :posts, only: [:index, :show, :create]
  end
end
# app/controllers/api/v1/posts_controller.rb
class Api::V1::PostsController < ApplicationController
  skip_before_action :verify_authenticity_token
  before_action :authenticate_user!

  def index
    render json: current_user.posts
  end
end

Phase 4: Separate API and Web Controllers

At this point, you have two controllers. One for HTML. One for JSON. They can evolve independently.


Part 10: Performance Considerations

Full Stack Performance

Factor Impact
Server rendering CPU usage on Rails server
Caching Russian doll caching helps significantly
Asset delivery CDN for static assets
Database queries N+1 still matters

API Only Performance

Factor Impact
Response size JSON is smaller than HTML
Client rendering Browser CPU usage (not your server)
API calls Multiple round trips possible
Serialization Can be slow with large datasets

Benchmark Example

App Type Time to First Paint Time to Interactive
Full Stack (cached) ~150ms ~300ms
Full Stack (uncached) ~300ms ~500ms
API + React ~200ms (skeleton) ~800ms (after JS loads)

Full stack is often faster for content-heavy apps. API + SPA is often faster for app-like interfaces after initial load.


The Decision Matrix

Your Situation Recommendation
Solo developer, building MVP Full stack
Small team, need to ship fast Full stack
Building mobile app (iOS + Android) API only
Already have React team API only or hybrid
Building internal tool Full stack
Building public API for third parties API only
Need real-time collaboration Full stack (Hotwire) or API + WebSockets
Not sure yet Start full stack, extract API later

Summary

Full stack Rails is underrated. API only Rails is overused.

Full Stack API Only
Development speed Fast Slow
Complexity Low High
Flexibility Low High
Best for Web apps, MVPs Mobile apps, multiple clients
Learning curve Gentle Steep

Start with full stack unless you have a specific reason not to.

You can always add API endpoints. You can always extract a separate frontend.

But starting API only when you don't need it is wasted time.

Build your app. Ship it. Then worry about architecture.

Your users don't care how you built it. They care that it works.