Phoenix自動生成APIのリクエスト受付／レスポンス返却のコード解析

（この記事は、「サーバーサイドプログラミング Advent Calendar 2017」の2日目です）

---

[fukuoka.ex](https://fukuokaex.fun/)代表の[piacere](https://twitter.com/piacere_ex)です
ご覧いただいて、ありがとうございます :bow:

「季節外れのfukuoka.ex Advent Calendar」も、先週で終わりましたので、今回からは、小ネタ中心にスイッチします

今回は、「mix phx.gen.json ～」で自動生成したPhoenixのAPIが、クライアントから、どのような形でリクエストを受け付け、クライアントにレスポンスを返すのかのコードの流れを解析していきます

この解析を元に、DBテーブルと紐付くCRUDとして生成されたAPIを、DBテーブルとは関係無いAPIへと改造する方法を、次回以降のどこかで実装しようと思います
<br>
:shamrock::shamrock::shamrock:<b>　お礼：「いいね」638件いただき、毎週１／２／３フィニッシュ ＋ 87回ランクイン達成　:shamrock::shamrock::shamrock:

4/27から6/13までの48日間に渡り、毎日お届けした、[「季節外れのfukuoka.ex Elixir Advent Calendar」](https://qiita.com/advent-calendar/2017/elixir-or-phoenix)と[「季節外れのfukuoka.ex(その2) Elixir Advent Calender」](https://adventar.org/calendars/2871)ですが、沢山の応援をいただき、誠にありがとうございます :bow:

[Elixirウィークリーランキング](https://qiita.com/tags/elixir)にて、開始の週から、終了の週までの<font color="red"><b>8週間、毎週連続で１／２／３フィニッシュ</b></font>を飾りました :tada:

また、[Qiitaトップページランキング](https://qiita.com/trend)には13回入賞し、各種ランキングにて<font color="red"><b>トータル87回ものランクイン</b></font>を達成しました :ocean:

これらは一重に、皆さまからいただいた「いいね」によるもので、fukuoka.exアドバイザーズ／キャスト一同、感謝しています :wink:

[![image.png](https://qiita-image-store.s3.amazonaws.com/0/155423/2a41f24b-b9db-8fbf-99ba-c2585d7d2d49.png)](https://adventar.org/calendars/2871)

# Phoenix自動生成APIの作り方

Phoenixで自動生成するAPIの作り方は、[「Elixir入門「第3回：Phoenix 1.3で高速webアプリ ＆ REST APIアプリをサクッと書いてみる」](https://www.slideshare.net/piacere_ex/elixir3phoenix-13web-rest-api-81219622)のP22以降をご覧ください

[![image.png](https://qiita-image-store.s3.amazonaws.com/0/155423/9ee89c54-f5f6-45d9-fa91-ba5d98a4a086.png)](https://www.slideshare.net/piacere_ex/elixir3phoenix-13web-rest-api-81219622)

なお、API自動生成以前に、Elixir／Phoenix／DBが、未だインストールされていない方は、[「Excelから関数型言語マスター1回目：行の「並べ替え」と「絞り込み」」](https://qiita.com/piacere_ex/items/6714e1440e3f25fb46a1)を第3回までなぞってください

以降では、上記手順にしたがって、以下コマンドで、Phoenix PJ作成／DBテーブル作成／API自動生成／Phoenix起動を行っている前提でお話します

```
mix phx.new sample_analytics --no-brunch --database=mysql
mix ecto.create
mix phx.gen.json Api Sample sample title:string body:string
【lib/sample_analytics_web/router.exにルート追加】
mix ecto.migrate
iex -S mix phx.server
```

# APIのリクエストはコントローラで受け付ける

PhoenixのAPIは、下記コントローラモジュールの各関数で受け付けます

index()はGETで取得できるリスト一覧、create()はPOSTで行うデータ追加、show()はID指定のGETで取得できる各データ、update()はPUTで行うデータ更新、deleteはDELETEで行うデータ削除、といった感じに1対1で対応しています

```elixir:lib/sample_analytics_web/controllers/sample_controller.ex
defmodule SampleAnalyticsWeb.SampleController do
  use SampleAnalyticsWeb, :controller

  alias SampleAnalytics.Api
  alias SampleAnalytics.Api.Sample

  action_fallback SampleAnalyticsWeb.FallbackController

  def index(conn, _params) do
    sample = Api.list_sample()
    render(conn, "index.json", sample: sample)
  end

  def create(conn, %{"sample" => sample_params}) do
    with {:ok, %Sample{} = sample} <- Api.create_sample(sample_params) do
      conn
      |> put_status(:created)
      |> put_resp_header("location", sample_path(conn, :show, sample))
      |> render("show.json", sample: sample)
    end
  end

  def show(conn, %{"id" => id}) do
    sample = Api.get_sample!(id)
    render(conn, "show.json", sample: sample)
  end

  def update(conn, %{"id" => id, "sample" => sample_params}) do
    sample = Api.get_sample!(id)

    with {:ok, %Sample{} = sample} <- Api.update_sample(sample, sample_params) do
      render(conn, "show.json", sample: sample)
    end
  end

  def delete(conn, %{"id" => id}) do
    sample = Api.get_sample!(id)
    with {:ok, %Sample{}} <- Api.delete_sample(sample) do
      send_resp(conn, :no_content, "")
    end
  end
end
```

index()は、DBデータ一覧をDBアクセッサであるApi.list_sample()経由で取得し、後述するビューモジュールのredner()でJSONオブジェクトリストとして返却します

show()は、引数で指定されたID指定でパターンマッチし、DBデータ1件をApi.get_sample!()経由で取得し、ビューモジュールのredner()にてJSONオブジェクトで返却します

create()は、リクエストで投げられた下記形式のJSONオブジェクトをパターンマッチし、Api.create_sample()にてバリデーション付insertを行い、insertが成功したら、show()同様に登録したデータ1件を、ビューモジュールのredner()にてJSONオブジェクトで返却します

DBデータ一覧をDBアクセッサであるApi.list_sample()経由で取得し、後述するビューモジュールのredner()でJSONオブジェクトリストとして返却します

なお、mixコマンドを使うと、下記のように、各HTTPメソッドと、コントローラ＋関数の関係が表示できます

```
mix phx.routes
  page_path  GET     /            SampleAnalyticsWeb.PageController :index
sample_path  GET     /sample      SampleAnalyticsWeb.SampleController :index
sample_path  GET     /sample/:id  SampleAnalyticsWeb.SampleController :show
sample_path  POST    /sample      SampleAnalyticsWeb.SampleController :create
sample_path  PATCH   /sample/:id  SampleAnalyticsWeb.SampleController :update
             PUT     /sample/:id  SampleAnalyticsWeb.SampleController :update
sample_path  DELETE  /sample/:id  SampleAnalyticsWeb.SampleController :delete
```

# APIのレスポンスJSON返却はビューで行う

PhoenixのAPIは、下記ビューモジュールのrender()で行います

コントローラのindex()は、render("index.json", ～)をパターンマッチして呼び出し、index()以外の全ては、render("show.json", ～)をパターンマッチして呼び出します

なお、"index.json"や"show.json"は、ファイル名では無く、コントローラからのパターンマッチ呼び分けのための文字列です

```elixir:lib/sample_analytics_web/views/sample_view.ex
defmodule SampleAnalyticsWeb.SampleView do
  use SampleAnalyticsWeb, :view
  alias SampleAnalyticsWeb.SampleView

  def render("index.json", %{sample: sample}) do
    %{data: render_many(sample, SampleView, "sample.json")}
  end

  def render("show.json", %{sample: sample}) do
    %{data: render_one(sample, SampleView, "sample.json")}
  end

  def render("sample.json", %{sample: sample}) do
    %{id: sample.id,
      title: sample.title,
      body: sample.body}
  end
end
```

index()から呼ばれた際に使われるrender_many()は、DBデータ全件のマップリストが、JSONのオブジェクトリストとして返却されます

index()以外から呼ばれた際に使われるrender_one()は、DBデータ1件のマップを、JSONオブジェクトで返却します

そのいずれも、内部では、ビュー内のrender("sample.json", ～)を呼び出しており、render_many()／render_one()のいずれにも、引数で渡されたデータから、1データ分のマップを作成し、render_many()／render_one()に戻しています

render_many()は、DBデータ一覧をEnum.map()することで、複数回、render("sample.json", ～)を呼び出します

# 終わり

今回は、Phoenixで自動生成したAPIのコードの流れを解析しました

PhoenixのAPIにおける、リクエスト受付と、JSONレスポンス返却の流れが、理解できましたでしょうか？

この内容を理解すれば、DBテーブルと紐付くCRUDとして生成されたAPIを、DBテーブルとは関係無いAPIへと改造する方法も、何となく想像が付くのでは無いかと思います
<br>
:stars::stars::stars::stars::stars:<b>　お知らせ：Elixir MeetUpを6月末に開催します　</b>:stars::stars::stars::stars::stars:

[<b>「fukuoka.ex#11：DB／データサイエンスにコネクトするElixir」</b>](https://fukuokaex.connpass.com/event/87241)を6/22（金）19時に開催します

fukuoka.exの発足から、ちょうど1周年となる、記念的なイベントでもあるため、このコラムを気に入っていただいた方と、ぜひ一緒に盛り上がりたいです

福岡近辺にお住まいの方であれば、遊びに来てください

<font color="red"><b>大人気により、一度は満席となりましたが、増枠しましたので、下記URLよりご参加ください</b></font>
https://fukuokaex.connpass.com/event/87241

特別ゲストは、Erlang／Elixirの両面で世界的に有名な「力武 健次さん」と、北九州の飯塚市で「e-ZUKA Tech Night」を6年間主催し続けるハウインターナショナルの「谷口 耕平さん」のお二人と、実に豪華なイベントです

私は、今回のシリーズを踏まえた、「1家に1台、パーソナルなデータ分析AIを全員が持つ2020年を作る」というタイトルで、Elixirによる、ブラウザUI上からサクっとデータ分析プラットフォームを披露するLTをお届けします

[![image.png](https://connpass-tokyo.s3.amazonaws.com/thumbs/cb/13/cb130a03343bc310b07244c308b940db.png)](https://fukuokaex.connpass.com/event/87241)

# p.s.「いいね」よろしくお願いします

よろしければ、ページ左上の ![image.png](https://qiita-image-store.s3.amazonaws.com/0/155423/4d515047-cc48-382e-c2b1-3ad0cc50dbbf.png) や ![image.png](https://qiita-image-store.s3.amazonaws.com/0/155423/a4e3da58-70a3-4197-95a2-6a6906650d01.png) のクリックをお願いしますー:bow: