Elixir + Phoenix でお手軽にJSONを返すWebAPIを構築
この記事はHamee Advent Calendar 2016の15日目の記事です。
今回は、ElixirのWebフレームワークPhoenixを使って、QiitaのAdvent Calendar 2016ランキングをJSONで返すWebAPIを構築してみます。
調べているとDBを利用する場合の記事が多く、DBを利用しない場合に上手く動作しなかったので、DBを使わない場合のの実装方法をまとめておきます。
やること
前提条件
- Elixirの環境構築済み
インストール
詳細は公式のインストールガイドをご参照ください。
Phoenixのインストール
以下のコマンドでPhoenixをインストールします。
$ mix archive.install https://github.com/phoenixframework/archives/raw/master/phoenix_new.ez
node.jsのインストール
Phoenixではjavascript, cssのcompileにbrunch.ioを利用しているため、node.js(>= 5.0.0)が必要になります。
macの場合はhomebrew経由でインストールできます。
$ brew install node
プロジェクトの作成
プロジェクトを作成していきましょう。
今回はスクレイピング結果をそのままJSON形式で返すAPIサーバーで、DBとViewは不要なので、オプションでその旨を指定しておきます。
$ mix phoenix.new --no-ecto --no-brunch --no-html web_api
ルーティングの設定
web/router.ex
に、ルーティングの設定を追記していきます。
get "/ranking", CalendarController, :index
が新しく追記した部分です。
/api/ranking
にGETリクエストがあった際に、CalendarController
のindex
アクションを実行します。
web/router.ex
defmodule WebApi.Router do use WebApi.Web, :router pipeline :api do plug :accepts, ["json"] end scope "/api", WebApi do pipe_through :api get "/ranking", CalendarController, :index end end
ライブラリのインストール
コントローラーの実装に入る前に、スクレイピングで使用するライブラリの依存関係をmix.exs
に追記していきます。
mix.exs
defmodule WebApi.Mixfile do use Mix.Project def project do [app: :web_api, version: "0.0.1", elixir: "~> 1.2", elixirc_paths: elixirc_paths(Mix.env), compilers: [:phoenix, :gettext] ++ Mix.compilers, build_embedded: Mix.env == :prod, start_permanent: Mix.env == :prod, deps: deps()] end # Configuration for the OTP application. # # Type `mix help compile.app` for more information. def application do [mod: {WebApi, []}, applications: [:phoenix, :phoenix_pubsub, :cowboy, :logger, :gettext, :httpoison, :floki]] end # Specifies which paths to compile per environment. defp elixirc_paths(:test), do: ["lib", "web", "test/support"] defp elixirc_paths(_), do: ["lib", "web"] # Specifies your project dependencies. # # Type `mix help deps` for examples and options. defp deps do [ {:phoenix, "~> 1.2.1"}, {:phoenix_pubsub, "~> 1.0"}, {:gettext, "~> 0.11"}, {:cowboy, "~> 1.0"}, {:httpoison, "~> 0.10.0"}, {:floki, "~> 0.11.0"} ] end end
コントローラーの実装
最後にQiitaのAdvent Calendar 2016ランキングからデータを抽出して、JSON形式で返す処理を実装していきます。
JSON形式で返す処理はjson conn, calendars
の部分です。
このように記述することで、PhoenixがJSON形式に変換してレスポンスを投げてくれます。
web/controllers/calendar_controller.ex
defmodule WebApi.CalendarController do use WebApi.Web, :controller def index(conn, _params) do calendars = ranking() json conn, calendars end # Advent Calendar 2016ランキングからランキング情報を抽出 defp ranking() do url = "http://qiita.com/advent-calendar/2016/ranking/subscriptions" HTTPoison.start res = HTTPoison.get! url %HTTPoison.Response{status_code: 200, body: body} = res body |> Floki.find(".adventCalendarRankingListItem-top, .adventCalendarRankingListItem") |> Enum.map(&parse_item/1) end # DOM要素からランキング、タイトル、ページリンクを抽出 defp parse_item(item) do rank = Floki.find(item, ".adventCalendarRankingListItem_rank") |> Floki.text title_link = Floki.find(item, ".adventCalendarRankingListItem_name > a") title = Floki.text(title_link) url = "http://qiita.com#{Floki.attribute(title_link, "href")}" %{rank: rank, title: title, url: url} end end
レスポンスを取得してみる
サーバーを起動して、レスポンスを取得してみます。
$ mix compile $ mix phoenix.server
$ curl "http://localhost:4000/api/ranking" | jq [ { "url": "http://qiita.com/advent-calendar/2016/docker", "title": "Docker", "rank": "1" }, { "url": "http://qiita.com/advent-calendar/2016/git", "title": "Git", "rank": "2" }, { "url": "http://qiita.com/advent-calendar/2016/go", "title": "Go", "rank": "3" }, { "url": "http://qiita.com/advent-calendar/2016/job", "title": "転職", "rank": "4" }, { "url": "http://qiita.com/advent-calendar/2016/deeplearning", "title": "DeepLearning", "rank": "5" }, { "url": "http://qiita.com/advent-calendar/2016/vim", "title": "Vim", "rank": "6" }, { "url": "http://qiita.com/advent-calendar/2016/ruby", "title": "Ruby", "rank": "7" }, { "url": "http://qiita.com/advent-calendar/2016/python", "title": "Python", "rank": "8" }, { "url": "http://qiita.com/advent-calendar/2016/muscle", "title": "筋肉", "rank": "9" }, { "url": "http://qiita.com/advent-calendar/2016/nodejs", "title": "Node.js", "rank": "10" }, { "url": "http://qiita.com/advent-calendar/2016/go2", "title": "Go (その2)", "rank": "11" }, { "url": "http://qiita.com/advent-calendar/2016/ouch-hack", "title": "おうちハック", "rank": "12" }, { "url": "http://qiita.com/advent-calendar/2016/go3", "title": "Go (その3)", "rank": "13" }, { "url": "http://qiita.com/advent-calendar/2016/vim2", "title": "Vim (その2)", "rank": "14" }, { "url": "http://qiita.com/advent-calendar/2016/se", "title": "システムエンジニア", "rank": "15" }, { "url": "http://qiita.com/advent-calendar/2016/python_python", "title": "Python", "rank": "16" }, { "url": "http://qiita.com/advent-calendar/2016/docker2", "title": "Docker2", "rank": "17" }, { "url": "http://qiita.com/advent-calendar/2016/tensorflow", "title": "TensorFlow", "rank": "18" }, { "url": "http://qiita.com/advent-calendar/2016/swift", "title": "Swift", "rank": "19" }, { "url": "http://qiita.com/advent-calendar/2016/job2", "title": "転職(その2)", "rank": "20" } ]