mybook

REST の基本原則 — リソース指向で考える

「リソース」という考え方

「サクラさん、まず既存のコードを見てほしい」

先輩エンジニアのヒロキが開いたコードに、サクラは違和感を覚えた。

GET /getUsers
POST /createUser
GET /getUserById?id=1
POST /deleteUser

「これ...動くんですけど、なんか変ですよね?」

ヒロキは頷いた。「そう。これは動詞ベースの設計だ。RESTはリソース指向で考える」

RESTとは何か

REST(Representational State Transfer)は、2000年にRoy Fieldingが博士論文で提唱したWebアーキテクチャのスタイルだ。RESTfulなAPIには6つの制約がある。

Loading diagram...

最も重要なのは統一インターフェースだ。URLはリソースを表し、HTTPメソッドが操作を表す。

リソース指向のURI設計

名詞で考える

# NG: 動詞ベース
GET /getUsers
POST /createUser
DELETE /deleteUser/1

# OK: リソースベース(名詞)
GET /users
POST /users
DELETE /users/1

リソースはすべて名詞で表現する。複数形を基本とする(user ではなく users)。

階層構造

# ユーザー一覧
GET /users

# 特定ユーザー
GET /users/:id

# ユーザーの記事一覧(ネスト)
GET /users/:user_id/articles

# ユーザーの特定記事
GET /users/:user_id/articles/:id

WARNING

ネストは2レベルまでにする。/users/1/articles/2/comments/3/likes のように深くなると管理が困難になります。

良いURI設計のルール

ルール
小文字を使う/users/Users
ハイフンで区切る/user-profiles/user_profiles
拡張子を使わない/users/users.json
バージョンを含める/v1/users
クエリは検索・フィルタ/users?status=active

HTTPメソッドの正しい使い方

HTTPメソッドはCRUD操作に対応している。

HTTPメソッド操作冪等性安全性
GET取得ありあり
POST作成なしなし
PUT全更新ありなし
PATCH部分更新なしなし
DELETE削除ありなし

冪等性: 同じリクエストを何度送っても結果が変わらない
安全性: サーバーの状態を変更しない

GETは副作用なし

# NG: GETで状態変更
GET /users/1/activate  # 悪い設計
 
# OK: PATCHで状態変更
PATCH /users/1
{ "status": "active" }

POSTとPUTの違い

# POST: 新規作成(IDはサーバーが決める)
POST /articles
→ 201 Created, Location: /articles/42

# PUT: 全体置き換え(IDをクライアントが指定)
PUT /articles/42
→ 200 OK または 204 No Content

# PATCH: 部分更新
PATCH /articles/42
{ "title": "新しいタイトル" }
→ 200 OK

RailsでのRESTful設計

routes.rb

# config/routes.rb
Rails.application.routes.draw do
  namespace :api do
    namespace :v1 do
      resources :users do
        resources :articles, only: [:index, :show]
      end
      resources :articles
    end
  end
end

rails routes を実行すると、自動生成されたURLを確認できる。

GET    /api/v1/users              users#index
POST   /api/v1/users              users#create
GET    /api/v1/users/:id          users#show
PATCH  /api/v1/users/:id          users#update
PUT    /api/v1/users/:id          users#update
DELETE /api/v1/users/:id          users#destroy

コントローラー

# app/controllers/api/v1/users_controller.rb
module Api
  module V1
    class UsersController < ApplicationController
      before_action :set_user, only: [:show, :update, :destroy]
 
      # GET /api/v1/users
      def index
        @users = User.all
        render json: @users
      end
 
      # GET /api/v1/users/:id
      def show
        render json: @user
      end
 
      # POST /api/v1/users
      def create
        @user = User.new(user_params)
        if @user.save
          render json: @user, status: :created,
                 location: api_v1_user_url(@user)
        else
          render json: { errors: @user.errors }, status: :unprocessable_entity
        end
      end
 
      # PATCH /api/v1/users/:id
      def update
        if @user.update(user_params)
          render json: @user
        else
          render json: { errors: @user.errors }, status: :unprocessable_entity
        end
      end
 
      # DELETE /api/v1/users/:id
      def destroy
        @user.destroy
        head :no_content
      end
 
      private
 
      def set_user
        @user = User.find(params[:id])
      end
 
      def user_params
        params.require(:user).permit(:name, :email)
      end
    end
  end
end

モデル

# app/models/user.rb
class User < ApplicationRecord
  has_many :articles, dependent: :destroy
 
  validates :name, presence: true
  validates :email, presence: true, uniqueness: true, format: { with: URI::MailTo::EMAIL_REGEXP }
end

よくある設計ミス

コレクションとシングルトン

# コレクション(複数)
GET /users      → ユーザー一覧
POST /users     → ユーザー作成

# シングルトン(現在のユーザー)
GET /me         → 自分のプロフィール(/users/:id の代わり)
PATCH /me       → 自分の情報更新

/me のようなシングルトンリソースは、認証済みユーザー自身を操作するときに使う。

アクションが動詞になりたがる場合

注文を確定する、パスワードをリセットするなど、どうしても動詞的な操作が必要なときは「サブリソース」として表現する。

# NG
POST /orders/1/confirm
POST /users/1/reset-password

# OK: 状態変更をリソースとして表現
POST /orders/1/confirmations      # 確認リソースを作成
POST /users/1/password-resets     # パスワードリセットリソースを作成

# または、状態をPATCHで更新
PATCH /orders/1
{ "status": "confirmed" }

HTTPステータスコードの使い方

ステータスコードで操作結果を正確に伝える。

Loading diagram...
# Railsでのステータスコード使用例
render json: @user, status: :ok              # 200
render json: @user, status: :created         # 201
head :no_content                              # 204
render json: errors, status: :bad_request    # 400
render json: errors, status: :unauthorized   # 401
render json: errors, status: :not_found      # 404
render json: errors, status: :unprocessable_entity  # 422

INFO

Railsは :ok, :created のようなシンボルでステータスコードを指定できます。数字より読みやすくミスが減ります。

AWS API Gatewayのリソース設定

# serverless.yml または CloudFormation
Resources:
  UsersResource:
    Type: AWS::ApiGateway::Resource
    Properties:
      RestApiId: !Ref ApiGateway
      ParentId: !GetAtt ApiGateway.RootResourceId
      PathPart: users
 
  UserResource:
    Type: AWS::ApiGateway::Resource
    Properties:
      RestApiId: !Ref ApiGateway
      ParentId: !Ref UsersResource
      PathPart: "{id}"
 
  GetUsersMethod:
    Type: AWS::ApiGateway::Method
    Properties:
      RestApiId: !Ref ApiGateway
      ResourceId: !Ref UsersResource
      HttpMethod: GET
      AuthorizationType: NONE
      Integration:
        Type: HTTP_PROXY
        IntegrationHttpMethod: GET
        Uri: !Sub "http://${LoadBalancer.DNSName}/api/v1/users"

サクラの気づき

「なるほど、URLは住所で、HTTPメソッドが動詞なんだ」

サクラはコードを書き直した。動詞が消え、名詞だけが残った。すっきりしたルーティングは、まるで整理された本棚のようだった。

一貫したルールに従うと、APIを初めて見た開発者でも直感的に使える。それがRESTの力だ。

次章では、リクエストとレスポンスの形式を詳しく設計する。