RESTful API 完全ガイド - nyg1971/business_api GitHub Wiki
RESTful API 完全ガイド
🎯 REST API とは
基本概念
REST = REpresentational State Transfer
- リソース指向の設計思想
- HTTPメソッドでCRUD操作を表現
- ステートレス通信
- 統一されたインターフェース
API = Application Programming Interface
- アプリケーション間のデータやり取りの仕組み
- JSON形式でのデータ交換
- プラットフォーム非依存
🏗️ RESTful設計の原則
1. リソース指向
# ✅ 良い設計(リソース中心)
/api/v1/users # ユーザーリソース
/api/v1/posts # 投稿リソース
/api/v1/comments # コメントリソース
# ❌ 悪い設計(アクション中心)
/api/v1/getUsers # 動詞を含む
/api/v1/createPost # 動詞を含む
/api/v1/deleteComment # 動詞を含む
2. HTTPメソッドの意味
メソッド | 意味 | 例 | 安全性 | 冪等性 |
---|---|---|---|---|
GET | 取得 | GET /users |
✅ | ✅ |
POST | 作成 | POST /users |
❌ | ❌ |
PUT | 更新(全体) | PUT /users/1 |
❌ | ✅ |
PATCH | 更新(部分) | PATCH /users/1 |
❌ | ❌ |
DELETE | 削除 | DELETE /users/1 |
❌ | ✅ |
3. ステートレス通信
# 各リクエストが独立している
# サーバーはクライアントの状態を保持しない
# 認証情報は毎回送信(JWT トークンなど)
GET /api/v1/users
Authorization: Bearer eyJhbGciOiJIUzI1NiJ9...
📋 標準的なRESTful APIパターン
ユーザー管理API
# ユーザー一覧取得
GET /api/v1/users
Response: [{"id": 1, "name": "太郎"}, {"id": 2, "name": "花子"}]
# 特定ユーザー取得
GET /api/v1/users/1
Response: {"id": 1, "name": "太郎", "email": "[email protected]"}
# ユーザー作成
POST /api/v1/users
Body: {"name": "次郎", "email": "[email protected]"}
Response: {"id": 3, "name": "次郎", "email": "[email protected]"}
# ユーザー更新
PUT /api/v1/users/1
Body: {"name": "太郎", "email": "[email protected]"}
Response: {"id": 1, "name": "太郎", "email": "[email protected]"}
# ユーザー削除
DELETE /api/v1/users/1
Response: {"message": "User deleted successfully"}
認証API(今回実装したもの)
# ユーザー登録
POST /api/v1/auth/signup
Body: {"user": {"email": "[email protected]", "password": "password123"}}
Response: {"token": "eyJ...", "user": {"id": 1, "email": "[email protected]"}}
# ログイン
POST /api/v1/auth/login
Body: {"email": "[email protected]", "password": "password123"}
Response: {"token": "eyJ...", "user": {"id": 1, "email": "[email protected]"}}
# 認証済みユーザー情報取得
GET /api/v1/auth/me
Headers: Authorization: Bearer eyJ...
Response: {"user": {"id": 1, "email": "[email protected]", "role": "staff"}}
🔗 リソース間の関係表現
ネストしたリソース
# ユーザーの投稿一覧
GET /api/v1/users/1/posts
# 投稿のコメント一覧
GET /api/v1/posts/1/comments
# 特定ユーザーの特定投稿
GET /api/v1/users/1/posts/5
関連リソースの取得
# クエリパラメータでの関連取得
GET /api/v1/posts?include=author,comments
GET /api/v1/users?filter[role]=admin
GET /api/v1/posts?sort=created_at&order=desc
📊 HTTPステータスコードの使い分け
成功レスポンス (2xx)
200 OK # 正常取得・更新
201 Created # 正常作成
204 No Content # 正常削除(レスポンスボディなし)
クライアントエラー (4xx)
400 Bad Request # 不正なリクエスト
401 Unauthorized # 認証が必要
403 Forbidden # 権限不足
404 Not Found # リソースが存在しない
422 Unprocessable Entity # バリデーションエラー
サーバーエラー (5xx)
500 Internal Server Error # サーバー内部エラー
503 Service Unavailable # サービス利用不可
🎨 JSON レスポンス設計
成功レスポンスの例
// 単一リソース
{
"user": {
"id": 1,
"name": "太郎",
"email": "[email protected]",
"created_at": "2025-06-18T10:00:00Z"
}
}
// 複数リソース + メタデータ
{
"users": [
{"id": 1, "name": "太郎"},
{"id": 2, "name": "花子"}
],
"meta": {
"total": 100,
"page": 1,
"per_page": 20
}
}
エラーレスポンスの例
// バリデーションエラー
{
"errors": [
"Email has already been taken",
"Password is too short (minimum is 6 characters)"
]
}
// 認証エラー
{
"error": "Unauthorized",
"message": "Invalid or expired token"
}
// 詳細なエラー情報
{
"error": {
"code": "VALIDATION_FAILED",
"message": "The request data is invalid",
"details": {
"email": ["has already been taken"],
"password": ["is too short"]
}
}
}
🔧 Rails での RESTful API 実装
ルーティング設計
# config/routes.rb
Rails.application.routes.draw do
namespace :api do
namespace :v1 do
# 認証系
post 'auth/signup', to: 'auth#signup'
post 'auth/login', to: 'auth#login'
get 'auth/me', to: 'auth#me'
# リソース系
resources :users do
resources :posts, only: [:index, :show, :create]
end
resources :posts do
resources :comments, except: [:show]
end
end
end
end
コントローラー実装パターン
# app/controllers/api/v1/users_controller.rb
class Api::V1::UsersController < Api::V1::BaseController
before_action :set_user, only: [:show, :update, :destroy]
before_action :authorize_admin, only: [:destroy]
def index
users = User.all.order(:created_at)
render json: {
users: users.as_json(only: [:id, :name, :email, :created_at]),
meta: { total: users.count }
}
end
def show
render json: { user: @user.as_json(include: :profile) }
end
def create
user = User.new(user_params)
if user.save
render json: { user: user }, status: :created
else
render json: { errors: user.errors.full_messages },
status: :unprocessable_entity
end
end
def update
if @user.update(user_params)
render json: { user: @user }
else
render json: { errors: @user.errors.full_messages },
status: :unprocessable_entity
end
end
def destroy
@user.destroy
render json: { message: 'User deleted successfully' }, status: :ok
end
private
def set_user
@user = User.find(params[:id])
rescue ActiveRecord::RecordNotFound
render json: { error: 'User not found' }, status: :not_found
end
def user_params
params.require(:user).permit(:name, :email, :role)
end
end
🔐 認証・認可パターン
JWT認証実装
# app/controllers/api/v1/base_controller.rb
class Api::V1::BaseController < ApplicationController
before_action :authenticate_request
private
def authenticate_request
header = request.headers['Authorization']
if header.present?
token = header.split(' ').last
begin
decoded = JsonWebToken.decode(token)
@current_user = User.find(decoded[:user_id])
rescue JWT::DecodeError
render json: { error: 'Unauthorized' }, status: :unauthorized
end
else
render json: { error: 'Unauthorized' }, status: :unauthorized
end
end
def current_user
@current_user
end
end
権限制御
# 役職別アクセス制御
def authorize_admin
unless current_user.admin?
render json: { error: 'Forbidden' }, status: :forbidden
end
end
def authorize_owner_or_admin(resource)
unless resource.user == current_user || current_user.admin?
render json: { error: 'Forbidden' }, status: :forbidden
end
end
📡 API バージョニング
URL バージョニング(推奨)
/api/v1/users # バージョン1
/api/v2/users # バージョン2(新機能追加)
ヘッダーバージョニング
GET /api/users
Accept: application/vnd.api+json;version=1
後方互換性の保持
# v1: 従来の形式
{
"id": 1,
"name": "太郎"
}
# v2: 新しい形式(v1も継続サポート)
{
"id": 1,
"name": "太郎",
"first_name": "太郎",
"last_name": "田中",
"full_name": "田中太郎" # 新フィールド
}
🧪 API テスト
RSpec Request Spec例
# spec/requests/api/v1/users_spec.rb
RSpec.describe 'Api::V1::Users', type: :request do
let(:user) { create(:user) }
let(:admin) { create(:user, :admin) }
let(:headers) { { 'Authorization' => "Bearer #{jwt_token(user)}" } }
describe 'GET /api/v1/users' do
it 'returns users list' do
create_list(:user, 3)
get '/api/v1/users', headers: headers
expect(response).to have_http_status(:ok)
expect(JSON.parse(response.body)['users']).to have(4).items
end
end
describe 'POST /api/v1/users' do
let(:valid_params) do
{
user: {
name: 'New User',
email: '[email protected]'
}
}
end
context 'with valid parameters' do
it 'creates a new user' do
post '/api/v1/users', params: valid_params, headers: headers, as: :json
expect(response).to have_http_status(:created)
expect(User.count).to eq(2)
end
end
context 'with invalid parameters' do
let(:invalid_params) { { user: { name: '' } } }
it 'returns validation errors' do
post '/api/v1/users', params: invalid_params, headers: headers, as: :json
expect(response).to have_http_status(:unprocessable_entity)
expect(JSON.parse(response.body)).to have_key('errors')
end
end
end
end
📚 フロントエンド連携例
JavaScript/React での使用
// APIクライアントクラス
class ApiClient {
constructor(baseURL = '/api/v1') {
this.baseURL = baseURL;
this.token = localStorage.getItem('token');
}
async request(endpoint, options = {}) {
const url = `${this.baseURL}${endpoint}`;
const config = {
headers: {
'Content-Type': 'application/json',
...(this.token && { 'Authorization': `Bearer ${this.token}` }),
...options.headers,
},
...options,
};
const response = await fetch(url, config);
if (!response.ok) {
throw new Error(`API Error: ${response.status}`);
}
return response.json();
}
// 認証
async login(email, password) {
const response = await this.request('/auth/login', {
method: 'POST',
body: JSON.stringify({ email, password }),
});
this.token = response.token;
localStorage.setItem('token', this.token);
return response;
}
// CRUD操作
async getUsers() {
return this.request('/users');
}
async createUser(userData) {
return this.request('/users', {
method: 'POST',
body: JSON.stringify({ user: userData }),
});
}
async updateUser(id, userData) {
return this.request(`/users/${id}`, {
method: 'PUT',
body: JSON.stringify({ user: userData }),
});
}
async deleteUser(id) {
return this.request(`/users/${id}`, {
method: 'DELETE',
});
}
}
// 使用例
const api = new ApiClient();
// ログイン
await api.login('[email protected]', 'password');
// ユーザー一覧取得
const users = await api.getUsers();
// ユーザー作成
const newUser = await api.createUser({
name: 'New User',
email: '[email protected]'
});
🎯 RESTful API設計のベストプラクティス
1. 一貫性のある命名
# ✅ 推奨
/api/v1/users
/api/v1/user_profiles
/api/v1/blog_posts
# ❌ 非推奨
/api/v1/Users # 大文字
/api/v1/userProfiles # キャメルケース
/api/v1/blog-posts # ハイフン
2. 適切なHTTPメソッド使用
# ✅ 正しい使い方
GET /posts # 安全で冪等
POST /posts # 非安全、非冪等
PUT /posts/1 # 非安全、冪等
DELETE /posts/1 # 非安全、冪等
# ❌ 間違った使い方
GET /posts/delete/1 # GETで削除は不適切
POST /posts/1 # 更新にPOSTは不適切
3. 適切なステータスコード
# 成功時
render json: data, status: :ok # 200
render json: data, status: :created # 201
head :no_content # 204
# エラー時
render json: errors, status: :bad_request # 400
render json: errors, status: :unauthorized # 401
render json: errors, status: :forbidden # 403
render json: errors, status: :not_found # 404
render json: errors, status: :unprocessable_entity # 422
4. エラーハンドリングの統一
# app/controllers/api/v1/base_controller.rb
class Api::V1::BaseController < ApplicationController
rescue_from ActiveRecord::RecordNotFound, with: :record_not_found
rescue_from ActiveRecord::RecordInvalid, with: :record_invalid
rescue_from ActionController::ParameterMissing, with: :parameter_missing
private
def record_not_found(error)
render json: {
error: 'Record not found',
message: error.message
}, status: :not_found
end
def record_invalid(error)
render json: {
errors: error.record.errors.full_messages
}, status: :unprocessable_entity
end
def parameter_missing(error)
render json: {
error: 'Missing required parameter',
parameter: error.param
}, status: :bad_request
end
end
ポイント
技術要素
-
RESTful API設計原則
- リソース指向設計
- 適切なHTTPメソッド使用
- ステートレス通信
-
JSON API実装
- 構造化されたレスポンス設計
- エラーハンドリングの統一
- バリデーション実装
-
セキュリティ対策
- JWT認証実装
- CORS設定
- Strong Parameters
- 役職別権限制御
-
テスト実装
- Request Spec作成
- 認証フローテスト
- エラーケーステスト
RESTful APIは現代のWebアプリケーション開発における標準的な設計手法で、マイクロサービス、SPA、モバイルアプリなど幅広い分野で活用されている。 今回実装したシステムは、実際の業務で求められるAPI開発の実装を包括的にカバーしています。