Help us understand the problem. What is going on with this article?

mix phx.gen.jsonを使わず気軽に作れるPhoenix API

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

Phoenixは、mix phx.gen.jsonで、DBアクセスをラップするREST APIが自動生成できますが、DB利用の無い、任意のAPIを作るときの作法が分かりにくいため、最小限のAPIを手動で作成するサンプルを挙げておきます

PhoenixによるAPIの構築が、とてもシンプルで、かつAPI返却は、どの言語よりも直感的だと言うことをご理解いただける内容です

なお、「Phoenix」は、ElixirのWebフレームワークです

内容が、面白かったり、気に入ったら、「いいね」よろしくお願いします :wink:

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」にアクセスすると、以下ページが表示されることを確認します
image.png

APIリクエスト受付コントローラファイルを追加

以下のコントローラファイルを追加します

このコントローラのindexという関数で、APIリクエストを受付できます

なお、このindexという関数名は、後述するルーティングと合わせていれば、どんな関数名でもOKです

中で呼び出しているrenderで、レスポンス返却処理を行います

lib/api_sample_web/controllers/api_controller.ex
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内に、返却するデータを記述します

lib/api_sample_web/views/api_view.ex
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に追加します

lib/api_sample_web/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からの返却が確認できます
image.png
ビューのrenderをいじって、返却内容が変わることを確認してみてください

ここまでが、Phoenixで、最小限のAPIを構築する手順となります

とても簡単で、シンプルな構造だと言うことが、お分かりいただけたのでは無いかと思います

APIリクエストで指定されたパラメータを返却に含める

コントローラが受けたパラメータは、既にrenderに渡しているので、ビューを以下のように修正すれば、paramsにパラメータが入るようになります

lib/api_sample_web/views/api_view.ex
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が出てきます
image.png
ブラウザ(かRESTクライアント)のURLを、「http://localhost:4000/api?media=Twitter&count=100」に変更して、アクセスすると、params配下にURLで指定したパラメータが出てきます
image.png

自前の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を呼びます

lib/vue_sample_web/controllers/post_controller.ex
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】で生成されるファイルで、以下のように定義されています

lib/vue_sample/api/api.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時は、このフォーマットそのものが返却されます

lib/vue_sample_web/views/post_view.ex
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.「いいね」よろしくお願いします

ページ左上の image.pngimage.png のクリックを、どうぞよろしくお願いします:bow:
ここの数字が増えると、書き手としては「ウケている」という感覚が得られ、連載を更に進化させていくモチベーションになりますので、もっとElixirネタを見たいというあなた、私達と一緒に盛り上げてください!:tada:

piacerex
福岡でプログラマしながらIT商社とIT企業を経営してます。Elixir/Kerasをよく使う。Elixirコミュ#fukuokaex、福岡理学部#FukuokaScienceを主催。プログラマ歴36年/XPer歴19年/デジタルマーケッター/経営者/CTO/技術顧問数社。 シボと重力子放射線射出装置は別腹(^^)
https://github.com/piacerex
fukuokaex
エンジニア/企業向けにElixirプロダクト開発・SI案件開発を支援する福岡のコミュニティ
https://fukuokaex.fun/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away