Phoenix(Elixir)でJSONを返すシンプルなWebAPIを作成

最近、なにかと話題のElixir。
達人プログラマやプログラミングRubyの著者で有名なDave Thomasさんが書いた
「Programming Elixir」で一気に知名度が上がりました。

Erlangの仮想環境上で動作するとか、並行処理が得意だとか、Ruby風味でとっつきやすいとか etc...
言語の特徴はすでにいろいろなサイト上で紹介されているので、あえてここでは触れません。
じっくり触ってみて、いずれ自分なりの見解を示したいと思っています。

では、アウトプットとしてElixir製のWebFrameworkである[Phoenix](http://www.phoenixframework.org/, "PhoenixFramework")を利用して、
RESTFulなWebAPIを実装してみたいと思います。

## __バージョン__

- MacOSX El Capitan（10.11.6）
- VirtualBox（5.1.8）
- Vagrant（1.8.7）
- Ubuntu（16.04 Xenial Xerus）
- PostgreSQL（9.6）
- Erlang/OTP（19.2.3）
- Elixir（1.4.1）
- Phoenix（1.2.1）

## __1. 仮想環境構築__

[こちら](http://qiita.com/eighty8/items/e71f32d52d7df9e0509a)を参考に環境を作っておいてください。

## __2. PostgreSQLをインストール__

PhoenixはデフォルトのデータストアをPostgreSQLに指定しているので、
[こちら](http://qiita.com/eighty8/items/82063beab09ab9e41692)を参考にインストールしておいてください。

## __3. Erlang/OTPをインストール__

冒頭でも書いた通り、ElixirはErlangの仮想環境上で動作するので、まずはErlang本体をインストールします。
ちなみにOTPは Open Telecom Platform の略で、Erlangの標準ツール類とライブラリ群の集合体です。
Erlang自体は言語仕様そのもので、OTPが無いと何もできないとか。
ErlangのコンパイラとインタプリタはこのOTPに含まれています。
OTPについては[こちら](http://erlang.org/doc/design_principles/des_princ.html)に詳しく書かれているので一読してみてください。
日本語翻訳は[ここ](http://erlangworld.web.fc2.com/otp/index.html)

### Erlangのdebパッケージをダウンロード

適当なディレクトリにdebパッケージをダウンロードしてください。

```
$ cd /var/tmp
```

```
$ wget https://packages.erlang-solutions.com/erlang-solutions_1.0_all.deb
```

### パッケージの取り込み

```
$ sudo dpkg -i erlang-solutions_1.0_all.deb
```

### パッケージのアップデート

```
$ sudo apt-get update
```

### Erlangのインストール

```
$ sudo apt-get install esl-erlang
```

### 確認

```
$ which erl
/usr/bin/erl
```

## __4. Elixirをインストール__

Elixirのdebパッケージもありますが、今後頻繁にバージョンアップされる事を考慮してexenvでインストールします。
Rubyにはrbenvというバージョン管理ツールがありますが、そのElixir版がexenvになります。

### exenvのダウンロード

```
$ sudo -s
# git clone https://github.com/mururu/exenv.git /usr/local/exenv
# git clone https://github.com/mururu/elixir-build.git /usr/local/exenv/plugins/elixir-build
```

### exenvのパスを設定

どのユーザーでもexenvコマンドが使えるようにします。

```
$ vim /etc/profile.d/exenv.sh
```

```
export EXENV_ROOT="/usr/local/exenv"
export PATH="$EXENV_ROOT/bin:$PATH"
eval "$(exenv init -)"
```

### 設定を有効にする

```
# exec $SHELL -l
```

### インストール可能なElixirのバージョンを表示

```
# exenv install --list
```

### Elixirのインストール

```
# exenv install 1.4.1
```

### システム全体で使用するElixirのバージョンを指定する

```
# exenv global 1.4.1
```

### コマンド群を配置

```
# exenv rehash
```

### 確認

```
# elixir -v
Erlang/OTP 19 [erts-8.2] [source-fbd2db2] [64-bit] [smp:2:2] [async-threads:10] [hipe] [kernel-poll:false]

Elixir 1.4.1
```

## __5. Elixir＆Erlang向けパッケージ管理ライブラリとビルドツールのインストール__

一旦rootから抜ける

```
# exit
```

一般ユーザーでのexenvを有効化

```
$ exec $SHELL -l
$ which exenv
```

### hexのインストール

```
$ mix local.hex --force
```

--force で最新を持ってきます

### rebarのインストール

```
$ mix local.rebar --force
```

### バージョン確認

```
$ mix hex.info
```

```
Hex:    0.15.0
Elixir: 1.4.1
OTP:    19.2.3

Built with: Elixir 1.3.4 and OTP 18.3.4.4
```
## __6. Phoenixのインストール__

### __インストール実行__

```
$ mix archive.install https://github.com/phoenixframework/archives/raw/master/phoenix_new.ez
```

### __バージョン確認__

```
$ mix phoenix.new -v
```

```
Phoenix v1.2.1
```

## __7. プロジェクトの作成__

ここまできたらようやくPhoenixフレームワークでAPIを作成する準備が整います。
それではやってみましょう！

### 適当なプロジェクトディレクトリを作成し移動

```
$ sudo mkdir -p /var/webapp
```

```
$ sudo chown vagrant:vagrant /var/webapp
```

```
$ cd /var/webapp
```

### プロジェクトの作成

今回はTODOを管理するAPIを作成したいと思います。
APIなのでレイアウトやフロント周りのビルドツールは必要ありません。
--no-brunchと--no-htmlオプションで除外しておきます。

```
$ mix phoenix.new todo_api --no-brunch --no-html
```

作成されたtodo_api配下に移動し中身を確認すると、
下記のような構成になっているはずです。

```
$ cd todo_api
$ ls -l
drwxrwxr-x  9 vagrant vagrant 4096 Feb  9 23:04 ./
drwxr-xr-x  4 vagrant vagrant 4096 Feb 13 22:52 ../
drwxrwxr-x  4 vagrant vagrant 4096 Feb  9 23:06 _build/
drwxrwxr-x  2 vagrant vagrant 4096 Feb  9 23:06 config/
drwxrwxr-x 19 vagrant vagrant 4096 Feb  9 23:04 deps/
-rw-rw-r--  1 vagrant vagrant  378 Feb  9 23:04 .gitignore
drwxrwxr-x  3 vagrant vagrant 4096 Feb  9 23:04 lib/
-rw-rw-r--  1 vagrant vagrant 1549 Feb  9 23:04 mix.exs
-rw-rw-r--  1 vagrant vagrant 3513 Feb  9 23:04 mix.lock
drwxrwxr-x  5 vagrant vagrant 4096 Feb  9 23:04 priv/
-rw-rw-r--  1 vagrant vagrant  693 Feb  9 23:04 README.md
drwxrwxr-x  7 vagrant vagrant 4096 Feb  9 23:04 test/
drwxrwxr-x  7 vagrant vagrant 4096 Feb  9 23:21 web/
```

## __8. DBのセットアップ__

PhoenixはデフォルトのデータストアにPostgreSQLが指定されているので、
事前に作成したPostgresのDB情報をPhoenixのDB設定に反映します。

__開発用__

```
$ vim config/dev.exs
```

```
# Configure your database
config :todo_api, TodoApi.Repo,
  adapter: Ecto.Adapters.Postgres,
  username: "作成したユーザー名",
  password: "作成したユーザーのパスワード",
  database: "todo_api_dev",
  hostname: "localhost",
  pool_size: 10
```

__テスト用__

```
$ vim config/test.exs
```

```
# Configure your database
config :todo_api, TodoApi.Repo,
  adapter: Ecto.Adapters.Postgres,
  username: "作成したユーザー名",
  password: "作成したユーザーのパスワード",
  database: "todo_api_test",
  hostname: "localhost",
  pool_size: 10
```

下記のコマンドでDBを作成します。

```
$ mix ecto.create
```

テストを実行してみます。

```
$ mix test
```

問題なくテストが通りました。

```
Compiling 6 files (.ex)
.............

Finished in 0.9 seconds
13 tests, 0 failures

Randomized with seed 903290
```

## __9. APIの作成__

Railsにはscaffoldというアプリケーションの基盤を作成するジェネレータがありますが、
Elixirにも同様の機能が存在します。
今回はそれを利用してAPIの基盤を作成してみます。

ちなみにTODOの管理対象は

- TODOの内容
- 完了フラグ

の2つだけを持つとてもシンプルなものにします。
また、レスポンスはJSONで返したいのでphoenix.gen.jsonを指定します。
（その他のジェネレータについては、mix --help | grep phoenix.gen で調べてみてください。）

```
$ mix phoenix.gen.json Todo todos description:string complete:boolean
```

ジェネレータを実行すると priv/repo/migrations/ 配下に後述するマイグレーションファイルが作成されます。
その他にもいろいろ作成されていますが説明は省きます。

```
* creating web/controllers/todo_controller.ex
* creating web/views/todo_view.ex
* creating test/controllers/todo_controller_test.exs
* creating web/views/changeset_view.ex
* creating web/models/todo.ex
* creating test/models/todo_test.exs
* creating priv/repo/migrations/20170214011354_create_todo.exs ← マイグレーションファイル

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

    resources "/todos", TodoController, except: [:new, :edit]

Remember to update your repository by running migrations:

    $ mix ecto.migrate
```

メッセージの中に「resources "/todos", TodoController, except: [:new, :edit]」の1行を、
ルーターのscopeブロックに追記してくださいと書かれているので追記します。

## __10. ルーターの編集__

```
$ vim web/router.ex
```

```
defmodule TodoApi.Router do
  use TodoApi.Web, :router

  pipeline :api do
    plug :accepts, ["json"]
  end

  scope "/api", TodoApi do
    pipe_through :api

    ### ここに追記 ###
    resources "/todos", TodoController, except: [:new, :edit]
  end
end
```

## __11. マイグレーションの実行__

マイグレーションとは、マイグレーションファイルというテーブル定義書のようなものから、
SQLを書く事なくテーブルを作成したりフィールドやインデックスを追加したり、
作成したものをロールバックさせたりする機能です。
Railsではお決まりの機能ですね。

```
$ mix ecto.migrate
```

マイグレーションを実行すると priv/repo/migrations/ 配下にマイグレーションファイルが作成されます。

```
$ ls -l priv/repo/migrations
-rw-rw-r-- 1 vagrant vagrant  244 Feb 14 10:13 20170214011354_create_todo.exs
```

## __12. APIの動作確認__

ここまでできたらサーバーを起動し、アクセスしてみましょう。

### サーバーの起動

下記のコマンドを実行するとErlang製のWebサーバー（Cowboy）が起動します。

```
$ mix phoenix.server
```

```
[info] Running TodoApi.Endpoint with Cowboy using http://localhost:4000
```

### アクセスして確認

他のマシーンからアクセスしてレスポンスが返ってくれば成功です。

```
$ curl -X GET "http://192.168.xxx.xxx:4000/api/todos"
{"data":[]}
```

まだ、データが何も登録されていなので空のレスポンスが返ってきます。

Cowboyのログ

```
[info] GET /api/todos
[debug] Processing by TodoApi.TodoController.index/2
  Parameters: %{}
  Pipelines: [:api]
[debug] QUERY OK source="todos" db=1.1ms
SELECT t0."id", t0."description", t0."complete", t0."inserted_at", t0."updated_at" FROM "todos" AS t0 []
[info] Sent 200 in 99ms
```

ためしに何かTODOをPOSTしてみます。

```
$ curl "http://192.168.xxx.xxx:4000/api/todos" -X POST -d "todo[description]=掃除をする" -d "todo[complete]=false"
{"data":{"id":1,"description":"掃除をする","complete":false}}

$ curl "http://192.168.xxx.xxx:4000/api/todos" -X POST -d "todo[description]=買い物に行く" -d "todo[complete]=true"
{"data":{"id":2,"description":"買い物に行く","complete":true}}

$ curl "http://192.168.xxx.xxx:4000/api/todos" -X POST -d "todo[description]=勉強する" -d "todo[complete]=false"
{"data":{"id":2,"description":"勉強する","complete":false}}
```

再度GETしてみます

```
curl -X GET "http://localhost:4000/api/todos"
{"data":[{"id":1,"description":"掃除をする","complete":false},{"id":2,"description":"買い物に行く","complete":true},{"id":3,"description":"勉強する","complete":false}]}
```

問題なく投稿したTODOが取得できました！

PATCHやDELETEもできそうです。
詳しくは web/controllers/todo_controller.ex を覗いてみてください。

## __13. おわりに__

環境のセットアップが少々煩雑な感じを受けますが、
普段からRailsを触っている人にとってはほぼ迷いなく導入できると思います。

また、今回はscaffoldで手っ取り早く作成したので、
Phoenix（Elixir）のコードには一切触れていません。

なので、今後は自動で生成されたコード類を読みながら、
より理解を深めていきたいと思っています。