ExcelからElixir入門⑥：SPAからPhoenix製APIを呼び出す（表示編）【LiveView版】

【本コラムは、5分で読めて、15分くらいでお試しいただけます】
[piacere](https://twitter.com/piacere_ex)です、ご覧いただいてありがとございます :bow:

[前回](https://qiita.com/piacerex/items/290b76b76d5ff8e019bf)までは、シンプルなPhoenixのサーバサイドレンダリング（SSR）により、Web上にDBデータや外部APIデータを表示してきました

今回からは、ElixirサーバサイドでSPAを開発できる「LiveView」を使ったフロントサイドと、Phoenixによる内部APIを組み合わせを用いて、よりElixir／Phoenixらしさを実感できるWeb開発へとステージアップします

なお、本コラムと全く同じ内容のVue.js版とReact版もあります

**Vue.js版**
https://qiita.com/piacerex/items/50d847170291c41fef64

**React版**
https://qiita.com/piacerex/items/27052ac628ec394c7560

ちなみにVue.jsやReactと同じ構成にするため、LiveViewでもAPIを用意して操作を行っていますが、本来LiveViewはAPI無しでSPAを開発できます … これは、シリーズのこの先にある[第9回](https://qiita.com/piacerex/items/3f8ee18c9443d63955bf)で実践します

**■「ExcelからElixir入門」シリーズの目次**
[**①データ並替え／絞り込み**](https://qiita.com/piacere_ex/items/6714e1440e3f25fb46a1)
|> [**②データ列抽出、Web表示**](https://qiita.com/piacere_ex/items/b7787580fce5f148242f)
|> [<b>③WebにDBデータ表示</b>](https://qiita.com/piacere_ex/items/a7558adc6856e3577dc6)
|> [<b>④Webに外部APIデータ表示</b>](https://qiita.com/piacere_ex/items/4c212615a4eb699dd109)
|> [<b>⑤Webにグラフ表示</b>](https://qiita.com/piacere_ex/items/290b76b76d5ff8e019bf)
|> [**⑥SPAからPhoenix製APIを呼び出す（表示編）【LiveView版】**](https://qiita.com/piacerex/items/64e77b85bfb4c6832662)
|> [**⑦SPAからPhoenix製APIを呼び出す（更新編）【LiveView版】**](https://qiita.com/piacerex/items/78ee0d2ad8cca0927980)
|> [<b>⑧Gigalixirに本番リリース</b>](https://qiita.com/piacerex/items/1a9cbcc740ca3707eaec)
|> [**⑨ElixirサーバサイドのみでReactと同じSPA／リアルタイムUIが作れる「LiveView」**](https://qiita.com/piacerex/items/3f8ee18c9443d63955bf)
|> [**⑩ElixirサーバサイドSPAをスマホで見るためにGigalixirリリース**](https://qiita.com/piacerex/items/583174d72e6fc0cd289e)
|> [**⑪Gigalixir上のLiveViewアプリに独自ドメイン名を付与して正式なアプリ公開**](https://qiita.com/piacerex/items/b61348c282395167fd8c)
|> [**⑫Elixir／PhoenixのCRUD Webアプリをリリース**](https://qiita.com/piacerex/items/511f24888c7350639d0b)

<h3>:ocean::ocean::ocean: Elixir Advent Calendar: 言語カテゴリ1位 & 全カテゴリ2位！ :ocean::ocean::ocean:</h3>

例年を遥かに超える盛り上がりを見せ、堂々のトップ獲得ッ！ :qiita: :tada: :confetti_ball:

https://qiita.com/advent-calendar/2022/elixir
https://qiita.com/advent-calendar/2022/ranking/feedbacks
https://qiita.com/advent-calendar/2022/ranking/feedbacks/categories/programming_languages
[![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/155423/356a15c6-0dbb-097e-38f3-a1b4fed47555.png)](https://qiita.com/advent-calendar/2022/ranking/feedbacks/categories/programming_languages)

# LiveView：リアルタイムSPAをサーバサイドのみで開発可

Elixirサーバサイドのみで書かれたコードにも関わらず、Vue.jsやReactのようなリアルタイムSPAが構築できるのが「LiveView」です

Vue.jsやReactとほぼ変わらない反応速度やフィーリングで、しかもフロントとサーバの書き分けが一切要らず、フロント側の入力内容やデータ状態をサーバサイドで一元管理でき、しかもAPI構築要らず … なんだか魔法のような体験ができるLiveViewをお楽しみください :wink:

# PhoenixでScaffoldを使わずにデータ一覧APIを作る

通常、PhoenixでAPIを作る場合、`mix phx.gen.json` によるDB CRUD付きREST API、いわゆる「Scaffold」で生成するのが一般的ですが、別に使わなくてもPhoenixでのAPI作成はカンタンですので、早速作ってみましょう

まず 「Sqlex」をインストールすることで、`DbMnesia`／`Db` モジュールを導入します（このシリーズの第3回まではこの2つの実装も扱っていましたが、入門編を超えた範囲になるのでOSS化しました）

https://github.com/piacerex/sqlex

mix.exsの `def deps do` 配下の `:phoenix` の直上に追記します

```diff_elixir:mix.exs
defmodule Basic.Mixfile do
  use Mix.Project
…
  defp deps do
    [
      {:req, "~> 0.3"}, 
+     {:sqlex, "~> 0.1.0"}, 
      {:phoenix, "~> 1.6.15"},
      …
    ]
  end
…
```

PhoenixをCtrl+C2回で止めて、ライブラリを取得（要ネット接続）し、Phoenixを起動します

```sh
mix deps.get
iex -S mix phx.server
```

次に、`controllers` フォルダ配下に `member_controller.ex` というファイルを追加し、下記のように `index` で `members` テーブルの一覧を返すようにします

Phoenixは、マップやリスト、リストマップを `json` に渡すと、自動的にJSON化してくれるのです

前回までで、DB（Mnesia）を使うPhoenix PJを作りましたが、そこに下記ファイルを追加しましょう

```diff_elixir:lib/basic_web/controllers/member_controller.ex
defmodule BasicWeb.MemberController do
  use BasicWeb, :controller

  def index(conn, _p) do
    conn
    |> json(
      "select * from members"
      |> Db.query
      |> Db.columns_rows 
      |> Enum.sort(fn current, next -> current["id"] < next["id"] end)
    )
  end
end
```

次に、ルーティングにAPI用エントリーとして、CRUD全部（GET／POST／PUT／DELETE）のエンドポイントを一括生成する `resources "/", MemberController` を含む `scope "/members", ～` ブロックを追加します

```diff_elixir:lib/vue_sample_web/router.ex
defmodule BasicWeb.Router do
  use BasicWeb, :router
…
+ scope "/members", BasicWeb do
+   pipe_through :api
+ 
+   resources "/", MemberController
+ end
  …
```

Phoenixを起動してください

```
iex -S mix phx.server
```

`http://localhost:4000/members` にブラウザでアクセスすると、下記のように返ってくれば成功です
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/155423/7937903d-fe0a-c3bd-2c77-688123253470.png)

## データ一覧APIをREST APIクライアントで直接叩いて確認

REST APIの確認には、「REST APIクライアント」が便利ですが、REST APIクライアントは下記が代表的です

**Chrome向け「Postman」**

https://chrome.google.com/webstore/detail/postman/fhbjgbiflinjbdggehcddcbncdddomop

**Firefox向け「RESTClient」**

https://addons.mozilla.org/ja/firefox/addon/restclient/

**VScode向け「REST Client」**

https://marketplace.visualstudio.com/items?itemName=humao.rest-client

ここではPostmanで説明しますが、下記のように「Headers」に `Content-Type` を `application/json` で設定してから、URLを設定し、「Send」ボタンをクリックしてください
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/155423/abc5f278-3e4f-d1ee-2886-ae1d2430fc24.png)

すると、下記のように返ってきます
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/155423/28e62dad-84a2-25b6-152e-7c60017f2cf3.png)

このように、PhoenixでAPIを作成するのは、とてもカンタンです

# SPAからデータ一覧APIを呼んだ後、表示する

LiveViewと、ElixirからREST APIを呼ぶためのライブラリ「Req」を使って、先ほど作ったAPIを呼ぶSPA（Single Page Application）を作ってみましょう

Reqは、mix.exsの `def deps do` 配下の `:phoenix` の直上に追記します

```elixir:mix.exs
defmodule Basic.MixProject do
…
  defp deps do
    [
      {:req, "~> 0.3"},

      {:phoenix, "~> 1.6.15"},
      …
```

ライブラリをインストールして、Phoenixを起動します（Phoenixが起動しっ放しの場合は、Ctrl+Cを2回で止めて下記コマンドを入れてください）

```
mix deps.get
iex -S mix phx.server
```

次にLiveViewで、まずLiveView用フォルダを作ります

```
mkdir lib/basic_web/live
```

LiveViewは、LiveViewモジュールとHTMLの2ファイルに分かれます

LiveViewモジュールは、初期表示時に実行される `mount` ハンドラで、ReqによるAPI呼出を行い、`members` にAPIで取得したデータを格納し、それがHTMLに渡されます

```elixir:lib/basic_web/live/index_live.ex
defmodule BasicWeb.IndexLive do
  use Phoenix.LiveView
  use BasicWeb, :live_view

  @url "http://localhost:4000/members"
  @headers ["Content-Type": "application/json"]

  def get() do
    Req.get!(@url, headers: @headers).body
    |> Enum.map(& &1 |> Map.new(fn {k, v} -> {String.to_atom(k), v} end))
  end

  def mount(_params, _session, socket) do
    {:ok, assign(socket, [members: get()])}
  end
end
```

HTMLでは、LiveViewモジュールの `mount` から渡された `members` を `for` で1件ずつ取り出し、各フィールドを `<%= n.【フィールド名】%>` で取り出して表示します

```elixir:lib/basic_web/live/index_live.html.heex
<h1>Members</h1>
<table>
  <tr>
    <th>id</th>
    <th>name</th>
    <th>age</th>
    <th>team</th>
    <th>position</th>
  </tr>
<%= for n <- @members do %>  
  <tr>
    <td><%= n.id %></td>
    <td><%= n.name %></td>
    <td><%= n.age %></td>
    <td><%= n.team %></td>
    <td><%= n.position %></td>
  </tr>
<% end %>
</table>
```

最後にRouterを `PageController, :index` のgetから、`IndexLive` のliveに変更します

```diff_elixir:lib/basic_web/router.ex
defmodule BasicWeb.Router do
  use BasicWeb, :router
…
  scope "/", BasicWeb do
    pipe_through :browser

+   live "/", IndexLive
-   get "/", PageController, :index
  end
…
```

`http://localhost:4000` にブラウザでアクセスすると、APIで取得したデータがテーブル表示されます
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/155423/6315519d-98d6-c6f5-556f-efea315c4328.png)

見た目が、SSR版と変わらないので、ちゃんとSPAとして動いているか分からないかも知れませんが、Phoenixを起動したコンソールで確認すると、下記の通り、API呼出である `MemberController.index` の実行が、ページ呼出である `PageController.index` の実行の後に行われていることが確認できます
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/155423/216ab14d-7119-412b-21b1-74e745d756a0.png)

# データ追加APIを実装する

APIによるデータ参照はできたので、次は、データ追加APIを実装してみます

`MemberController` モジュールに `create` を追加し、`members` テーブルへのinsert文を `Db.query` に渡し、その後、ステータスコードとして `201 Created` を返却します

```diff_elixir:lib/basic_web/controllers/member_controller.ex
defmodule BasicWeb.MemberController do
  use BasicWeb, :controller

  def index(conn, _p) do
    conn
    |> json(
      "select * from members"
      |> Db.query
      |> Db.columns_rows 
      |> Enum.sort(fn current, next -> current["id"] < next["id"] end)
    )
  end
+ def create(conn, p) do
+   "insert into members values('#{p["name"]}', #{p["age"]}, '#{p["team"]}', '#{p["position"]}')"
+   |> Db.query
+   send_resp(conn, :created, "")
+ end
end
```

## データ追加APIから直接データを追加し、Web上で確認

では、APIでデータ追加してみましょう

「GET」を「POST」に変更し、「Body」を「raw」で設定した後、下記内容をbodyに入力して、藤井名人をチームにjoinしてもらい、大幅戦力増強しましょうｗ

```json
{
  "name": "藤井 聡太", 
  "age": 20, 
  "team": "杉本昌隆八段門下", 
  "position": "竜王"
}
```

入力後は、こんな感じになります
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/155423/6b58bfce-70e8-d504-3a74-c6ae6ca775f4.png)

「Send」ボタンをクリックすると、「Status」に `201 Created` が返ってきます
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/155423/200ba380-ece1-ae8e-1c67-82b1494052c2.png)

データ一覧APIを叩くと、藤井名人が追加されているのが確認できます
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/155423/7471791e-f6e8-8ff6-d935-d9bcb002d372.png)

ブラウザをリロードすると、藤井名人がチームにjoinしたことが確認できました :wink:
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/155423/cbdd26eb-88d7-6b39-788d-6a32c441da43.png)

# 【参考】本コラムの検証環境

本コラムは、以下環境で検証しています（恐らくUbuntu実機やMacでも動きます）

- Windows 10
  - 実機＋Elixir 1.14.2 (Erlang/OTP 25)
    - Phoenix 1.6.15
    - LiveView 0.17.12
  - WSL2／Ubuntu 20.04＋Elixir 1.14.2 (Erlang/OTP 25)　※[**最新版のインストール手順はコチラ**](https://qiita.com/piacerex/items/01e89435af0f7a454ad2)
    - Phoenix 1.6.15
    - LiveView 0.17.12
  - Docker／Debian 11.6＋Elixir 1.14.2 (Erlang/OTP 25)
    - Phoenix 1.6.15
    - LiveView 0.17.12

- Windows11
  - WSL2／Ubuntu 22.04＋Docker Compose＋Elixir 1.13.4 (Erlang/OTP 25)
    - Phoenix 1.6.15
    - LiveView 0.17.12

# 終わり

今回は、Reactによるフロントサイドと、Phoenixによる内部APIを組み合わせを用いて、APIで取得したデータをWeb表示してみました

[次回は、**画面入力と、APIによるデータ更新** を行ってみます](https://qiita.com/piacerex/items/78ee0d2ad8cca0927980)