Ruby on Rails

Rails APIとは?作成手順・JWT認証・React連携・Docker環境を実装解説【Rails 8.1】

Rails APIは、Ruby on Railsをビュー層のない「APIモード」で動かし、JSONを返すバックエンドに特化させた構成を指します。ReactやVueと組み合わせたSPA、モバイルアプリのバックエンド、マイクロサービスで採用されます。本記事はRails 8.1系を前提に、API作成の手順・JSONの整形・JWT認証・ReactとのCORS連携・Docker Composeでの開発環境構築・API仕様書の自動生成までを、動くコードで一気通貫に解説します。とくにReactとの連携で詰まりやすいCORSと、独学者が最短で環境を立ち上げるDocker構成は、コピーして動かせる形で示します。

まとめ:Rails APIの要点

  • APIモードとはrails new アプリ名 --api でビュー・不要ミドルウェアを省いた軽量構成。既存アプリは config.api_only = true で切り替える。
  • 作成の骨格api/v1 名前空間でRESTfulにルーティングし、コントローラで render json: を返す。整形は標準の as_json、規模が出たら jbuilder か alba を使う(active_model_serializersはメンテナンスモード)。
  • 認証devise-jwt でJWTを発行し、フロントはヘッダに Authorization: Bearer を付与。トークンの保存先はXSS/CSRFのリスクと引き換えで選ぶ。
  • React連携の要:別オリジンになるため rack-cors でCORSを許可する。--api なら config/initializers/cors.rb が生成済み(コメントアウト状態)。
  • 開発環境:Docker Composeで DB・Rails API・React を3コンテナに分けると、独学でもOSに依存せず同じ環境を再現できる。
  • バージョン:APIモードはRails 5.0で標準化。本記事は執筆時点の最新安定版 Rails 8.1系を前提とする。

Rails APIとは|APIモードと通常モードの違い

Rails APIモードは、HTMLビューを描画しないでJSONなどのデータだけを返すことに特化したRailsの動作モードです。通常モードが完全なMVC(Model-View-Controller)を備えるのに対し、APIモードはViewとブラウザ向けミドルウェアを省いた分だけ軽く、起動と応答が速くなります。

APIモードで省かれるもの

APIモードでは、ブラウザのセッションやCookie、Flash、アセットパイプライン、ビューのレンダリングに関わるミドルウェアが既定で外れます。具体的には、Cookieやセッション関連のミドルウェア、ActionDispatch::Flash、静的ファイル配信などが読み込まれず、ApplicationControllerActionController::Base ではなく ActionController::API を継承します。この差がリクエスト処理の軽量化に直結します。

通常モードとの使い分け基準

サーバー側でHTMLを組み立てて返す従来型のWebアプリなら通常モードが向きます。一方、フロントをReactやVue、あるいはモバイルアプリが担い、Railsはデータ提供に徹する構成ならAPIモードを選びます。判断の分かれ目は「Railsがビューを描くか否か」の一点で、画面を持たないならAPIモードにする、というのが実務上の基準です。管理画面など一部だけHTMLが必要なら、通常モードのまま respond_to でJSONを併用する折衷も可能です。

Rails APIの作成手順|rails new –api から最初のエンドポイントまで

ここではRails 8.1系で、記事(Article)を管理する最小のAPIを作ります。プロジェクト作成からエンドポイント公開までの流れは次のとおりです。

プロジェクトの作成とディレクトリ構成

--api オプションを付けて新規作成します。既定のデータベースはSQLiteですが、本番を見据えるなら -d postgresql を付けてPostgreSQLにしておくと、後述のDocker構成にもそのまま乗ります。

rails new blog_api --api -d postgresql
cd blog_api
rails generate model Article title:string body:text
rails db:create db:migrate

モデル・コントローラ・マイグレーションの生成は rails generate が担います。scaffoldやカスタムジェネレータを含む使い分けはRailsジェネレータ(rails generate)の使い方で詳しく整理しています。

ルーティングとコントローラの実装

APIはバージョンを切って壊れにくくするのが定石です。api/v1 の名前空間でRESTfulなルートを定義します。

# config/routes.rb
Rails.application.routes.draw do
  namespace :api do
    namespace :v1 do
      resources :articles
    end
  end
end

この1行の resources :articles だけで、一覧・詳細・作成・更新・削除に対応する /api/v1/articles 系のエンドポイントが揃います。対応するコントローラを作り、JSONを返します。

