Contents
Rails 7 API モードのセットアップ
Rails 7 の API モードは、不要なミドルウェアやビュー関連コードを除外した最小構成でプロジェクトを開始できる点が魅力です。このセクションでは Ruby と Rails のインストールから rails new my_api --api による雛形作成までの手順と、Rails 7.1 で変更されたデフォルト設定について解説します。
Rails のインストールと --api オプションの意味
まずは Ruby(3.2 系) と Rails 7 を導入し、API 用プロジェクトを生成します。
|
1 2 3 |
gem install rails -v "~>7.0" rails new my_api --api |
--apiは ActionController::API ベースのミドルウェアスタックを作成し、HTML ビューや Asset Pipeline の設定を省きます。- 生成されるディレクトリは
app/controllers,app/models,config/routes.rbだけになるため、軽量かつ起動が速くなります。
Zeitwerk と Hotwire の取り扱い(Rails 7.1 の変更点)
Rails 7 系ではデフォルトコードローダーとして Zeitwerk が採用されており、app/ 以下のファイル名とクラス名が一致すれば自動ロードされます。API モードでも同様に機能します。
重要:Rails 7.1 からは
gem 'hotwire-rails'がデフォルト Gemfile から除外されるため、API アプリを作成した直後に Hotwire 関連の設定が残っていることはありません。フロントエンドが別プロセスになるケースでは、特に意識せずにそのまま利用できます。
本番環境で必要なミドルウェアと CORS/HTTPS 設定
本番向け API では、セキュリティ・パフォーマンス確保のために追加ミドルウェアや HTTPS 強制が必須です。この章では除外されるミドルウェアの確認方法と、CORS とレートリミットを実装する手順を示します。
デフォルトで除外されるミドルウェア一覧
rails new --api が生成しない主なミドルウェアは次の通りです。
| 除外されたミドルウェア | 主な役割 |
|---|---|
ActionDispatch::Cookies |
Cookie の管理 |
ActionDispatch::Session::CookieStore |
セッション管理 |
Rack::MethodOverride |
HTTP メソッドの上書き |
Sprockets (Asset Pipeline) |
静的資産配信 |
API アプリでは Token 認証が主流なため Cookie/Session は不要です。その代わり CORS と レートリミット が重要になります。
rack‑cors と rack‑attack の導入手順
Gemfile に以下を追記し、バンドルします。
|
1 2 3 |
gem 'rack-cors' gem 'rack-attack' |
続いて config/application.rb 内でミドルウェアスタックに組み込みます。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
# config/application.rb module MyApi class Application < Rails::Application # --- CORS 設定 ------------------------------------------------- config.middleware.insert_before 0, Rack::Cors do allow do origins 'https://example.com' # 必要に応じて複数指定可 resource '/api/*', headers: :any, methods: %i[get post put patch delete options head], expose: ['Authorization'], max_age: 600 end end # --- Rack::Attack 設定 ----------------------------------------- config.middleware.use Rack::Attack end end |
rack-attack の具体的なルールは config/initializers/rack_attack.rb に記述します。
|
1 2 3 4 5 6 7 8 9 10 |
# config/initializers/rack_attack.rb class Rack::Attack throttle('req/ip', limit: 100, period: 1.minute) { |req| req.ip } self.blocklisted_response = lambda do |_env| [429, { 'Content-Type' => 'application/json' }, [{ error: 'Too Many Requests' }.to_json]] end end |
production.rb における HTTPS 強制設定例
本番環境では force_ssl を有効にし、HSTS ヘッダーを付与します。
|
1 2 3 4 5 6 7 |
# config/environments/production.rb Rails.application.configure do config.force_ssl = true config.ssl_options = { hsts: { expires: 30.days, preload: true } } config.log_level = :info # ログノイズ削減 end |
force_ssl が有効になると HTTP リクエストは自動的に HTTPS にリダイレクトされるため、クライアント側でも https:// エンドポイントを使用してください。
API 用コントローラと名前空間ルーティングの設計
大規模な API ではバージョニングや機能ごとの分割が保守性の鍵になります。この章では Api::V1 名前空間の作り方と、実務で推奨されるルーティング構造を示します。
名前空間ディレクトリと BaseController の作成
まずは名前空間用フォルダと共通ロジックを持つベースコントローラを生成します。
|
1 2 3 |
mkdir -p app/controllers/api/v1 rails g controller api/v1/base --skip-template-engine --skip-assets |
BaseController は ActionController::API を継承し、認証やエラーハンドリングの共通処理を配置します。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
# app/controllers/api/v1/base_controller.rb module Api module V1 class BaseController < ActionController::API before_action :authenticate_user! rescue_from ActiveRecord::RecordNotFound, with: :render_not_found rescue_from JWT::DecodeError, with: :render_unauthorized private def render_not_found(exception) render json: { error: exception.message }, status: :not_found end def render_unauthorized render json: { error: 'Invalid token' }, status: :unauthorized end end end end |
ルーティングベストプラクティス
config/routes.rb に名前空間とバージョニングを明示すると、将来的な拡張が楽になります。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
# config/routes.rb Rails.application.routes.draw do namespace :api, defaults: { format: :json } do namespace :v1 do # 認証系エンドポイント post 'auth/login', to: 'authentication#login' post 'auth/refresh', to: 'authentication#refresh' # Todo リソース(例) resources :todos, only: %i[index show create update destroy] end end # ヘルスチェック用エンドポイント(任意) get '/up', to: proc { [200, {}, ['OK']] } end |
defaults: { format: :json }により拡張子が省略されたリクエストでも JSON が返ります。- 認証は専用コントローラに分離し、ビジネスロジックと切り分けて保守性を高めます。
JSON シリアライザの選択肢と実装例
JSON の生成方法として代表的なのは ActiveModelSerializers (AMS) と Jbuilder です。ここでは両者の特徴比較と、シンプルな実装サンプルを示します。
AMS と Jbuilder の比較表
以下の観点で違いを整理しました。
| 項目 | ActiveModelSerializers (AMS) | Jbuilder |
|---|---|---|
| 宣言的 DSL | あり(serializer クラス) |
なし(Ruby スクリプト) |
| 再利用性 | 高い(同一 serializer を複数で使用可) | 中程度(部分テンプレートで対応) |
| パフォーマンス | 大量データ時に若干遅くなることがある | ビュー側ロジックなので高速 |
| カスタム属性 | attributes メソッドで簡単追加 |
Ruby で自由記述可能 |
| 学習コスト | DSL に慣れが必要 | 基本的な Rails ビューと同等 |
選択指針:共通レスポンスや入れ子構造が頻繁に出る中規模以上の API では AMS、シンプル CRUD のみであれば Jbuilder が手軽です。
AMS のシンプル実装例
Gemfile に AMS を追加し、シリアライザを生成します。
|
1 2 3 |
# Gemfile gem 'active_model_serializers', '~> 0.14.0' |
|
1 2 |
rails g serializer todo |
生成された app/serializers/todo_serializer.rb は次のようになります。
|
1 2 3 4 5 |
class TodoSerializer < ActiveModel::Serializer attributes :id, :title, :completed, :created_at, :updated_at belongs_to :user, serializer: UserSerializer # 必要に応じてネスト end |
コントローラ側では render json: だけで自動的に適用されます。
|
1 2 3 4 5 6 7 8 9 10 11 12 |
module Api::V1 class TodosController < BaseController def index render json: Todo.all end def show render json: Todo.find(params[:id]) end end end |
Jbuilder の基本例
Jbuilder は Rails に標準同梱されているため追加依存がありません。テンプレートは app/views/api/v1/todos/index.json.jbuilder に作成します。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
# app/views/api/v1/todos/index.json.jbuilder json.array! @todos do |todo| json.id todo.id json.title todo.title json.completed todo.completed json.created_at todo.created_at.iso8601 json.updated_at todo.updated_at.iso8601 json.user do json.id todo.user.id json.name todo.user.name end end |
コントローラはインスタンス変数を設定して render するだけです。
|
1 2 3 4 5 |
def index @todos = Todo.includes(:user).all render :index end |
認証・テスト・デプロイの実践フロー
本番レベルの API に求められるトークン認証、テスト自動化、Docker 化と CI/CD の流れを具体例で示します。JWT を手書きするパターンと devise_token_auth で簡易化するパターンの両方を紹介します。
カスタム JWT 実装(自前実装)
まずは JWT 用 gem を追加し、認証ロジックとトークンサービスクラスを作ります。
|
1 2 3 |
# Gemfile gem 'jwt' |
認証コントローラ例
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
module Api::V1 class AuthenticationController < BaseController skip_before_action :authenticate_user!, only: %i[login] def login user = User.find_by(email: params[:email]) if user&.valid_password?(params[:password]) token = JwtService.encode(user_id: user.id) render json: { token: token }, status: :ok else render json: { error: 'Invalid credentials' }, status: :unauthorized end end def refresh payload = JwtService.decode(request.headers['Authorization'].split.last) new_token = JwtService.encode(user_id: payload[:user_id]) render json: { token: new_token } rescue JWT::DecodeError render json: { error: 'Invalid token' }, status: :unauthorized end end end |
JwtService 実装
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
# app/services/jwt_service.rb class JwtService HMAC_SECRET = Rails.application.credentials.jwt_secret EXPIRATION_TIME = 24.hours.from_now.to_i def self.encode(payload) payload[:exp] = EXPIRATION_TIME JWT.encode(payload, HMAC_SECRET) end def self.decode(token) decoded = JWT.decode(token, HMAC_SECRET).first HashWithIndifferentAccess.new(decoded) end end |
BaseController に認証フィルタを追加
|
1 2 3 4 5 6 7 8 9 |
def authenticate_user! header = request.headers['Authorization'] token = header.split.last if header.present? decoded = JwtService.decode(token) @current_user = User.find(decoded[:user_id]) rescue JWT::DecodeError, ActiveRecord::RecordNotFound render json: { error: 'Unauthorized' }, status: :unauthorized end |
devise_token_auth を利用する場合
認証を外部ライブラリに委託したいときは以下の手順でセットアップします。
|
1 2 3 4 |
# Gemfile gem 'devise' gem 'devise_token_auth' |
|
1 2 3 4 5 |
rails generate devise:install rails generate devise User rails generate devise_token_auth:install User auth rails db:migrate |
生成されたエンドポイントは次の通りです。
| メソッド | パス | 内容 |
|---|---|---|
| POST | /auth |
ユーザー登録 |
| POST | /auth/sign_in |
ログイン(トークン発行) |
| DELETE | /auth/sign_out |
ログアウト |
この方法はセッション管理・トークンリフレッシュまで自動処理してくれるため、実装コストが大幅に削減できます。
RSpec と FactoryBot による API エンドポイントテスト例
テスト用 gem を追加し、認証付きリクエストスペックを作成します。
|
1 2 3 4 5 6 7 |
# Gemfile (test group) group :test do gem 'rspec-rails' gem 'factory_bot_rails' gem 'database_cleaner-active_record' end |
リクエストスペック
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
# spec/requests/api/v1/todos_spec.rb require 'rails_helper' RSpec.describe "Todos API", type: :request do let(:user) { create(:user) } let(:headers) { token = JwtService.encode(user_id: user.id) { 'Authorization' => "Bearer #{token}", 'Content-Type' => 'application/json' } } describe "GET /api/v1/todos" do before { create_list(:todo, 3, user: user) } it "returns all todos of the authenticated user" do get '/api/v1/todos', headers: headers expect(response).to have_http_status(:ok) json = JSON.parse(response.body) expect(json.size).to eq(3) end end end |
FactoryBot 定義例
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
# spec/factories/users.rb FactoryBot.define do factory :user do email { Faker::Internet.unique.email } password { 'password123' } end end # spec/factories/todos.rb FactoryBot.define do factory :todo do title { Faker::Lorem.sentence } completed { false } association :user end end |
テストは bundle exec rspec で実行し、CI パイプラインに組み込めばコードプッシュ時に自動検証が走ります。
Docker 化と GitHub Actions による CI/CD 構成
本番デプロイを自動化するための最小構成として Dockerfile、docker‑compose、GitHub Actions ワークフローを提示します。
Dockerfile(production 用)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
# ---- ベースイメージ ---- FROM ruby:3.2-slim # 必要パッケージインストール RUN apt-get update -qq && \ apt-get install -y build-essential libpq-dev nodejs && \ rm -rf /var/lib/apt/lists/* WORKDIR /app COPY Gemfile* ./ RUN bundle config set --local without 'development test' && \ bundle install --jobs 4 --retry 3 COPY . . ENV RAILS_ENV=production EXPOSE 3000 CMD ["rails", "server", "-b", "0.0.0.0"] |
docker‑compose(開発・テスト環境)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
version: '3.8' services: db: image: postgres:15-alpine environment: POSTGRES_USER: postgres POSTGRES_PASSWORD: password POSTGRES_DB: my_api_development volumes: - pg_data:/var/lib/postgresql/data web: build: . command: bundle exec rails s -p 3000 -b '0.0.0.0' environment: DATABASE_URL: postgres://postgres:password@db:5432/my_api_development RAILS_MASTER_KEY: ${RAILS_MASTER_KEY} ports: - "3000:3000" depends_on: - db volumes: pg_data: |
GitHub Actions(CI/CD)概要
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 |
name: CI on: push: branches: [ main ] pull_request: jobs: test: runs-on: ubuntu-latest services: postgres: image: postgres:15-alpine env: POSTGRES_USER: postgres POSTGRES_PASSWORD: password POSTGRES_DB: my_api_test ports: ['5432:5432'] options: >- --health-cmd "pg_isready" --health-interval 10s --health-timeout 5s --health-retries 5 steps: - uses: actions/checkout@v3 - name: Set up Ruby uses: ruby/setup-ruby@v1 with: ruby-version: '3.2' - name: Install dependencies run: | gem install bundler bundle config set without 'development' bundle install --jobs 4 --retry 3 - name: Prepare DB env: RAILS_ENV: test DATABASE_URL: postgres://postgres:password@localhost:5432/my_api_test run: | bin/rails db:create db:schema:load - name: Run RSpec run: bundle exec rspec deploy: needs: test runs-on: ubuntu-latest if: github.ref == 'refs/heads/main' steps: - uses: actions/checkout@v3 - name: Build & push Docker image uses: docker/build-push-action@v5 with: context: . push: true tags: ghcr.io/${{ github.repository }}:latest |
このワークフローはプッシュ時にテストを実行し、全テストが成功したら Docker イメージをビルド・プッシュします。Render、Fly.io などの PaaS にデプロイすれば、本番環境へ自動的に反映できます。
まとめ
rails new my_api --apiで最小構成の API アプリが即座に生成でき、Rails 7.1 ではhotwire-railsが除外される点を意識すれば不要な設定は省けます。- 本番環境では CORS (
rack-cors) とレートリミット (rack-attack) を追加し、HTTPS はforce_ssl+ HSTS で強制します。 - 名前空間 (
Api::V1) とnamespace :apiルーティングを採用すればバージョニングと保守性が向上します。 - JSON の生成は AMS(再利用性重視)か Jbuilder(シンプルさ重視)のどちらでも実装可能です。
- 認証は自前 JWT で柔軟に、または
devise_token_authで迅速に構築できます。 - RSpec + FactoryBot によるテストと Docker + GitHub Actions の CI/CD パイプラインを整えることで、コード品質とデプロイの安全性が確保されます。
これらの手順を踏めば、Rails 7 API モードで本番レベルのバックエンドサービスを短時間で構築・運用できるようになります。次は実際に「Todo API」サンプルを作成し、GitHub に公開してみましょう!