ElixirでもPHP的に気軽なWebアプリ開発をしたい（Phoenixより薄いWebFW実現？）

[fukuoka.ex／kokura.ex](https://fukuokaex.connpass.com/)のpiacereです
ご覧いただいて、ありがとうございます :bow:

Phoenixは、Rails同様、MVC※やルーティングによるWebアプリ構築を前提に置いているため、PHPのような気軽さでWebアプリを作るのに向いていないと思われがちです
<font color="silver">※正確には、Phoenixのソレは、MVCのようで、MVCではありません</font>

他にも、「Phoenixを使わず、薄いWebサーバを使いたい」といった声を聞くことがありますが、よくよく聞いてみると、ステータスコードをキメ細かく調整したり、サーバプロセスを自前で起動したい等の本来的な薄いWebサーバを必要としている訳では無く、「MVCを使いたくない」「ルーティングを書きたくない」といったレベルの要望だったりすることも多いです

そこで、Phoenixのルーティングやリクエストハンドラーを司る「Plug」の柔軟性を用いれば、PHPと同様の気軽さ…つまり「ロジック入りhtmlファイルを置いたパスにエンドポイントが作られる」という形式でWebアプリ開発することもできる … ということを実感していただくためにコラムを書きました

<h3>:ocean::ocean::ocean: Advent Calendar、fukuoka.ex1位、Elixir2位達成ヽ(=´▽`=)ﾉ :ocean::ocean::ocean:</h3>

fukuoka.ex Advent Calendar、Webテクノロジーカテゴリで堂々1位 … 各コラムぜひお読みください
https://qiita.com/advent-calendar/2020/fukuokaex
[![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/155423/8a22a81f-2917-6072-10bd-525e0fc24b75.png)](https://qiita.com/advent-calendar/2020/neosvr_reso)

そして、プログラミング言語カテゴリは、1位がRust、2位がElixir、3位がGoとモダン言語揃い踏みでのトップ3、熱いネー:laughing:
https://qiita.com/advent-calendar/2020/elixir
[![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/155423/ea185203-c525-c778-dc10-6d2ecc391616.png)](https://qiita.com/advent-calendar/2020/elixir)

# 本コラムの検証環境

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

- Windows 10
- Elixir 1.10.1 　　　　 ※[<b>最新版のインストール手順はコチラ</b>](https://qiita.com/piacere_ex/items/6714e1440e3f25fb46a1#%E4%BA%8B%E5%89%8D%E6%BA%96%E5%82%99elixir%E3%81%AE%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB)
- Phoenix 1.4.15　　　※[<b>最新版のインストール手順はコチラ</b>](https://qiita.com/piacerex/items/b7787580fce5f148242f#phoenix%E3%81%AE%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB)
- Node.js 12.14.0

なおPhoenixは、1.3系でも動作します（ElixirもPhoenixのバージョンに準じた古いものでも大丈夫です）

# 事前準備：Phoenix PJを作成する

まずPhoenix PJを作成し、Phoenixを起動します

```
mix phx.new basic --no-ecto
…
Fetch and install dependencies? [Yn] 【Yを入力してEnter】
cd basic
iex -S mix phx.server
```

なおチョイ技として、上記Yで走るmix deps.getとnpm installのうち、npm installが遅いので、コンソールを2枚立てて、裏で走らせると、構築を待たずに先に進むこともできます

```:1枚目のコンソール
mix phx.new basic --no-ecto
…
Fetch and install dependencies? [Yn] 【Nを入力してEnter】
cd basic
mix deps.get
iex -S mix phx.server
```

```:2枚目のコンソール
cd 【上記Phoenix PJを作った元のフォルダ】
cd basic/asset
npm install
```

ブラウザで``「http://localhost:4000」``にアクセスすると、Phoenixで作られたデフォルトのWebページが見れます
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/155423/505f0d28-8e5a-2c3a-7a35-82e24aff1aa8.png)

# PHPのような気軽さでWebアプリを作るには？

Phoenixが前提としている「特定MVCへのルーティング」をhtml.eexファイル名へと自動マッピングし、mix phx.newで生成されたコントローラがデフォルトで素通ししていないGETパラメータを素通しすればOKです

なお、html.eexファイル名へのマッピング時、複数階層のフォルダもマッピング可能とすることで、サブフォルダ配下のhtml.eexファイルにも気軽にアクセスできるようになります

# 手順①：html.eexの表示をルーティング不要に

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

「path_」パラメータには、URLに指定されたフォルダ階層が、リストで入ってきます（たとえば、abc/defというパスであれば、[ "abc", "def" ]という感じで）

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

#    get "/", PageController, :index  # <-- remove here
    get "/*path_", PageController, :index  # <-- add here
…
```

# 手順②：html.eexの複数階層フォルダ指定可能に

render()の第2引数に指定するパスは、デフォルトだと複数階層を指定できないので、Viewにワイルドカード的なパスのパターン指定を追加すれば、複数階層を指定可能にできます

```elixir:lib/basic_web.ex
defmodule BasicWeb do
…
  def view do
    quote do
      use Phoenix.View,
        pattern: "**/*",  # <-- add here
        root: "lib/basic_web/templates",
        namespace: BasicWeb, 
…
```

# 手順③：パスの自動マッピング、GETパラメータの素通し

PageControllerで、「path_」パラメータのフォルダ階層リストを、html.eexのパスにマッピングします

また、mix phx.newで生成されたコントローラが「_params」という感じでアンダースコアで未使用にしているので、これを解除し、renderの第3引数に渡すことで、GETパラメータを素通しさせます

```elixir:lib/basic_web/controllers/page_controller.ex
defmodule BasicWeb.PageController do
  use BasicWeb, :controller

  def index( conn, params ) do
    template = if params[ "path_" ] == nil, do: "index.html", else: Path.join( params[ "path_" ] ) <> ".html"
    render( conn, template, params: params )
  end
end
```

# 手順④：html.eexページを追加する

## １）templates/page直下にhtml.eex追加

これで、page配下にhtml.eexを追加するだけで、新たなページが追加可能となったので、追加してみます

```erb:lib/basic_web/templates/page/another.html.eex
<h1>This is another page</h1>

<%= inspect( System.build_info() ) %>
<hr>
<%= inspect( @params ) %>
```

ここまでが終わったら、ブラウザで``「http://localhost:4000/abc/def?abc=xyz123」``にアクセスすると、以下のような結果が表示されることを確認して、準備完了です
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/155423/aff62c93-8848-0a24-e09e-cc2662e3d6f4.png)

これで、ルーティングやMVCを追加せずとも、PHPのように、新たなページ追加やGETパラメータ処理できるようになりました

## ２）templates/page配下のサブフォルダ下にhtml.eex追加

pageフォルダ配下にフォルダ階層を掘って、そこにhtml.eexを配置しても動きます

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

```erb:lib/basic_web/templates/page/abc/def.html.eex
<h1>I'm def page</h1>

<%= inspect( @params ) %>
```

ブラウザで``「http://localhost:4000/abc/def」``にアクセスすると、以下のような結果が表示されます（「path_」パラメータにフォルダ階層がリストで入っていることも確認できます）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/155423/29ab9c5a-dafb-59d3-a660-1d6c3b004eac.png)

## ３）GETフォーム処理も気軽に書く

GETパラメータが通るようになっているので、formでフォーム処理も気軽に書けます

checkboxのような複数値を取るタイプには、nameの末尾に「[]」を置く点が、忘れてはならないポイントです

```erb:lib/basic_web/templates/page/abc/form.html.eex
<h1>I'm form page</h1>

<form method="GET" action="/abc/def">

<p>
メモ：
<input name="memo" type="text">
</p>

<p>
好きな言語：
<input name="language[]" type="checkbox" value="Elixir">Elixir
<input name="language[]" type="checkbox" value="Rust">Rust
<input name="language[]" type="checkbox" value="Julia">Julia
</p>

<p>
年代：
<select name="age">
  <option value="10">10代
  <option value="20">20代
  <option value="30">30代
  <option value="30">40代
  <option value="50">50代
  <option value="60">60代
  <option value="70">70代以上
</select>
</p>

<input type="submit" value="送信">

</form>
```

ブラウザで``「http://localhost:4000/abc/form」``にアクセスすると、以下のような結果が表示されます
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/155423/83cd3f93-516f-1bce-75e9-a01d1c6bf8ca.png)

