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.rbでmountするだけで組み込める。 - 強み:
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.rb に mount して既存アプリの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.ru に run PingApi と書き、rackup で起動する。GET /ping にアクセスすると {"message":"pong",...} が返る。Railsに組み込む場合は次章のマウントを使う。
RailsアプリへのマウントとAPIモジュールの配置
既存のRailsアプリにGrapeを載せるときは、Grapeのクラスを app/api 配下に置き、routes.rb で mount する。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のルーティングは、resource・namespace でURLの階層を作り、その中で get・post・put・delete を宣言する形をとる。パスの一部を変数として受け取るときは 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::Methods を include し、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.md と CHANGELOG.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 のようにパスへ版を含めておくと、破壊的変更を新バージョン側だけに入れ、旧バージョンを並走させてクライアントの移行を待てます。社内限定で破壊的変更を一括反映できるなら省略も選択肢です。