方言を話すおしゃべり猫型ロボット『ミーア』をリリースしました(こちらをクリック)

Grapeを用いたRails APIの構築:Reactフロントエンドからの呼び出し方法

grape-rails-api
この記事は約12分で読めます。

 本記事では、RailsでAPIを構築する際にGrapeを使うメリットや実装手順を解説

RailsのコントローラーのみによるAPI構築の限界

ビューとAPIレスポンス(Json)の混在

RailsのコントローラーとモデルだけでAPIを構築することは可能だが、以下のような制限や課題がある。

RailsのコントローラーはHTMLビューをレンダリングすることを前提に設計されており、API専用に設計されていないため、API用のロジックを追加した場合、ビューとレスポンスの混在という問題が発生する。

Ruby
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では返していなかった追加の情報(ageaddress)が必要になり、レスポンス形式を変更する場合、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ファイル構成例

Ruby
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で共通の処理を定義するクラス。

Ruby
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

ユーザー関連のデータ取得エンドポイント。

Ruby
# 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全体を管理する

Ruby
class AppAPI::V1::Root < Grape::API
  mount AppAPI::V1::Users::Data
end

config/routes.rb

APIをマウントするルート設定。これにより、/api/v1/usersなどのエンドポイントが有効になる

Ruby
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

TypeScript
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

TypeScript
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コンポーネント内でfetchUserscreateUserを使用する。

src/components/UserList.js

TypeScriptimport React, { useEffect, useState } from 'react'; import { fetchUsers, createUser } from '../api/usersApi'; const UserList = () => { const [users, setUsers] = useState([]); const [newUser, setNewUser] = useState({ name: '', email: '' }); // ユーザー一覧を取得 useEffect(() => { const getUsers = async () => { try { const data = await fetchUsers(); setUsers(data); } catch (error) { console.error('Failed to fetch users:', error); } }; getUsers(); }, []); // 新しいユーザーを作成 const handleCreateUser = async () => { try { const createdUser = await createUser(newUser); setUsers((prevUsers) => [...prevUsers, createdUser]); setNewUser({ name: '', email: '' }); // フォームをリセット } catch (error) { console.error('Failed to create user:', error); } }; return ( <div> <h1>User List</h1> <ul> {users.map((user) => ( <li key={user.id}> {user.name} - {user.email} </li> ))} </ul> <h2>Add New User</h2> <input type="text" placeholder="Name" value={newUser.name} onChange={(e) => setNewUser({ ...newUser, name: e.target.value })} /> <input type="email" placeholder="Email" value={newUser.email} onChange={(e) => setNewUser({ ...newUser, email: e.target.value })} /> <button onClick={handleCreateUser}>Add User</button> </div> ); }; export default UserList; 全体の流れ

バックエンド(Grape API)

  • /api/v1/usersエンドポイントを定義し、GETリクエストでユーザー一覧を取得、POSTリクエストで新しいユーザーを作成。

Axiosインスタンス

  • axiosInstanceを作成して、APIへの共通設定を定義。

ReactでAPIを利用

  • fetchUserscreateUser関数を使ってデータを取得・送信。
  • コンポーネント内で状態管理(useState)を行い、動的なUIを構築。
タイトルとURLをコピーしました