rails generate controller api/v1/articles
# app/controllers/api/v1/articles_controller.rb
module Api
  module V1
    class ArticlesController < ApplicationController
      def index
        render json: Article.all
      end

      def show
        render json: Article.find(params[:id])
      end

      def create
        article = Article.new(article_params)
        if article.save
          render json: article, status: :created
        else
          render json: { errors: article.errors }, status: :unprocessable_entity
        end
      end

      private

      def article_params
        params.require(:article).permit(:title, :body)
      end
    end
  end
end

存在しないIDへのアクセスは ActiveRecord::RecordNotFound を送出します。APIでは例外を握りつぶさず、rescue_from で404 JSONに変換しておくと、フロント側のエラー処理が安定します。

# app/controllers/application_controller.rb
class ApplicationController < ActionController::API
  rescue_from ActiveRecord::RecordNotFound do |e|
    render json: { error: e.message }, status: :not_found
  end
end

既存アプリをAPIモードへ切り替える

すでにあるRailsアプリをAPI寄りにするには、config/application.rb に1行加えます。ただし途中からの切り替えはビュー依存コードの棚卸しが必要になるため、実際は新規を --api で作り直すほうが早いケースが多いです。

# config/application.rb
module BlogApi
  class Application < Rails::Application
    config.api_only = true
  end
end

JSONレスポンスの整形とシリアライザの選定

小規模なうちは render json:as_json のオプション(only/except/include)で十分です。ネストや条件分岐が増えたら専用のシリアライザを導入しますが、ここで選択を誤ると保守が重くなります。2026年時点の実情を踏まえた判断は次のとおりです。

手段 位置づけ 向く場面
as_json / render json: Rails標準 属性が少なく整形が単純
jbuilder Rails同梱(通常モード) ビューとしてJSONを書きたい
alba 活発に開発中・高速 新規で本格的な整形が必要
active_model_serializers メンテナンスモード 既存資産の踏襲のみ

結論として、新規プロジェクトで active_model_serializers を選ぶ理由はほぼありません。0.10系の維持は続くものの新機能開発は止まっており、1.0のリリース予定もないためです。整形ロジックをしっかり書くなら、依存が少なく高速な alba が第一候補になります。--api では jbuilder がGemfileに入らないため、jbuilderを使う場合は明示的に追加します。過度な抽象化を避け、まずは標準の as_json で始めて、必要になった時点で alba へ寄せる進め方が無駄がありません。既存アプリで active_model_serializers を使い続ける場合の導入手順とバージョン境界は、ActiveModelSerializersの使い方|0.10.16の対応Railsと採用判断にまとめています。

JWTによるRails API認証の実装|トークンの発行と保存

APIはCookieセッションに頼れない場面が多く、トークンベースの認証が基本になります。デファクトはJWT(JSON Web Token)で、Railsでは devisedevise-jwt の組み合わせが手軽です。GSC上でも「railsapi 認証」「react railsapi 認証」の検索が多く、連携でつまずく中心がここです。

devise-jwt によるトークン発行

GemfileにDevise系を追加し、ユーザーモデルにJWT認証の設定を加えます。ログイン成功時にレスポンスヘッダの Authorization にトークンが載り、以降のリクエストはそのトークンで認証します。

# Gemfile
gem "devise"
gem "devise-jwt"

# app/models/user.rb
class User < ApplicationRecord
  devise :database_authenticatable, :registerable,
         :jwt_authenticatable, jwt_revocation_strategy: JwtDenylist
end

ログアウトで即時にトークンを無効化したい場合は、失効ストラテジ(上記の JwtDenylist など)を用意し、無効化したトークンを記録するテーブルを持たせます。デナイリスト方式では、トークンの識別子 jti と有効期限 exp を保存するテーブルを作ります。

# db/migrate/xxxx_create_jwt_denylists.rb
class CreateJwtDenylists < ActiveRecord::Migration[8.1]
  def change
    create_table :jwt_denylists do |t|
      t.string :jti, null: false
      t.datetime :exp, null: false
    end
    add_index :jwt_denylists, :jti
  end
end

ステートレスなJWTでも、こうした失効管理を入れることで「ログアウトしたのにトークンが使える」問題を防げます。

フロントでのトークン保存とセキュリティ

