Rails APIモードのルーティングにおけるformatの扱い方
2020.10.14
RailsでAPIモードを使う場合のルーティング、特にformatの設定について見直してみました。
Railsのルーティング
Railsのルーティングについては公式ドキュメントを参考にすることができ、その他にも「Rails ルーティング」のように検索すればたくさんの情報が見つかります。
- Railsガイド: Rails のルーティング
APIモードを使う場合でも書き方は同じなので困ることはないのですが、あらためて format
の扱いが気になったので調べてみました。
以降の内容は基本的にJSONを扱うAPIサーバーを実装する前提とし、動作確認は以下のバージョンで行いました。
- ruby: 2.7.1
- rails: 6.0.3.3
ルーティングでformatを設定する
ベースとして以下のようなルーティングを定義します。
Rails.application.routes.draw do
namespace :v1 do
resources :users
end
end
# rails routesの出力結果(一部省略)
# v1_users GET /v1/users(.:format) v1/users#index
# POST /v1/users(.:format) v1/users#create
# v1_user GET /v1/users/:id(.:format) v1/users#show
# PATCH /v1/users/:id(.:format) v1/users#update
# PUT /v1/users/:id(.:format) v1/users#update
# DELETE /v1/users/:id(.:format) v1/users#destroy
また v1/users#show
は以下のように定義されているとします。
def show
render json: User.find(params[:id])
end
このとき3つのパターンでリクエストした結果を以下に示します。
(ヘッダ情報は一部省略しています)
# パターン1
$ curl -i localhost:3000/v1/users/1
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
{"id":1,"email":"email"}
# パターン2
$ curl -i localhost:3000/v1/users/1.json
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
{"id":1,"email":"email"}
# パターン3
$ curl -i localhost:3000/v1/users/1.html
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
{"id":1,"email":"email"}
パターン1とパターン2は違和感がないように見えますが、パターン3では .html
とリクエストしているにも関わらずJSONのレスポンスを返しています。
アクションで render json:
としているので、formatによらずコントローラーはJSONを返すという挙動になっています。
formatを指定(defaultスコープ)
ルーティングを namespace :v1, default: { format: 'json' } do
と変更。
- Railsガイド: デフォルト設定を定義する
Rails.application.routes.draw do
namespace :v1, default: { format: 'json' } do
resources :users
end
end
同様に3つのパターンでリクエストした結果は指定しない場合と変わりませんでした。
ただし head
メソッドを使ってレスポンスを返す場合はヘッダが変わりました。
# パターン1
$ curl -i localhost:3000/v1/users/1
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
{"id":1,"email":"email"}
# パターン2
$ curl -i localhost:3000/v1/users/1.json
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
{"id":1,"email":"email"}
# パターン3
$ curl -i localhost:3000/v1/users/1.html
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
{"id":1,"email":"email"}
formatを指定
ルーティングを namespace :v1, format: 'json' do
と変更。
Rails.application.routes.draw do
namespace :v1, format: 'json' do
resources :users
end
end
同様に3つのパターンでリクエストした結果、パターン3ではRoutingErrorが発生し404となりました。
# パターン1
$ curl -i localhost:3000/v1/users/1
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
{"id":1,"email":"email"}
# パターン2
$ curl -i localhost:3000/v1/users/1.json
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
{"id":1,"email":"email"}
# パターン3
$ curl -i localhost:3000/v1/users/1.html
HTTP/1.1 404 Not Found
# RoutingErrorが発生
結局どれにすればいいのか?
参考にさせていただいた以下の記事にも書かれていますが、JSONを扱うAPIであれば本記事で最後に紹介した format: 'json'
とするのが良いように思いました。
- scramble cadenza: respond_to vs routes 指定の format
例のように .html
やその他のフォーマットを意図せず(あるいは悪意を持って)リクエストされたときに404を返すほうが挙動として望ましい気がします。
例外的に他のフォーマットを許容したい場合は、個別のルーティングにもブロックにも format
を設定することで上書きが可能です。
または /json|yaml/
のような正規表現も可能なようです。