本記事では、RailsでAPIを構築する際にGrapeを使うメリットや実装手順を解説
RailsのコントローラーのみによるAPI構築の限界
ビューとAPIレスポンス(Json)の混在
RailsのコントローラーとモデルだけでAPIを構築することは可能だが、以下のような制限や課題がある。
RailsのコントローラーはHTMLビューをレンダリングすることを前提に設計されており、API専用に設計されていないため、API用のロジックを追加した場合、ビューとレスポンスの混在という問題が発生する。
class UsersController < ApplicationController
def index
respond_to do |format|
format.html { render :index }
format.json { render json: User.all }
end
end
end
こうしたコードは小規模なアプリケーションでは問題ないが、規模が大きくなると管理が難しくなる。またAPIのバージョニングを手動で実装する必要があり、バージョニングの管理が困難になる。
APIバージョニング手動管理の煩雑さ
APIのバージョニング(v1, v2など)を導入して管理するのは、既存のクライアント(アプリケーションやユーザー)に影響を与えずにAPIの仕様を変更する必要がある場合。
例えば、ユーザー情報を返すAPIレスポンスの場合で、v2ではv1では返していなかった追加の情報(age
やaddress
)が必要になり、レスポンス形式を変更する場合、v1のクライアントは新しいフィールドを想定していないため、互換性のない変更になる。なので、v1を維持しつつ、v2として新しい仕様を導入することで、互換性を保ちながら移行可能になる。
上記のような課題を解決する方法としてGrapeというライブラリがある。
Grape: Rails API専用に設計されたDSL
GrapeはRailsAPI専用に設計されたDSL(ドメイン固有言語:特定の作業や問題解決を目的に設計されたプログラミング言語)で、RESTfulなAPIを効率的に構築できる。
https://github.com/ruby-grape/grape
Grapeが適しているケース
- サーバーサイドをAPI専用として利用する。
- モバイルアプリやフロントエンド(React/Vue.js)からのJSONリクエストを主に処理する。
- APIバージョン管理が必要。
- RESTfulな設計を重視したい。
Grapeファイル構成例
app/
├── api/
│ ├── base_api.rb # Grape APIのベースクラス
│ ├── app_api/
│ │ ├── v1/
│ │ │ ├── users/
│ │ │ │ ├── data.rb # ユーザー関連のデータ取得API
│ │ │ │ ├── profile.rb # ユーザープロファイル関連のAPI
│ │ │ ├── root.rb # V1 APIのエントリーポイント
│ │ ├── v2/
│ ├── tasks/
│ │ ├── management.rb # タスク管理のAPI
│ ├── root.rb # V2 APIのエントリーポイント
config/
└── routes.rb # APIをマウントするルート設定
Grape実装例
app/api/base_api.rb
全APIで共通の処理を定義するクラス。
class BaseAPI < Grape::API
format :json
prefix :api
helpers do
def current_user
# 認証トークンから現在のユーザーを取得
User.find_by(auth_token: headers['Authorization'])
end
end
rescue_from ActiveRecord::RecordNotFound do |e|
error!({ message: 'Resource not found', details: e.message }, 404)
end
rescue_from :all do |e|
error!({ message: 'Internal server error', details: e.message }, 500)
end
end
app/api/app_api/v1/users/data.rb
ユーザー関連のデータ取得エンドポイント。
# app/api/app_api/v1/users/data.rb
module AppAPI
module V1
module Users
class Data < Grape::API
resource :users do
# GETリクエスト: ユーザー一覧を取得
desc 'Get all users'
get do
present User.all, with: Entities::User
end
# POSTリクエスト: 新しいユーザーを作成
desc 'Create a new user'
params do
requires :name, type: String, desc: 'User name'
requires :email, type: String, desc: 'User email'
end
post do
user = User.create!(declared(params))
present user, with: Entities::User
end
end
end
end
end
end
app/api/app_api/v1/root.rb
app/api/api/v1/root.rb
を作成し、v1
のAPI全体を管理する
class AppAPI::V1::Root < Grape::API
mount AppAPI::V1::Users::Data
end
config/routes.rb
APIをマウントするルート設定。これにより、/api/v1/users
などのエンドポイントが有効になる
Rails.application.routes.draw do
mount AppAPI::V1::Root => '/api/v1'
mount AppAPI::V2::Root => '/api/v2'
end
フロントエンド(React)からAPIエンドポイントを呼び出す
以下は、ReactでAxiosを使用して/api/v1/users
エンドポイントを呼び出す例。
Axiosインスタンスの作成
APIのベースURLを設定したAxiosインスタンスを作成する。
src/api/axiosInstance.js
import Axios from 'axios';
const axiosInstance = Axios.create({
baseURL: '/api/v1', // APIのベースURL
headers: {
'Content-Type': 'application/json',
},
});
export default axiosInstance;
APIリクエストの関数作成
GETリクエストとPOSTリクエストを行う関数を定義する。
src/api/usersApi.js
import axiosInstance from './axiosInstance';
// ユーザー一覧を取得
export const fetchUsers = async () => {
try {
const response = await axiosInstance.get('/users');
return response.data; // サーバーから返されたデータ
} catch (error) {
console.error('Error fetching users:', error);
throw error;
}
};
// 新しいユーザーを作成
export const createUser = async (user) => {
try {
const response = await axiosInstance.post('/users', user);
return response.data; // 作成されたユーザー情報
} catch (error) {
console.error('Error creating user:', error);
throw error;
}
};
Reactコンポーネントでの利用
Reactコンポーネント内でfetchUsers
とcreateUser
を使用する。
src/components/UserList.js