Ruby on Rails

Grape(Ruby)でREST APIを構築する実装ガイド|Rails統合からテストまで

Grapeは、Ruby でREST風のAPIを作ることだけに特化したフレームワークだ。Rack の上で動き、単体でもRailsアプリの一部としてもマウントできる。Railsのコントローラを介さずにエンドポイント・パラメータ検証・バージョニング・レスポンス整形をDSLで宣言できるため、JSON APIの記述量が小さく済む。この記事では、インストールから最小APIの起動、Railsへのマウント、パラメータのバリデーション、grape-entity によるレスポンス整形、APIのバージョニング、エラー処理、RSpecでのテストまでを実装コード付きで示す。バージョンは2026年時点の安定版であるGrape 3系(3.2系〜3.3系)とRuby 3系を前提にする。

まとめ:Grapeを使うと何がどう変わるか

  • 用途を絞る:GrapeはJSON APIに特化したRackミドルウェア。画面(HTML)を持つアプリではなく、API専用サーバやRailsのAPI層を切り出すときに向く。
  • 導入gem 'grape' を入れ、Grape::API を継承したクラスにエンドポイントを宣言する。Railsなら routes.rbmount するだけで組み込める。
  • 強みparams do requires end の宣言的バリデーション、version によるAPIバージョニング、rescue_from の集約エラー処理が標準で用意される。
  • レスポンス整形grape-entity でモデルとJSON出力を分離し、公開するフィールドをEntityクラスで管理する。
  • 採用判断:将来HTML画面やフォームが必要になるならRailsのフルスタック構成を選ぶ方が堅実。純粋なJSON APIで記述量とバージョニングを重視するならGrapeが効く。

Grapeとは:Ruby製のREST API特化フレームワーク

Grapeは2010年に登場した、Rubyで「REST風(REST-like)API」を作るためのフレームワークで、公式は「An opinionated framework for creating REST-like APIs in Ruby(意見を持ったフレームワーク)」と表現している。Railsのようにビュー・ORM・アセットまで面倒を見るのではなく、APIのエンドポイント定義・入力検証・出力整形・バージョニングだけを担当する点が最大の特徴だ。Google API設計ガイドで整理されているようなREST API設計の原則を、Rubyのコードとして素直に落とし込める。

RackアプリとしてもRailsマウントとしても動く構成

Grapeの実体はRackアプリケーションだ。Grape::API を継承したクラスがそのままRackのエンドポイントになるため、config.ru で単体起動もできれば、Railsの routes.rbmount して既存アプリのAPI層として同居させることもできる。RailsのルーターがリクエストをGrapeモジュールへ転送し、以降の処理はGrape側のDSLが引き受ける、という分担になる。この「Rackに乗る」性質のおかげで、Puma などのRackサーバやRackミドルウェア(認証・ロギング)をそのまま流用できる。

GrapeとRails APIモード・Sinatraの使い分け

フレームワーク選定では立場をはっきりさせたい。Grapeを選ぶべきなのは、返すものがJSONに限定され、バージョニングや宣言的なパラメータ検証を重視するAPI専用の局面だ。逆に、近い将来HTML画面・フォーム・管理画面が必要になる見込みがあるなら、最初からRailsのAPIモード(rails new --api)を選ぶ方が無難で、あとからビューやアセットパイプラインを足しやすい。RailsのAPI層だけを薄く切り出したい、複数バージョンのAPIを長く並走させる、といった要件が明確なときにGrapeの利点が出る。Rails単体のAPIモードでの実装手順(JWT認証・Docker環境まで)と比較し、画面要件の有無で振り分けるとよい。Sinatraはさらに低レベルで自由度が高い反面、パラメータ検証やバージョニングは自前になるため、API規約を強制したいチーム開発ではGrapeの「意見の強さ」が効く。

Grapeのインストールと最小APIの起動

GemfileにGrapeを追加して bundle install する。Grape 3系はRuby 3系を前提とし、Rackは2以上に対応する。最新の対応バージョンはRubyGemsとGitHubのReleasesで確認してほしい。

# Gemfile
gem 'grape'          # 2026年時点の安定版は3系(3.2系〜3.3系)

最小構成は、Grape::API を継承し、レスポンス形式を format :json で固定してエンドポイントを1つ宣言するだけだ。get ブロックの戻り値がそのままJSONにシリアライズされる。

# app/api/ping_api.rb
class PingApi < Grape::API
  format :json

  get :ping do
    { message: 'pong', time: Time.now.iso8601 }
  end
end

単体で動かすなら config.rurun PingApi と書き、rackup で起動する。GET /ping にアクセスすると {"message":"pong",...} が返る。Railsに組み込む場合は次章のマウントを使う。

RailsアプリへのマウントとAPIモジュールの配置