「送信」ボタンを押下すると、入力されたパラメータが表示されますが、def.html.eexでやっているように、@ paramsの中身を使ってフォーム処理を行うことができます

checkboxは、選択したものが、リストで入ってきます
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/155423/ddc8004a-dccf-dcd7-e8b3-e401ab0d8e12.png)

## ４）POSTフォーム処理も気軽に書く

POSTパラメータによるフォーム処理に対応するには、ルーティングにPOSTの分も書きます

```elixir:lib/basic_web/router.ex
defmodule BasicWeb.Router do
…
  scope "/", BasicWeb do
…
    post "/*path_", PageController, :index  # <-- add here
…
```

formのmethodをPOSTに変更します

```erb:lib/basic_web/templates/page/abc/form.html.eex
<h1>I'm form page</h1>

<form method="POST" action="/abc/def">
              ^-- modify here
…
```

ブラウザで``「http://localhost:4000/abc/form」``にアクセスし、「送信」ボタンを押下すると、GET同様、POSTでも入力されたパラメータが表示されるようになります（GETのときはURLにパラメータがあったのに対し、POSTでは無くなっています）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/155423/83070f53-8ea1-4d27-87d4-8edb2debcf3e.png)

# 終わり

Phoenixで、PHPみたいに気軽なWebアプリ開発をする手順を紹介しました

Phoenixは、ルーティングとMVCを前提にしていますが、Plugの機能とコントローラでのパラメータ処理を併用すれば、このような気軽さを導入することもできます

無論、ルーティングやMVCは、理由があって採用されている面もあるので、そのメリット／デメリットはちゃんと学んだり、理解しつつ、一方で、このテクニックを使って、簡単なWebアプリ構築もお楽しみください

なお、Elixirサーバサイドでリアルタイムフロントが書ける[「LiveView」においても、この気軽さを導入するコラム](https://qiita.com/piacerex/items/f6963b87a1bf624b506f)書いていますので、ご参考にどうぞ

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

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/155423/67568d54-6584-ad33-77c7-b3b3afe70287.png) や ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/155423/f5b819ef-d655-ef72-294c-b1a9d2c5b563.png) にて、どうぞ応援よろしくお願いします:bow: