【旧版】ExcelからElixirマスター⑥：Vue.js＋内部API (表示編)

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

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

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

**■「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)
|> [<b>⑥Vue.js＋内部API（表示編）</b>](https://qiita.com/piacere_ex/items/50d847170291c41fef64)
|> [<b>⑦Vue.js＋内部API（更新編）</b>](https://qiita.com/piacerex/items/7cd1162ce6d66a334a07)
|> [<b>⑧Gigalixirに本番リリース</b>](https://qiita.com/piacerex/items/1a9cbcc740ca3707eaec)
|> [**⑨Elixir／PhoenixのCRUD Webアプリをリリース**](https://qiita.com/piacerex/items/511f24888c7350639d0b)
|> [**⑩「LiveView」ElixirサーバサイドのみでReact的SPA／リアルタイムUIが作れる**](https://qiita.com/piacerex/items/3f8ee18c9443d63955bf)
|> [**⑪LiveView製Qiita検索SPAをフォームsubmitスタイルに換装**](https://qiita.com/piacerex/items/21b0e308a36e486d8b25)
|> [**⑫LiveViewのコード内HTMLをテンプレートファイルに分離**](https://qiita.com/piacerex/items/5b1aabb0c45bb9934bb9)
|> [**⑬ElixirサーバサイドSPAをスマホで見るためにGigalixirリリース**](https://qiita.com/piacerex/items/583174d72e6fc0cd289e)
|> [**⑭Gigalixir上のLiveViewアプリに独自ドメイン名を付与して正式なアプリ公開**](https://qiita.com/piacerex/items/b61348c282395167fd8c)

<br>:stars::stars::stars:<b>　お知らせ：Elixir MeetUpを来週末10/26（金）に開催します（リモート視聴も）　</b>:stars::stars::stars:

[<b>「fukuoka.ex#16：蔵出し始めました！Elixir実践テクニック公開します① 」</b>](https://fukuokaex.connpass.com/event/105154/)を10/26（金）19時に開催します

今回のMeetUpから、「座談会＋モブプログラミング」という形で、fukuoka.exアドバイザーズ／キャストが、過去会で登壇したり、日頃研鑽している数々のElixir実践テクニックをダイレクトに伝えていきます

今回は、「Elixir＋Vue.jsで2倍速開発」と「ElixirでElixirコンパイラ開発」の2トラックにて、集まった皆さまと一緒にElixir実践テクニックをシェアしたいと思います

https://fukuokaex.connpass.com/event/105154
[![image.png](https://qiita-image-store.s3.amazonaws.com/0/155423/98aabdbd-8cd7-426b-0062-05d5863ce3b8.png)](https://fukuokaex.connpass.com/event/105154)

一応、リモート視聴にも対応していますが、福岡近辺にお住まいの方であれば、遊びに来ていただいて、参加型コンテンツを120%楽しんでいただくのが絶対オススメです

# DBのインストール

フロントサイドで表示したり、投入するデータをDBに入れるため、まずDBサーバをインストールします

ここでは、DBサーバとして、「PostgreSQL」か「MySQL」のいずれかをインストールすることとします

![image.png](https://qiita-image-store.s3.amazonaws.com/0/155423/6c24ba27-835d-809a-b7b7-bb79836b65c1.png)

パスワードは、PostgreSQLは「postgres」、MySQLは未設定（空文字列）で設定しておくと、Phoenix PJ作成後に設定変更が不要になります

※下記リンク先コラムの中には、「パスワードの weak 評価はダメ」といった記載ありますが、本番環境の構築をしている訳では無いので構いません

## ①PostgreSQLのインストール

![image.png](https://qiita-image-store.s3.amazonaws.com/0/155423/5ce639ea-d036-d50f-f0c4-9389a0e9e548.png)

下記OS毎のインストール手順を実施してください

Windows：https://eng-entrance.com/postgresql-download-install
macOS：https://qiita.com/okame_qiita/items/ac7b6a7d96d07ecbc50b
Ubuntu：https://qiita.com/eighty8/items/82063beab09ab9e41692
CentOS 7：https://weblabo.oscasierra.net/postgresql10-centos7-install/
CentOS 6：https://weblabo.oscasierra.net/postgresql-installing-postgresql9-centos6-1/

なおUbuntuの場合、インストール直後のpostgresユーザのパスワードを下記SQLで設定し、PostgreSQLを再起動する必要があります

```sql
ALTER USER postgres WITH PASSWORD 'postgres';
```

## ②MySQLのインストール

![image.png](https://qiita-image-store.s3.amazonaws.com/0/155423/a60e12bd-f2c7-d04e-ee24-1f225a1b8a6e.png)

下記OS毎のインストール手順を実施してください

Windows：https://qiita.com/KeisyaRinco/items/c3074d8450cad96f7e4f
macOS：https://qiita.com/griffin3104/items/c7908359a3e3e18cd269
Ubuntu：https://www.server-world.info/query?os=Ubuntu_16.04&p=mysql
CentOS 7：https://enomotodev.hatenablog.com/entry/2016/09/01/225200
CentOS 6：https://qiita.com/UmedaTakefumi/items/924cdce7cfff083bf492

# Vue.js向け内部API用Phoenix PJを作成

Phoenix PJを作成します

```
mix phx.new vue_sample
Fetch and install dependencies? [Yn] （←y、Enterを入力）
…
cd vue_sample
```

あと、PostgreSQLの場合は、DB指定不要ですが、MySQLの場合は、DB指定が必要です

```:※MySQLの場合
mix phx.new vue_sample --database=mysql
Fetch and install dependencies? [Yn] （←n、Enterを入力）
…
```

パスワードを、以下以外で設定している場合は、config/dev.exsの「password」項目の修正が必要です

- PostgreSQL：postgres
- MySQL：未設定（空文字列）

```exs:config/dev.exs　※MySQLの場合
…
# Configure your database
config :vue_sample, VueSample.Repo,
  username: "root",
  password: "",
  hostname: "localhost",
  database: "vue_sample_dev",
  show_sensitive_data_on_connection_error: true,
  pool_size: 10
…
```

ちなみにPostgreSQLだと、以下に相当します

```exs:config/dev.exs　※PostgreSQLの場合
…
# Configure your database
config :vue_sample, VueSample.Repo,
  username: "postgres",
  password: "postgres",
  hostname: "localhost",
  database: "vue_sample_dev",
  show_sensitive_data_on_connection_error: true,
  pool_size: 10
…
```

DBを作成します

```
mix ecto.create
```

Phoenixサーバーを起動します

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

ブラウザで「```http://localhost:4000```」にアクセスすると、Phoenixで作られたWebページが見れます
![image.png](https://qiita-image-store.s3.amazonaws.com/0/155423/818578fd-10b1-ddfa-c32e-71526aa52a9a.png)

# PhoenixでAPIを作る

PhoenixでAPIを作るには、mixコマンドで、以下のように行います

Ctrl+cを2回押して、一度、Phoenixを停止してから、コマンドを入力します

```
mix phx.gen.json Api Post posts title:string body:text
```

以下ログと、実行後の作業指示が示されます

```
* creating lib/vue_sample_web/controllers/post_controller.ex
* creating lib/vue_sample_web/views/post_view.ex
* creating test/vue_sample_web/controllers/post_controller_test.exs
* creating lib/vue_sample_web/views/changeset_view.ex
* creating lib/vue_sample_web/controllers/fallback_controller.ex
* creating lib/vue_sample/api/post.ex
* creating priv/repo/migrations/20220201143346_create_posts.exs
* creating lib/vue_sample/api.ex
* injecting lib/vue_sample/api.ex
* creating test/vue_sample/api_test.exs
* injecting test/vue_sample/api_test.exs
* creating test/support/fixtures/api_fixtures.ex
* injecting test/support/fixtures/api_fixtures.ex

Add the resource to your :api scope in lib/vue_sample_web/router.ex:

    resources "/posts", PostController, except: [:new, :edit]


Remember to update your repository by running migrations:

    $ mix ecto.migrate
```

まず、ルーティングにAPI用エントリーとして、上記「resources "/posts", ～」を、「get "/", ～」直下に追記します

```elixir:lib/vue_sample_web/router.ex
defmodule VueSampleWeb.Router do
  use VueSampleWeb, :router
  …
  scope "/", VueSampleWeb do
    pipe_through :browser

    get "/", PageController, :index
    resources "/posts", PostController, except: [:new, :edit]
  end
  …
```

マイグレートします

```
mix ecto.migrate
```

以下ログのように、テーブルが作成されます

```
01:51:55.226 [debug] Selecting all records by match specification `[{{:schema_migrations, :"$1", :"$2"}, [], [[:"$1"]]}]` with limit nil

01:51:55.282 [info]  == Running VueSample.Repo.Migrations.CreatePosts.change/0 forward

01:51:55.282 [info]  create table posts

01:51:55.310 [info]  == Migrated in 0.0s
```

Phoenixを起動してください

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

# APIでデータを参照する

先ほど作成したAPIで、データを参照してみましょう（と言っても、未だデータ投入していないので、空が返ってくるだけですが…）

APIを叩くために、REST APIクライアントが必要ですが、Firefoxの「RESTClient」、Chromeの「Postman」が便利です

Firefox「RESTClient」
https://addons.mozilla.org/ja/firefox/addon/restclient/

Chrome「Postman」
https://chrome.google.com/webstore/detail/postman/fhbjgbiflinjbdggehcddcbncdddomop

ここでは、FirefoxのRESTClientをインストールして、使ってみます

まず、RESTClientの「Headers」メニューで、「Custom Header」を選択して、REST API用のヘッダー設定をします
![image.png](https://qiita-image-store.s3.amazonaws.com/0/155423/3fcf4caf-6348-6a5c-0435-9da669af6151.png)

次に、URLに「```http://localhost:4000/posts```」を入れ、「SEND」ボタンをクリックすると、以下のような画面になります
![image.png](https://qiita-image-store.s3.amazonaws.com/0/155423/4a59d151-de16-3267-65eb-d1b6ddd04564.png)

「Headers」タブから、「Response」タブに切り替えると、中身が空のデータが返っていることが確認できます
![image.png](https://qiita-image-store.s3.amazonaws.com/0/155423/91d72891-0337-310c-6cc4-69196e9b6f39.png)

# APIでデータを投入する

では、APIでデータ投入してみましょう

「Method」を「POST」に変更し、「Body」に以下を入力します

```
{ "post": { "title": "t1", "body": "b1" } }
```

「SEND」ボタンをクリックすると、以下の通り、エラーになります
![image.png](https://qiita-image-store.s3.amazonaws.com/0/155423/250e300c-f32e-ceba-9fb5-3ad1db090086.png)

「Response」の中身を見てみると、CSRFトークンエラーが出ています

これは、リクエストにCSRFトークンが付与されていないことが原因ですが、[ステートレスなAPIでCSRF対応](http://watanabe-tsuyoshi.hatenablog.com/entry/2015/03/04/123649)は、このコラムの本筋から大きく外れるので、いったんシンプルにrouter.exに設定されているCSRF対策を解除します

```elixir:lib/vue_sample_web/router.ex
defmodule VueSampleWeb.Router do
  use VueSampleWeb, :router

  pipeline :browser do
    plug :accepts, ["html"]
    plug :fetch_session
    plug :fetch_flash
#   plug :protect_from_forgery
    plug :put_secure_browser_headers
  end
  …
```

コンパイルします

```
iex> recompile
```

再度、「SEND」ボタンをクリックすると、今度は成功します
![image.png](https://qiita-image-store.s3.amazonaws.com/0/155423/d68fd338-11c9-7a73-a496-72cbc6008002.png)

「Method」を「GET」に切り替え、「SEND」ボタンをクリックすると、投入したデータが返っていることが確認できます
![image.png](https://qiita-image-store.s3.amazonaws.com/0/155423/23470997-96f8-5228-891d-da8583a635bf.png)

もう1件、データ投入しておきましょう

「Method」を「POST」に変更し、「Body」に以下を入力し、「SEND」ボタンをクリックします

```
{ "post": { "title": "t2", "body": "b2" } }
```

# Vue.jsでデータ表示する（axiosでAPI呼出）

Vue.jsとaxiosを使って、上記で作成したAPIの呼び出しを行い、テーブル表示してみます

Vue.jsとaxiosは、「<script src=～」でCDN（Content Delivery Network）を指定することでロードします

「var app = new Vue」で始まるブロックが、Vue.jsのメイン処理です

メイン処理の「el」で指定したものと同じidを、上方のdivタグで指定していますが、Vue.jsはこの中で有効です

trタグに指定した「v-for」では、メイン処理の「data」で定義しているresults配列を1件ずつ取り出し、resultに代入しており、各フィールドを「{{ result.【フィールド名】 }}」という記述で取得できます

なお、results配列は、初期表示時に実行される「mounted」ハンドラ内の、axiosによるAPI呼出で更新されます

```html:lib/vue_sample_web/templates/page/index.html.heex
<section class="phx-hero">
  <h1><%= gettext "Welcome to %{name}!", name: "Phoenix" %></h1>
  <p>Peace of mind from prototype to production</p>
</section>

<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.18.0/axios.min.js"></script>

<div id="app">

<h1>Posts</h1>
<table border="1">
<tr v-for="result in results">
	<td style="padding: 10px;">{{ result.title }}</td>
	<td style="padding: 10px;">{{ result.body }}</td>
</tr>
</table>

</div>

<script>
	var app = new Vue
	( {
		el: '#app',
		data: 
		{
			results: [], 
		}, 
		mounted()
		{
			axios.get( '/posts' )
			.then( response => { this.results = response.data.data } )
		}, 
	} )
</script>
```

APIで取得できるデータがテーブル表示されます
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/155423/dd16ea9d-57bd-335d-1231-8ccf431fdbfb.png)

RESTClientから、もう1件、データ投入します

```
{ "post": { "title": "t3", "body": "b3" } }
```

ブラウザをリロードすると、増やしたデータが表示されます
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/155423/cdfde730-5b80-ddbf-4641-acbb1dc30d88.png)

# 終わり

今回は、Vue.jsを使ったフロントサイドと、Phoenixによる内部APIを組み合わせを用いて、APIにより取得したデータを画面表示してみました

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