既存のRailsアプリにGrapeを載せるときは、Grapeのクラスを app/api 配下に置き、routes.rbmount する。Railsのオートロード(Zeitwerk)に載せるため、ディレクトリ名とクラス名を対応させる。

# app/api/v1/base.rb
module V1
  class Base < Grape::API
    version 'v1', using: :path      # 例: /api/v1/...
    format :json

    mount V1::Statuses              # リソースごとにクラスを分割してマウント
  end
end

# config/routes.rb
Rails.application.routes.draw do
  mount V1::Base => '/api'
end

この構成では、/api/v1/statuses のようなURLがGrape側で解決される。Railsのコントローラと違い、1つのAPIクラスに複数エンドポイントをまとめて宣言できるため、リソース単位でファイルを分けて mount で束ねるのが定石だ。app/api を新設した直後はオートロード対象に入るよう、開発環境を再起動しておく。Railsの開発環境自体の整備は VS CodeでのRuby on Rails開発環境の構築手順を参照。

エンドポイント定義とルーティング設計

Grapeのルーティングは、resourcenamespace でURLの階層を作り、その中で getpostputdelete を宣言する形をとる。パスの一部を変数として受け取るときは route_param を使う。

# app/api/v1/statuses.rb
module V1
  class Statuses < Grape::API
    resource :statuses do
      # GET /api/v1/statuses
      get do
        Status.all           # 素の実装。整形は後述のgrape-entityで行う
      end

      # GET /api/v1/statuses/:id
      route_param :id do
        get do
          Status.find(params[:id])
        end
      end

      # POST /api/v1/statuses
      post do
        Status.create!(text: params[:text])
      end
    end
  end
end

ルーティングをリソース単位のクラスに分割し、上位の Base クラスから mount で集約すると、URL階層とファイル構成が一致して見通しがよくなる。1クラスに全エンドポイントを詰め込むと肥大化するため、リソースが増えたらクラスを割るのが実務的だ。

パラメータの宣言とバリデーション

Grapeの強みは、各エンドポイントの直前に params do ... end ブロックで入力仕様を宣言できる点にある。requires は必須、optional は任意で、型・値域・許可値までまとめて検証できる。検証に失敗すると、Grapeが自動で 400 Bad Request とエラーメッセージを返すため、エンドポイント本体では正常系だけを書けばよい。

params do
  requires :text, type: String, allow_blank: false
  optional :status, type: String, values: %w[draft published]
  optional :page, type: Integer, default: 1
end
post do
  Status.create!(declared(params))    # declaredで宣言済みパラメータのみ取り出す
end

declared(params) を使うと、params do で宣言したキーだけを安全に取り出せるので、想定外のパラメータがモデルに渡るのを防げる。ネストしたパラメータは group/ブロックで階層化でき、配列やハッシュの検証もDSLで表現できる。

レスポンス整形とgrape-entity

モデルをそのまま返すと内部カラムまで露出してしまう。公開するフィールドを制御するには grape-entity gemを使い、モデルとJSON表現を分離する。Entityクラスに expose で公開項目を宣言し、エンドポイントでは present で紐づける。

# Gemfile
gem 'grape-entity'

# app/api/v1/entities/status_entity.rb
module V1
  module Entities
    class StatusEntity < Grape::Entity
      expose :id
      expose :text
      expose :created_at
      # expose しないカラムはJSONに出ない
    end
  end
end

# エンドポイント側
get do
  present Status.all, with: V1::Entities::StatusEntity
end

Entityを噛ませることで「DBのカラム構成」と「APIの契約」を切り離せる。カラム名を変えてもEntity側で吸収でき、条件付き表示(expose ..., if: ...)や別名(as:)も宣言的に書ける。JSONシリアライズはEntityが担うため、エンドポイント本体はオブジェクトを渡すだけで済む。

APIのバージョニング戦略

公開APIは後方互換を壊さずに進化させる必要がある。Grapeは version でAPIのバージョンを宣言でき、URLパス・リクエストヘッダ・パラメータのいずれかで振り分けられる。

方式 指定 クライアントの指定方法
パス using: :path URLに /v1/ を含める
ヘッダ using: :header Acceptヘッダでバージョンを送る
パラメータ using: :param クエリ文字列で指定

実務ではURLに版が現れて分かりやすい using: :path がよく使われる。新バージョンは V2::Base として別クラスに切り出し、旧バージョンと並走させる。破壊的変更は新バージョン側だけに入れ、旧バージョンは一定期間そのまま残してクライアントの移行を待つ、という運用がとりやすい。

エラー処理と例外の集約

例外処理は rescue_from で一元化する。特定の例外クラスを捕捉し、ステータスコードとJSONボディを統一形式で返せる。エンドポイント内で意図的にエラーを返したいときは error! を使う。