発行したJWTをReact側のどこに保存するかは、セキュリティ設計の分かれ目です。localStorage は実装が簡単な一方、JavaScriptから読めるためXSSでトークンを盗まれるリスクがあります。HttpOnly なCookieに置けばJavaScriptから隔離できますが、今度はCSRF対策が必要になります。どちらを選んでも守るべき点が残るため、XSS対策(入力のエスケープ・CSP)とCSRF対策はセットで検討してください。トークンを扱う際の攻撃面はCSRF(クロスサイトリクエストフォージェリ)とは何か?で前提を押さえておくと判断しやすくなります。少なくとも、有効期限(exp)は短くし、更新にはリフレッシュトークンを別管理するのが安全側の設計です。

React × Rails API連携|CORS設定とデータ取得

ReactアプリとRails APIは別ポート(別オリジン)で動くのが普通で、そのままではブラウザがCORS(Cross-Origin Resource Sharing)で通信をブロックします。連携が動かない原因の大半はこのCORS設定漏れです。ここが「react railsapi」系検索の実需の中心でありながら、解説が抜けがちな箇所です。

rack-cors でCORSを許可する

--api で作成したアプリには config/initializers/cors.rb が最初から生成されています(全体がコメントアウトされた状態)。Gemfileの rack-cors を有効化し、フロントのオリジンを明示して許可します。

# Gemfile
gem "rack-cors"

# config/initializers/cors.rb
Rails.application.config.middleware.insert_before 0, Rack::Cors do
  allow do
    origins "http://localhost:5173"   # ReactのViteが動くオリジン
    resource "*",
      headers: :any,
      expose: ["Authorization"],       # JWTをヘッダで返すため公開
      methods: [:get, :post, :put, :patch, :delete, :options]
  end
end

ポイントは3つです。第一に insert_before 0 で他ミドルウェアより先に動かすこと。第二に、JWTをヘッダで返すなら exposeAuthorization を含めること。第三に、本番では origins "*" を使わず具体的なドメインを列挙することです。認証情報付きのリクエスト(credentials: true)はワイルドカードのオリジンと併用できない仕様なので、この点でも本番のオリジンは限定する必要があります。

React側からのデータ取得とJWT付与

Reactからはfetchでもaxiosでも構いません。ログインで受け取ったトークンを、以降のリクエストの Authorization ヘッダに付けます。なお、Reactの新規作成に長く使われた create-react-app は開発が終了しており、現在はViteでの作成が標準です。

// 記事一覧の取得(fetch)
const token = localStorage.getItem("token");

const res = await fetch("http://localhost:3000/api/v1/articles", {
  headers: {
    "Content-Type": "application/json",
    "Authorization": `Bearer ${token}`,
  },
});
const articles = await res.json();

APIのURLはコンポーネントに直書きせず、Viteなら import.meta.env.VITE_API_URL のように環境変数へ逃がすと、開発と本番でオリジンを切り替えやすくなります。

DockerでReact+Rails API開発環境を構築する

「react railsapi docker」「独学 react railsapi」の検索が示すとおり、連携を学ぶ最初の壁は環境構築です。RubyとNode、PostgreSQLをOSに直接入れると、バージョン差で手順が崩れます。Docker Composeで役割ごとにコンテナを分ければ、どのOSでも同じ環境を再現でき、独学でも「動かない」で止まりにくくなります。ここは競合の解説が手薄な領域なので、そのまま使える最小構成を示します。

3コンテナ構成のdocker-compose.yml

リポジトリ直下に Rails APIを api/、Reactを front/ として並べたモノレポ構成を前提に、DB(PostgreSQL)、Rails API、Reactフロントの3サービスへ分けます。compose.yaml はその直下(api/front/ と同じ階層)に置きます。

# compose.yaml
services:
  db:
    image: postgres:16
    environment:
      POSTGRES_PASSWORD: password
    volumes:
      - pg_data:/var/lib/postgresql/data

  api:
    build: ./api
    command: bash -c "bin/rails db:prepare && bin/rails s -b 0.0.0.0"
    volumes:
      - ./api:/app
    ports:
      - "3000:3000"
    depends_on:
      - db

  front:
    build: ./front
    command: npm run dev -- --host
    volumes:
      - ./front:/app
    ports:
      - "5173:5173"

volumes:
  pg_data:

APIコンテナの database.yml では、接続先ホストを localhost ではなくサービス名の db にします。コンテナ間はサービス名で名前解決されるためです。ここを localhost のままにして接続できない、というのが最頻の詰まりどころです。Docker Composeの仕組みそのものはdocker-composeとは?複数コンテナをymlで定義し一括管理する仕組みで基礎から確認できます。

# api/config/database.yml(development)
development:
  adapter: postgresql
  host: db
  username: postgres
  password: password
  database: blog_api_development

