LoginSignup
57
41

More than 3 years have passed since last update.

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

Last updated at Posted at 2020-03-22

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

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

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

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

:ocean::ocean::ocean: Advent Calendar、fukuoka.ex1位、Elixir2位達成ヽ(=´▽`=)ノ :ocean::ocean::ocean:

fukuoka.ex Advent Calendar、Webテクノロジーカテゴリで堂々1位 … 各コラムぜひお読みください
https://qiita.com/advent-calendar/2020/fukuokaex
image.png

そして、プログラミング言語カテゴリは、1位がRust、2位がElixir、3位がGoとモダン言語揃い踏みでのトップ3、熱いネー:laughing:
https://qiita.com/advent-calendar/2020/elixir
image.png

本コラムの検証環境

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

なお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

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" ]という感じで)

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にワイルドカード的なパスのパターン指定を追加すれば、複数階層を指定可能にできます

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パラメータを素通しさせます

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ページを追加する

1)templates/page直下にhtml.eex追加

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

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

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

2)templates/page配下のサブフォルダ下にhtml.eex追加

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

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

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

3)GETフォーム処理も気軽に書く

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

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

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

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

checkboxは、選択したものが、リストで入ってきます
image.png

4)POSTフォーム処理も気軽に書く

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

lib/basic_web/router.ex
defmodule BasicWeb.Router do

  scope "/", BasicWeb do

    post "/*path_", PageController, :index  # <-- add here

formのmethodをPOSTに変更します

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

終わり

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

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

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

なお、Elixirサーバサイドでリアルタイムフロントが書ける「LiveView」においても、この気軽さを導入するコラム書いていますので、ご参考にどうぞ

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

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

57
41
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
57
41