REST の基本原則 — リソース指向で考える
「リソース」という考え方
「サクラさん、まず既存のコードを見てほしい」
先輩エンジニアのヒロキが開いたコードに、サクラは違和感を覚えた。
GET /getUsers
POST /createUser
GET /getUserById?id=1
POST /deleteUser
「これ...動くんですけど、なんか変ですよね?」
ヒロキは頷いた。「そう。これは動詞ベースの設計だ。RESTはリソース指向で考える」
RESTとは何か
REST(Representational State Transfer)は、2000年にRoy Fieldingが博士論文で提唱したWebアーキテクチャのスタイルだ。RESTfulなAPIには6つの制約がある。
最も重要なのは統一インターフェースだ。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
endrails 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ステータスコードの使い方
ステータスコードで操作結果を正確に伝える。
# 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 # 422INFO
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の力だ。
次章では、リクエストとレスポンスの形式を詳しく設計する。