docker compose up で3コンテナが起動し、APIは localhost:3000、Reactは localhost:5173 で待ち受けます。前段のCORS設定でReactのオリジン(5173)を許可していれば、この時点で連携が通ります。Dockerを使わずローカルに直接Rails環境を整える場合は、ruby on rails を vscode で開発する環境構築【2026年版】のDev Container構成も選択肢になります。

API仕様書の自動生成|rswagでSwagger/OpenAPIを出す

フロントと分業する以上、エンドポイントの仕様書は必須です。手書きは陳腐化するため、テストから仕様を生成する rswag を使うと、実装とドキュメントのずれを防げます。生成物はOpenAPI(Swagger)形式で、ブラウザ上のUIから叩いて確認できます。

# Gemfile
gem "rswag"
rails generate rswag:install

インストール後、リクエスト仕様(spec)にエンドポイントの入出力を記述し、rake rswag:specs:swaggerize を実行すると仕様ファイルが出力されます。仕様がテストとして走るため、レスポンスの構造を変えたのにドキュメントだけ古い、という食い違いが起きにくいのが利点です。

# spec/requests/api/v1/articles_spec.rb(抜粋)
require "swagger_helper"

RSpec.describe "api/v1/articles", type: :request do
  path "/api/v1/articles" do
    get "記事一覧を取得" do
      produces "application/json"
      response "200", "articles found" do
        run_test!
      end
    end
  end
end

Rails APIから外部サービスを呼ぶ|HTTPクライアントとWebhook

Rails APIは、自らエンドポイントを提供するだけでなく、外部サービスのAPIを呼び出すクライアントにもなります。決済(Stripe)、認証連携(OAuth)、通知(Slack)など、他システムとの橋渡しは実務で頻出します。

HTTPクライアントで外部APIを呼ぶ

外部APIへのリクエストは、標準の Net::HTTP のほか、FaradayHTTParty が使われます。ミドルウェアで再試行やロギングを差し込める Faraday は、外部連携が増えるほど扱いやすくなります。

# Gemfile
gem "faraday"

# 例:外部APIからデータ取得
conn = Faraday.new(url: "https://api.example.com")
res = conn.get("/data")
data = JSON.parse(res.body)

Webhook受信と非同期処理

外部サービスからのイベント通知(Webhook)は、POST を受けるエンドポイントを用意して処理します。受信ハンドラ内で重い処理を同期実行すると、送信元がタイムアウトして再送を繰り返す原因になります。受信は素早く 200 を返し、実処理はバックグラウンドジョブへ逃がすのが定石です。非同期処理の実装はSidekiqとは?Rubyで非同期処理を簡単に実現するバックグラウンドジョブツールが参考になります。

# config/routes.rb
post "webhooks/receive", to: "webhooks#receive"

# app/controllers/webhooks_controller.rb
class WebhooksController < ApplicationController
  def receive
    event = JSON.parse(request.body.read)
    ProcessWebhookJob.perform_later(event)   # 実処理は非同期へ
    head :ok
  end
end

よくある質問

Rails APIモードと通常モードはどちらを選ぶべきですか?

Railsがビューを描くなら通常モード、フロントをReactやモバイルアプリに任せてデータだけ返すならAPIモードです。画面を持たないバックエンドはAPIモードにする、と判断すれば大きく外しません。

Rails APIとReactは同じサーバーで動かせますか?

開発中は別ポート(例:Rails 3000、React 5173)で動かすのが一般的で、その場合はオリジンが異なるため rack-cors でのCORS許可が必須です。本番では同一ドメイン配下にリバースプロキシで束ねる構成も取れます。

Rails APIの認証は何を使えばよいですか?

トークンベースのJWTが標準的で、devise-jwt で手軽に実装できます。トークンの保存先(localStorageかHttpOnly Cookieか)はXSS/CSRFのリスクを踏まえて選び、有効期限は短くしてリフレッシュトークンで更新します。

APIモードはどのRailsバージョンから使えますか?

APIモード(--api / api_only)はRails 5.0で標準化されました。以降のバージョンで利用でき、本記事は執筆時点の最新安定版であるRails 8.1系を前提にしています。

JSONの整形にはどのライブラリがおすすめですか?

属性が少なければ標準の as_json で十分です。本格的な整形が必要になったら、開発が活発で高速な alba が有力候補です。active_model_serializers はメンテナンスモードのため、新規採用は避けるのが無難です。

関連記事

資料請求

RELATED POSTS 関連記事