9
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Phoenixお気軽API開発②:Express/Go的なPhoenix軽量APIの上に参照系REST APIを実装する

Last updated at Posted at 2020-03-31

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

前作のPhoenix軽量APIは、シンプルなリテラルやElixir実行結果だけを返す簡易APIまでの実装だったので、これをREST API化したいと思います

今回は、まずGET、つまり「参照系REST API」を実装します

本コラムの検証環境

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

なお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をインストールします

mix.exs
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つを追加します

/lib/basic_web/router.ex
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っぽいです(ここの原因究明は別の機会で)
image.png

手順③:indexのAPIコントローラ実装不要に

GETのURL末尾が、数値で無いときはindex.jsonを、数値のときはshow.jsonを返すようにします

タプルでのパターンマッチによる束縛と、if式のタプル返却が、Elixirらしいコンパクトで、差分が分かりやすいコードです

lib/basic_web/controllers/api_controller.ex
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に連動して変わるようにしています(これも上記コントローラ側でバインディングしています)

lib/basic_web/templates/api/v1/users/show.json.eex
%{
  id:   params[ "id" ], 
  name: "Mr.xxx"
  age:  params[ "age" ]
}

RESTクライアントで「GET http://localhost:4000/api/v1/users/123」のアクセスを行うと、以下のような結果が表示されます
image.png

これで、バージョン付きAPIでの、id指定による1件JSON返却ができるようになりました

2)index.json.eexの追加

今度は、users配下に、下記内容でindex.json.eex JSONテンプレートを作ります

ここの記述も、Elixirそのままのコードが使えます

lib/basic_web/templates/api/v1/users/index.json.eex
[
  %{
    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」にアクセスすると、以下のような結果が表示されます
image.png

一応、バージョン付き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パラメータで指定したものが、全件に適用されます

lib/basic_web/templates/api/v1/users/index.json.eex
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」にアクセスすると、以下のような結果が表示されます
image.png

これで、1件ずつJSONを反映した、一覧JSON返却ができるようになりました

下記のように、show.json.eex側を変更すると、index.json.eexも返却が変わることも確認します

lib/basic_web/templates/api/v1/users/show.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が変わることが確認できます
image.png

終わり

JSONテンプレート追加だけで実現できる、参照系REST APIを実装しました

まだDB等に接続していないため、変数展開は、ループカウントとGETパラメータくらいしか使えていませんが、次回は、DBから値取得をするようにします

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

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

9
3
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
9
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?