fukuoka.ex/kokura.exのpiacereです
ご覧いただいて、ありがとうございます
前々作は「PHP的気軽さでWebアプリを作る方法」、前作はこれを応用して「リアルタイムフロントであるLiveViewにも気軽さを導入」してみましたが、今作では、Phoenix API開発に、Node.js Express/Go的な軽量APIのテイストを付与してみます
この対応により、APIも、WebアプリやLiveView同様、ルーティング/MVCが不要になります
また、従来通りのmix phx.gen.jsonによるScaffold(≒コード自動生成)で構築されるAPIだと、JSONフォーマットがカスタマイズしにくかったり、カスタムフィールドを乗せるのにもノウハウが必要ですが、ここでは、シンプルなJSONテンプレートによるAPI構築を実現するので、普段のPhoenix使いでは体験できない気楽さを感じていただけたら幸いです
Advent Calendar、fukuoka.ex1位、Elixir2位達成ヽ(=´▽`=)ノ
fukuoka.ex Advent Calendar、Webテクノロジーカテゴリで堂々1位 … 各コラムぜひお読みください
https://qiita.com/advent-calendar/2020/fukuokaex
そして、プログラミング言語カテゴリは、1位がRust、2位がElixir、3位がGoとモダン言語揃い踏みでのトップ3、熱いネー
https://qiita.com/advent-calendar/2020/elixir
本コラムの検証環境
本コラムは、以下環境で検証しています(Windowsで実施していますが、Linuxやmacでも動作する想定です)
- Windows 10
- Elixir 1.10.1 ※最新版のインストール手順はコチラ
- Phoenix 1.4.15 ※最新版のインストール手順はコチラ
- Node.js 12.14.0
なおPhoenixは、1.3系でも動作します(ElixirもPhoenixのバージョンに準じた古いものでも大丈夫です)
事前準備:PHP的ハック導入済みPhoenix PJの作成
下記コラムの手順で、PHP的ハック導入済みのPhoenix PJを作成します
ElixirでもPHP的に気軽なWebアプリ開発をしたい(Phoenixより薄いWebFW実現?)
https://qiita.com/piacerex/items/25c82153976dcb6d57c1
ここまでが終わったら、ブラウザで「http://localhost:4000/abc/def?abc=xyz123」
にアクセスすると、以下のようなページが表示されることを確認して、準備完了です
手順①:APIをルーティング不要に
まずはルーティングで、前作同様、「*path_」というワイルドカード指定をすることで、多階層のURLパスを「path_」パラメータから取得できるようにします
接続先のコントローラは、API専用のマッピングコントローラとします
なお、前作の続きでやられている場合は、getやpostのワイルドカード指定とぶつかるため、getやpostの分は、コメントアウトや削除しておいてください
defmodule BasicWeb.Router do
…
scope "/", BasicWeb do
pipe_through :browser
get "/", PageController, :index
# get "/*path_", PageController, :index # <-- remove here
# post "/*path_", PageController, :index # <-- remove here
# live "/*path_", LiveViewController # <-- remove here
get "/*path_", ApiController, :index # <-- add here
…
手順②:html.leexの複数階層フォルダ指定可能に
ここは上記のコラム内で実施済みなので、特にやることはありません
手順③:パスの自動マッピング、GETパラメータの素通し
API専用のマッピングコントローラを追加します
「path_」パラメータのフォルダ階層リストを、json.eexのパスにマッピングします
また、mix phx.newで生成されたコントローラが「_params」という感じでアンダースコアで未使用にしているので、これを解除し、renderの第3引数に渡すことで、GETパラメータを素通しさせます
defmodule BasicWeb.ApiController do
use BasicWeb, :controller
def index( conn, params ) do
template = if params[ "path_" ] == nil, do: "index.json", else: Path.join( params[ "path_" ] ) <> ".json"
render( conn, template, params: params )
end
end
「path_」パラメータ未指定時のためのindex.json.eexを追加します
なお、JSONテンプレートは、templates配下に「api」というフォルダを掘って、配置することにします
%{
}
手順④:json.eexをビュー向けにテンプレート展開
API専用のビューを追加します
PhoenixにおけるJSONレンダリングは、Phonenixのビュー配下で、マップもしくはマップリストをJSONに変換することで実現されるため、コントローラでのrender()、つまりeexテンプレート展開(<%= ~ %>)のように文字列展開されると、都合が悪いです
そこで、ビューのrender()にて、Code.eval_string()を使って、自前でテンプレート展開を行います
Elixirのビルトインモジュールや自作モジュールは、Code.eval_string()で展開可能ですが、入力パラメータは自前でバインディングをかけなければならないため、Code.eval_string()の第2引数にてバインディングを行います
ちなみに、Code.eval_file()というもっと便利そうなものもあるのですが、Code.eval_string()のようなバインディングができないため、今回の目的には沿いませんでした
Phoenixは、カスタムテンプレートエンジンを定義できるので、JSON専用テンプレートを構築することもできるのですが、今回は、バインディングもできる強力なCode.eval_string()の恩恵に預かることにしました
defmodule BasicWeb.ApiView do
use BasicWeb, :view
def render( path, %{ view_template: view_template, params: params } ) do
File.read!( "lib/basic_web/templates/api/#{ view_template }.eex" )
|> Code.eval_string( [ params: params ] )
|> elem( 0 )
end
end
なお、Webページテンプレート同様、「@ param」で入力パラメータ指定可能にしたかったのですが、Code.eval系はモジュールアトリビュートが通常変数と扱いが異なるためNGで、EEx.eval系は@が利用できますが文字列返却になってしまうため、断念しました
手順⑤:json.eexを追加する
1)templates/api直下にjson.eex追加
これで、json.eexを追加するだけで、新たなAPIが追加可能となったので、追加してみます
JSONテンプレートは、Elixirのマップとリストで構成し、中身は通常のマップやリストと同様、Elixirコードを記述できます
まずはシンプルに、マップ1つだけとし、入力パラメータ/リテラル/Elixirビルトインモジュール関数を値に設定します
%{
id: params[ "id" ],
name: "Elixir",
version: System.version()
}
RESTクライアントで「GET http://localhost:4000/sample?id=123」
のアクセスを行うと、以下のような結果が表示されます
入力パラメータや、Elixirビルトインモジュール関数が実行され、その結果がJSONとして返却されます
2)templates/api配下のサブフォルダ下にjson.eex追加
apiフォルダ配下にフォルダを掘って、その配下にjson.eexを配置しても動きます
「abc」というフォルダを掘り、「def.json.eex」というJSONテンプレートを追加します
今度は、リストマップで構成し、各マップの値をランダム値で変動させてみます
[
%{
greeting: "I'm def-1",
type: "plane API",
random_no: :rand.uniform
},
%{
greeting: "I'm def-2",
type: "plane API",
random_no: :rand.uniform
},
%{
greeting: "I'm def-3",
type: "plane API",
random_no: :rand.uniform
}
]
RESTクライアントで「GET http://localhost:4000/abc/def」
のアクセスを行うと、以下のような結果が表示されます
ランダム値が展開されたJSONオブジェクトを梱包するJSON配列が返却されました
終わり
JSONテンプレート追加だけで実現できるお気軽API開発を実現してみました
前々作のPHP的ハックをAPIに応用すると、Scaffoldを用いることなく、ルーティング/MVCにも煩わされない、軽量なAPI開発が可能となることがお分かりいただけたでしょうか?
Phoniexは、ファットなWebフレームワークだと誤解されがちですが、ここまでの3部作で見てきたように、ちょっとした工夫を施すことで、軽量フレームワークに化ける「プログレッシブWebフレームワーク」としての一面があり、そこが上手く共有できていたら嬉しいです