おなじみgit探検隊
Elixirに限ったことではないが、定期的にgitでトレンドのレポジトリはチェックするようにしている
そうすると大体、何が流行っていて何に注目が集まっているかが何となく分かる
最近は中国語のREAD.MEが多くて翻訳ないと詰む
さておき、また今回も良さげなElixirのレポジトリを発見した
github.com
hexdocs.pm
An Elixir web micro-framework
説明の通り、ウェブのマイクロフレームワーク
開発自体は3年程前からされているようでラストコミットは2018年の7月っぽい
ちょうどElixirでAPIをさくっと作りたかったのでありがたい
信じてもらえるか分からないが実はPhoenixでjsonAPIを作ったことはある
普通に楽々作れるし、ドキュメントもある
じゃあ、なんでtrotを使うんだって話ですけど
単に作業量が圧倒的に少ないからに限る
cloud functionやらpythonのresponderやらトレンドはミニマムに高速で作ることだと思っている
Phoenixは1つエンドポイントを作るだけとかマイクロサービスを作る際にはオーバースペック感がある
そんな機能は別に使わへんがな... あとは学習コストの問題も
だったらさくっと覚えられて素早く使える物がいいよねってことでこうなった(事後
色々とエラーには遭遇してかなり時間は取られたことは内緒
プロジェクトの新規作成
いつも通りです
mix new simple_api
./simple_api/mix.exs に以下を追加
trotのREAD.MEにはtrotだけが記述されているが、僕の環境ではerrorになったので
plug_cowboyも追加している
fakerに関しては後に使うためインストールするようにしているが無くても問題ない
def application do
[
extra_applications: [:logger],
applications: [:trot, :faker]
]
end
defp deps do
[
{:trot, github: "hexedpackets/trot"},
{:plug_cowboy, "~> 1.0"},
{:faker, "~> 0.12"},
]
end
plug_cowboyをインストールするようにしていなかった時のerror
warning: please add the following dependency to your mix.exs:
{:plug_cowboy, "~> 1.0"}
次に./sample_api/config/config.exs に以下を追加
公式ドキュメントだと割とひっそり書かれていて気づかない場合があるかもしれないが
少なくともrouterを設定するモジュールを記述しないとnot foundをただ返すだけの
ポンコツサーバーが出来上がる
config :trot, :port, 4000
config :trot, :router, SimpleApi
これで準備は完了
APIをさくっと作る
今回は特にdbを使っていないので触れるのは「GETとPOST」メゾットのみ
データのやり取りが出来れば十分
公式では
- GET
- POST
- PUT
- PATCH
- DELETE
- OPTIONS
をサポートしている
まずはGETから書いてみる
モジュールの頭に以下を追加
use Trot.Router
このRouterを記述したモジュールが先ほどconfigに記述したモジュール名と等しくなるようにする
./simple_api/lib/simple_api.ex
defmodule SimpleApi do
use Trot.Router
get "/easy-get" do
"what's up!!"
end
end
たったこれだけ
マクロになっていてサクッと記述できる
第2引数でheader情報にマッチが取れるらしいが今の所使い道は思いつかない
さっそくターミナルからcurlしてみる
❯ curl http://localhost:4000/easy-get
what's up!!%
いいですね。無事に文字列が帰ってきてる
最後に%が付くのはデフォルト? なぜだろう
次にPOST
値の受け取り方に関して公式ドキュメントに一切の記述が無くて不親切だなと思った
色々と調べた結果、Plugの関数を使うことで取得できることが分かったが
かなりここに時間をとられた(1日調べまくった
そもそもconnという謎の変数の存在に気付けへんわ
ここにリクエスト情報が詰まっているっぽい
user = conn.body_params["user"]
%Plug.Conn{
adapter: {Plug.Adapters.Cowboy.Conn, :...},
assigns: %{},
:
:
host: "localhost",
method: "POST",
owner:
params: %{"user" => "okb"},
path_info: ["easy-post"],
path_params: %{},
port: 4000,
:
:
scheme: :http,
script_name: [],
secret_key_base: nil,
state: :unset,
status: nil
}
あとは簡単
普通に書くだけ(日本語難しい
post "/easy-post" do
user = conn.body_params["user"]
res = "hello, #{user}"
IO.puts(res)
res
end
呼び出す
curl -H "Content-Type: application/json" -X POST -d '{"user": "okb"}' http://localhost:4000/easy-post
hello, okb%
無事に値が返ってきた
ついでにサーバーサイドのログにも"hello, okb"が出力されている
基本的な部分に触れたところでもう少し便利なAPIを作ってみる
他のモジュール関数を呼び出す
せっかくなので過去に作成したモジュールを使用する
まずはGETメゾットが叩かれた時にmockデータを返すようなAPIを作る(ずっとほしかった
実装にFakerというライブラリを使っている
詳しくは以下をご覧ください
www.okb-shelf.work
今回は試しがてらURLから値を受け取る(queryではない)をやってみる
URLに渡した指定数分だけデータを返すようにしている
defmodule CreateMock do
def user_info(num) do
1..num |> Enum.map(fn -> () end)
end
defp _create_user_info do
%{
email: Faker.Internet.email(),
city: Faker.Address.En.city(),
name: Faker.Name.En.first_name() <> " " <> Faker.Name.En.last_name(),
phone: Faker.Phone.EnUs.phone(),
food: Faker.Food.dish(),
age: trunc(:rand.uniform() * 100)
}
end
end
get "/mock/:create_num" do
info = CreateMock.user_info(String.to_integer(create_num))
%{data: info}
end
呼び出してみる
❯ curl http://localhost:4000/mock/2
{"data":[{"phone":"(988) 898-5220","name":"Hilario Dach","food":"Scotch eggs","email":"wallace.emmerich@spinka.info","city":"East Cierra","age":83},{"phone":"431-588-1937","name":"Faustino Senger","food":"Meatballs with sauce","email":"rey2086@kris.net","city":"South Friedrich","age":0}]}%
やったぜ
次にPOSTで送った2つの文字列が一致しているか、どれだけタイポしているかをチェックする
詳しくは以下をご覧ください
www.okb-shelf.work
defmodule TypoChecker do
def main(input_val, ans_val) when input_val == ans_val, do: :good
def main(input_val, ans_val), do: _main(String.graphemes(input_val), ans_val, 0)
defp _main([], , counter), do: counter
defp _main([head | tail], ans_val, counter) do
str_lst = String.graphemes(ans_val)
[_n_head | n_tail] = str_lst
case type_judge(str_lst, head) do
:hit -> _main(tail, List.to_string(n_tail), counter+1)
:empty -> _main(tail, List.to_string(n_tail), counter)
end
end
def type_judge([], ), do: :empty
def type_judge([head | tail], compare_str) do
if String.contains?(compare_str, head) do
:hit
else
type_judge(tail, compare_str)
end
end
end
post "/typo-check" do
input_val = conn.body_params["input_val"]
ans_val = conn.body_params["ans_val"]
case TypoChecker.main(input_val, ans_val) do
:good -> "no typo"
_ -> "is typo"
end
end
Elixirのatomをそのままreturnするとerrorになるため
case使って無理やり文字列を返すようにしている
では呼び出す
> curl -H "Content-Type: application/json" -X POST -d '{"input_val": "apple", "ans_val": "apple"}' http://localhost:4000/typo-check
no typo%
> curl -H "Content-Type: application/json" -X POST -d '{"input_val": "apple", "ans_val": "appple"}' http://localhost:4000/typo-check
is typo%
いいですね
使い方さえ分かってしまえばtrotでのAPI作成は爆速ですわ
あとはheorkuとかにデプロイする方法を調査してまた記事にでもします