fukuoka.ex/kokura.exのpiacereです
ご覧いただいて、ありがとうございます
前作のPhoenix軽量APIは、シンプルなリテラルやElixir実行結果だけを返す簡易APIまでの実装だったので、これをREST API化したいと思います
今回は、まずGET、つまり「参照系REST API」を実装します
本コラムの検証環境
本コラムは、以下環境で検証しています(Windowsで実施していますが、Linuxやmacでも動作する想定です)
- Windows 10
- Elixir 1.10.1 ※最新版のインストール手順はコチラ
- Phoenix 1.4.15 ※最新版のインストール手順はコチラ
- Node.js 12.14.0
なおPhoenixは、1.3系でも動作します(ElixirもPhoenixのバージョンに準じた古いものでも大丈夫です)
あと下記コラムの続きとして実施するので、未実施であれば、事前に実施しておいてください(近々、ボイラープレート生成のmixコマンドとしてOSS化は考えています)
PHP的ハックを応用してNode.js Express/Go的な軽量APIをPhoenixで実現してみた
|> Express/Go的なPhoenix軽量APIの上に参照系REST APIを実装する
手順①:数値のハンドリングにsmallex導入
REST APIは、URLでid指定しますが、ここの判定をスマートにするために、smallexの型判定モジュールを使いたいので、smallexをインストールします
defmodule Basic.MixProject do
…
defp deps do
[
{ :smallex, "~> 0.0" },
…
一度、iexを抜けて、下記でsmallexをインストールします
mix deps.get
iex -S mix phx.server
手順②:REST APIをルーティング不要に
前回同様、ルーティングやMVCの追加無しに、REST APIを増設できるようにします
REST APIのエントリーポイントは、「/api」配下に集約し、下記の汎用ルーティング4つを追加します
defmodule BasicWeb.Router do
…
scope "/api/", BasicWeb do
pipe_through :browser
# v-- add start
get "/*path_", ApiController, :index
post "/*path_", ApiController, :create
put "/*path_", ApiController, :update
delete "/*path_", ApiController, :delete
# ^-- add end
end
…
なお、mix phx.gen.jsonで自走生成される元々のREST APIであれば、「resources」を使い、4つでは無く、1つで済ませますが、「resources」は、下記の通り、ワイルドカード指定がNGっぽいです(ここの原因究明は別の機会で)
手順③:indexのAPIコントローラ実装不要に
GETのURL末尾が、数値で無いときはindex.jsonを、数値のときはshow.jsonを返すようにします
タプルでのパターンマッチによる束縛と、if式のタプル返却が、Elixirらしいコンパクトで、差分が分かりやすいコードです
defmodule BasicWeb.ApiController do
use BasicWeb, :controller
def index( conn, params ) do
id = params[ "path_" ] |> List.last |> Type.to_number
{ new_params, template, path } =
if id == nil do
{
params,
"index.json",
params[ "path_" ]
}
else
{
params |> Map.put( "id", id ),
"show.json",
params[ "path_" ] |> Enum.drop( -1 )
}
end
prefix = if params[ "path_" ] == nil, do: "", else: Enum.join( path, "/" ) <> "/"
render( conn, "#{ prefix }#{ template }", params: new_params )
end
end
手順⑤:json.eexを追加する
1)show.json.eexの追加
templates配下に、apiフォルダを掘り、その下にv1フォルダ(APIのバージョン指定用)を掘り、更にusersフォルダを掘ります
そして、users配下に、下記内容でshow.json.eex JSONテンプレートを作ります
ここの記述は、Elixirそのままのコードが使え、paramsでGETパラメータを参照可能です(これは上記コントローラ側でバインディングしています)
これにより、ageはGETパラメータで変わる仕様になっています
また、idの値は、URLに連動して変わるようにしています(これも上記コントローラ側でバインディングしています)
%{
id: params[ "id" ],
name: "Mr.xxx"
age: params[ "age" ]
}
RESTクライアントで「GET http://localhost:4000/api/v1/users/123」
のアクセスを行うと、以下のような結果が表示されます
これで、バージョン付きAPIでの、id指定による1件JSON返却ができるようになりました
2)index.json.eexの追加
今度は、users配下に、下記内容でindex.json.eex JSONテンプレートを作ります
ここの記述も、Elixirそのままのコードが使えます
[
%{
id: params[ 1 ],
name: "Mr.xxx"
},
%{
id: params[ 2 ],
name: "Ms.yyy"
},
%{
id: params[ 3 ],
name: "Mrs.zzz"
}
]
RESTクライアントで「http://localhost:4000/api/v1/users」
にアクセスすると、以下のような結果が表示されます
一応、バージョン付きAPIでの、一覧っぽいJSON返却はできるようになりましたが、show.json.eexと無関係なJSON定義になってしまっています…
3)show.json.eexを繰り返すindex.json.eexに修正
index.json.eexを、show.json.eexを繰り返すようなJSONテンプレートに修正します
JSONテンプレート内で、Elixirコードがそのまま使える特性を活かします
File.read!()で、show.json.eexを読み込み、for式の中で、Code.eval_string()にてshow.json.eexのparams指定してのテンプレート展開を繰り返すようにします
idは、for式のループカウンタ0~3を当てはめます
ageは、GETパラメータで指定したものが、全件に適用されます
json = File.read!( "lib/basic_web/templates/api/v1/users/show.json.eex" )
for i <- 0..3 do
new_params = params |> Map.put( "id", i )
json |> Code.eval_string( [ params: new_params ] ) |> elem( 0 )
end
RESTクライアントで「http://localhost:4000/api/v1/users」
にアクセスすると、以下のような結果が表示されます
これで、1件ずつJSONを反映した、一覧JSON返却ができるようになりました
下記のように、show.json.eex側を変更すると、index.json.eexも返却が変わることも確認します
%{
id: params[ "id" ],
no: params[ "id" ] + 1,
age: params[ "age" ]
}
RESTクライアントで「http://localhost:4000/api/v1/users」
にアクセスすると、以下のように、show.json.eexの変更により、index.json.eexが変わることが確認できます
終わり
JSONテンプレート追加だけで実現できる、参照系REST APIを実装しました
まだDB等に接続していないため、変数展開は、ループカウントとGETパラメータくらいしか使えていませんが、次回は、DBから値取得をするようにします