fukuoka.ex代表のpiacereです
ご覧いただいて、ありがとうございます
Phoenixは、mix phx.gen.jsonで、DBアクセスをラップするREST APIが自動生成できますが、DB利用の無い、任意のAPIを作るときの作法が分かりにくいため、最小限のAPIを手動で作成するサンプルを挙げておきます
PhoenixによるAPIの構築が、とてもシンプルで、かつAPI返却は、どの言語よりも直感的だと言うことをご理解いただける内容です
なお、「Phoenix」は、ElixirのWebフレームワークです
内容が、面白かったり、気に入ったら、「いいね」よろしくお願いします
API作成対象となるPhoenix PJの作成
まずは、まっさらなPhoenix PJにAPIを追加する手順で説明するので、Phoenix PJを作成してください
mix phx.new api_sample --no-ecto
…(ファイル作成ログが続く)…
Fetch and install dependencies? [Yn] (←y、Enterを入力)
Phoenixを起動します
cd api_sample
iex -S mix phx.server
ブラウザで「http://localhost:4000
」にアクセスすると、以下ページが表示されることを確認します
APIリクエスト受付コントローラファイルを追加
以下のコントローラファイルを追加します
このコントローラのindexという関数で、APIリクエストを受付できます
なお、このindexという関数名は、後述するルーティングと合わせていれば、どんな関数名でもOKです
中で呼び出しているrenderで、レスポンス返却処理を行います
defmodule ApiSampleWeb.ApiController do
use ApiSampleWeb, :controller
def index( conn, params ) do
render( conn, "api.json", api_data: params )
end
end
APIレスポンス返却ビューファイルを追加
以下のビューファイルを追加します
このビューのrenderという関数で、APIレスポンスを返却できます
なお、このrenderという関数名は、Phoenixのビルトイン関数のため、関数名は変更できません(コントローラから呼び出されている引数3つのrenderから、ここで定義する引数2つのrenderがコールバックされます)
また、第一引数の「api.json」と、第二引数の「%{ api_data: _data }」のキーである「api_data」は、上記コントローラのrender呼出の第二引数および第三引数のキーと一致させる必要があります(逆に言えば、両方を書き換えてしまえば良い、ということです)
render内に、返却するデータを記述します
defmodule ApiSampleWeb.ApiView do
use ApiSampleWeb, :view
def render( "api.json", %{ api_data: _params } ) do
%{
id: 123,
name: "hoge"
}
end
end
Elixirのマップ(もしくはマップリスト)が、そのままJSON化されるので、どの言語よりも、直感的な記述でグッドです
API用エントリーポイントをルーティングに追加
APIのためのエントリーポイントをrouter.exに追加します
defmodule ApiSampleWeb.Router do
…
scope "/", ApiSampleWeb do
pipe_through :browser
get "/", PageController, :index
get "/api", ApiController, :index # ←ここを追加
end
最小限のAPIを動作確認する
ブラウザ(かRESTクライアント)で、「http://localhost:4000/api
」にアクセスすると、以下のように、APIからの返却が確認できます
ビューのrenderをいじって、返却内容が変わることを確認してみてください
ここまでが、Phoenixで、最小限のAPIを構築する手順となります
とても簡単で、シンプルな構造だと言うことが、お分かりいただけたのでは無いかと思います
APIリクエストで指定されたパラメータを返却に含める
コントローラが受けたパラメータは、既にrenderに渡しているので、ビューを以下のように修正すれば、paramsにパラメータが入るようになります
defmodule ApiSampleWeb.ApiView do
use ApiSampleWeb, :view
def render( "api.json", %{ api_data: params } ) do
%{
params: params,
id: 123,
name: "hoge"
}
end
end
ブラウザ(かRESTクライアント)をリロードすると、空のparamsが出てきます
ブラウザ(かRESTクライアント)のURLを、「http://localhost:4000/api?media=Twitter&count=100
」に変更して、アクセスすると、params配下にURLで指定したパラメータが出てきます
自前のPhoenix PJにAPIを追加するには?
上記2ファイルの「ApiSample」を、既存のPhoenix PJ名に置換し、配置した後、router.exに上記同様のエントリー追加をすればOKです
オマケ:mix phx.gen.jsonで生成される内容の解説
基本は、ここまでで終わりですが、mix phx.gen.jsonで生成されるコントローラ/ビューについても、少し解説しておきます
対象は、Elixir/Phoenix入門チュートリアルの第6回、Vue.js+内部API(表示編) で生成したコントローラ/ビューとします
まず、コントローラですが、index/create/show/update共に、DBアクセッサに定義されたDB操作関数であるlist_postsやcreate_post等を呼び出した後、上記サンプル同様、renderを呼びます
defmodule VueSampleWeb.PostController do
…
def index(conn, _params) do
posts = Api.list_posts()
render(conn, "index.json", posts: posts)
end
def create(conn, %{"post" => post_params}) do
with {:ok, %Post{} = post} <- Api.create_post(post_params) do
conn
|> put_status(:created)
|> put_resp_header("location", post_path(conn, :show, post))
|> render("show.json", post: post)
end
end
def show(conn, %{"id" => id}) do
post = Api.get_post!(id)
render(conn, "show.json", post: post)
end
def update(conn, %{"id" => id, "post" => post_params}) do
post = Api.get_post!(id)
with {:ok, %Post{} = post} <- Api.update_post(post, post_params) do
render(conn, "show.json", post: post)
end
end
def delete(conn, %{"id" => id}) do
post = Api.get_post!(id)
with {:ok, %Post{}} <- Api.delete_post(post) do
send_resp(conn, :no_content, "")
end
end
…
router.exでは、「resource」でエントリーポイントが指定されますが、実際に各関数が、どのREST APIにマッピングされているかは、以下mixコマンドで確認できます
mix phx.routes
…
post_path GET /posts VueSampleWeb.PostController :index
post_path GET /posts/:id VueSampleWeb.PostController :show
post_path POST /posts VueSampleWeb.PostController :create
post_path PATCH /posts/:id VueSampleWeb.PostController :update
PUT /posts/:id VueSampleWeb.PostController :update
post_path DELETE /posts/:id VueSampleWeb.PostController :delete
DBアクセッサは、コンテキスト名(mix phx.gen.jsonの第一引数に指定する文字列)のフォルダ配下に、【コンテキスト名.ex】で生成されるファイルで、以下のように定義されています
defmodule VueSample.Api do
@moduledoc """
The Api context.
"""
…
def list_posts do
Repo.all(Post)
end
…
def create_post(attrs \\ %{}) do
%Post{}
|> Post.changeset(attrs)
|> Repo.insert()
end
…
次にビューですが、1つ目のrenderは、コントローラのindexから、Phoenixコールバック経由で呼ばれます
2つ目のrenderは、コントローラのcreate/show/updateから、Phoenixコールバック経由で呼ばれます
3つ目のrenderは、上記2つのrender内で呼ばれているrender_many(複数データ返却用)およびrender_one(単一データ返却用)からコールバックされる、データ返却用フォーマットです
render_many時は、このフォーマットのリストが返却され、render_one時は、このフォーマットそのものが返却されます
defmodule VueSampleWeb.PostView do
use VueSampleWeb, :view
alias VueSampleWeb.PostView
def render("index.json", %{posts: posts}) do
%{data: render_many(posts, PostView, "post.json")}
end
def render("show.json", %{post: post}) do
%{data: render_one(post, PostView, "post.json")}
end
def render("post.json", %{post: post}) do
%{id: post.id,
title: post.title,
body: post.body}
end
end
終わり
Phoenixにおける最小限のAPIを、手動作成のコントローラ/ビューで作成してみました
更に、mix phx.gen.jsonで生成されるコントローラ/ビューについても解説しました
DB利用の無い任意のAPIを作るときや、mix phx.gen.jsonで生成されたDB利用時のAPIを改造する際の基本が、今回の内容で掴めたのでは無いかと思います
【2019/3/22追記】
以下のコラムで、今回の知識を応用したコラムを書いてます
p.s.「いいね」よろしくお願いします
ページ左上の や
のクリックを、どうぞよろしくお願いします
ここの数字が増えると、書き手としては「ウケている」という感覚が得られ、連載を更に進化させていくモチベーションになりますので、もっとElixirネタを見たいというあなた、私達と一緒に盛り上げてください!