LoginSignup
15
10

More than 1 year has passed since last update.

Phoenixお気軽API開発①:PHP的ハックを応用してNode.js Express/Go的な軽量APIをPhoenixで実現してみた

Last updated at Posted at 2020-03-24

fukuoka.ex/kokura.exのpiacereです
ご覧いただいて、ありがとうございます :bow:

前々作は「PHP的気軽さでWebアプリを作る方法」前作はこれを応用して「リアルタイムフロントであるLiveViewにも気軽さを導入」してみましたが、今作では、Phoenix API開発に、Node.js Express/Go的な軽量APIのテイストを付与してみます

この対応により、APIも、WebアプリやLiveView同様、ルーティング/MVCが不要になります

また、従来通りのmix phx.gen.jsonによるScaffold(≒コード自動生成)で構築されるAPIだと、JSONフォーマットがカスタマイズしにくかったり、カスタムフィールドを乗せるのにもノウハウが必要ですが、ここでは、シンプルなJSONテンプレートによるAPI構築を実現するので、普段のPhoenix使いでは体験できない気楽さを感じていただけたら幸いです

:ocean::ocean::ocean: Advent Calendar、fukuoka.ex1位、Elixir2位達成ヽ(=´▽`=)ノ :ocean::ocean::ocean:

fukuoka.ex Advent Calendar、Webテクノロジーカテゴリで堂々1位 … 各コラムぜひお読みください
https://qiita.com/advent-calendar/2020/fukuokaex
image.png

そして、プログラミング言語カテゴリは、1位がRust、2位がElixir、3位がGoとモダン言語揃い踏みでのトップ3、熱いネー:laughing:
https://qiita.com/advent-calendar/2020/elixir
image.png

本コラムの検証環境

本コラムは、以下環境で検証しています(Windowsで実施していますが、Linuxやmacでも動作する想定です)

なお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」にアクセスすると、以下のようなページが表示されることを確認して、準備完了です
image.png

手順①:APIをルーティング不要に

まずはルーティングで、前作同様、「*path_」というワイルドカード指定をすることで、多階層のURLパスを「path_」パラメータから取得できるようにします

接続先のコントローラは、API専用のマッピングコントローラとします

なお、前作の続きでやられている場合は、getやpostのワイルドカード指定とぶつかるため、getやpostの分は、コメントアウトや削除しておいてください

lib/basic_web/router.ex
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パラメータを素通しさせます

lib/basic_web/controllers/api_controller.ex
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」というフォルダを掘って、配置することにします

lib/basic_web/templates/api/index.json.eex
%{
}

手順④: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()の恩恵に預かることにしました

lib/basic_web/views/api_view.ex
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ビルトインモジュール関数を値に設定します

lib/basic_web/templates/api/sample.json.eex
%{
  id:      params[ "id" ], 
  name:    "Elixir", 
  version: System.version()
}

RESTクライアントで「GET http://localhost:4000/sample?id=123」のアクセスを行うと、以下のような結果が表示されます

入力パラメータや、Elixirビルトインモジュール関数が実行され、その結果がJSONとして返却されます
image.png

idを変えると、返却されるJSONも変わります
image.png

2)templates/api配下のサブフォルダ下にjson.eex追加

apiフォルダ配下にフォルダを掘って、その配下にjson.eexを配置しても動きます

「abc」というフォルダを掘り、「def.json.eex」というJSONテンプレートを追加します

今度は、リストマップで構成し、各マップの値をランダム値で変動させてみます

lib/basic_web/templates/api/abc/def.json.eex
[
  %{
    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配列が返却されました
image.png

終わり

JSONテンプレート追加だけで実現できるお気軽API開発を実現してみました

前々作のPHP的ハックをAPIに応用すると、Scaffoldを用いることなく、ルーティング/MVCにも煩わされない、軽量なAPI開発が可能となることがお分かりいただけたでしょうか?

Phoniexは、ファットなWebフレームワークだと誤解されがちですが、ここまでの3部作で見てきたように、ちょっとした工夫を施すことで、軽量フレームワークに化ける「プログレッシブWebフレームワーク」としての一面があり、そこが上手く共有できていたら嬉しいです

さて、次回以降では、より本格的なAPI利用に向けて、REST API対応(POST/PUT/DELETEやURLとしてのid指定)や、軽量APIからのDB利用、APIバージョン管理とWebページ共存なども行っていきたいと思います

p.s.このコラムが、面白かったり、役に立ったら…

image.pngimage.png にて、どうぞ応援よろしくお願いします:bow:

15
10
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
15
10