module V1
  class Base < Grape::API
    format :json

    rescue_from ActiveRecord::RecordNotFound do |e|
      error!({ error: 'not_found', message: e.message }, 404)
    end

    rescue_from Grape::Exceptions::ValidationErrors do |e|
      error!({ error: 'invalid_params', details: e.full_messages }, 400)
    end
  end
end

バリデーション失敗は Grape::Exceptions::ValidationErrors として捕捉できるため、エラーレスポンスの形をAPI全体でそろえられる。開発時は rescue_from :all で全例外を拾ってスタックトレースを確認できるが、本番では例外種別ごとに適切なステータスを返す実装に切り替える。

RSpecによるGrape APIのテスト

GrapeはRackアプリなので、rack-test を使ってHTTPリクエストをシミュレートし、RSpecでレスポンスを検証する。Rack::Test::Methodsinclude し、app にテスト対象のGrapeクラスを指定するのが基本形だ。

# spec/requests/statuses_spec.rb
require 'rails_helper'

RSpec.describe 'V1 Statuses API' do
  include Rack::Test::Methods
  def app = V1::Base       # ルートはBaseに集約されるためappはBaseを指す

  it 'returns statuses as json' do
    get '/api/v1/statuses'
    expect(last_response.status).to eq(200)
    expect(JSON.parse(last_response.body)).to be_an(Array)
  end

  it 'rejects blank text on create' do
    post '/api/v1/statuses', { text: '' }
    expect(last_response.status).to eq(400)
  end
end

正常系だけでなく、params do requires end で宣言したバリデーションが実際に 400 を返すか、rescue_from のエラー形式が期待どおりかまでテストしておくと、API契約の回帰を防げる。RSpec自体の役割や書き方は RSpecとテスト駆動開発の基礎を参照。

Grape 3系で押さえる変更点と本番運用

Grapeはメジャー更新でRackの新仕様に追随しており、既存コードを移行する際に注意すべき点がある。2026年時点の安定版は3系で、Ruby 3系が前提だ。

  • Rack 3のヘッダ小文字化:Rack 3環境ではレスポンスヘッダのキーが小文字(例 content-type)になる。ヘッダ名を大文字前提で判定しているコードは修正が必要(Grape 1.9.0以降の挙動)。
  • Digest認証の廃止:Rack 3で Rack::Auth::Digest が廃止されたことに伴い、GrapeのDigest認証も除去された。認証はトークン方式やRackミドルウェアで実装する。
  • Acceptヘッダの厳格化:Acceptヘッダの解釈がRFCに沿って厳密化されており、フォーマットネゴシエーションの挙動が旧版と変わる場合がある(例:拡張子やAcceptによる format の自動判定が、非対応のメディアタイプに対してより厳密に406を返す等)。

本番運用では、APIドキュメントを grape-swagger で自動生成し、OpenAPI(Swagger)仕様として公開すると、エンドポイント・パラメータ定義がコードと同期する。バージョンを上げる際は、公式の UPGRADING.mdCHANGELOG.md で破壊的変更を確認してから移行するのが安全だ。変動が速いバージョン情報は、GitHubのReleasesとRubyGemsで最新を確認してほしい。

よくある質問

Grapeは何に使うフレームワークですか?

RubyでREST風のJSON APIを作ることに特化したフレームワークです。エンドポイント定義・パラメータ検証・バージョニング・レスポンス整形をDSLで宣言でき、Rackの上で単体起動もRailsへのマウントもできます。HTML画面を持つWebアプリ全体を作る用途には向きません。

GrapeとRailsのAPIモードはどちらを使うべきですか?

返すものがJSONに限定され、複数バージョンの並走や宣言的なパラメータ検証を重視するならGrapeが向きます。将来HTML画面やフォームが必要になる見込みがあるなら、拡張しやすいRailsのAPIモードを選ぶ方が安全です。RailsのAPI層だけを薄く切り出したいときにGrapeの利点が出ます。

grape-entityは必須ですか?

必須ではありませんが、実務ではほぼ必要になります。モデルをそのまま返すと内部カラムまで露出するため、公開フィールドをEntityクラスで制御し、DBのカラム構成とAPIの契約を分離するのが安全です。単純なレスポンスならハッシュを直接返しても動きます。

Grapeの最新バージョンと対応するRubyは?

2026年時点の安定版はGrape 3系(3.2系〜3.3系)で、Ruby 3系が前提です。Rackは2以上に対応します。バージョンは更新が速いため、RubyGemsのページとGitHubのReleasesで最新を確認してください。

Grapeでバージョニングは必要ですか?

外部に公開するAPIでは推奨されます。version 'v1', using: :path のようにパスへ版を含めておくと、破壊的変更を新バージョン側だけに入れ、旧バージョンを並走させてクライアントの移行を待てます。社内限定で破壊的変更を一括反映できるなら省略も選択肢です。

関連記事

資料請求

RELATED POSTS 関連記事