Chris Chris - 6 months ago 46
JSON Question

How to properly send JSON data to a phx phoenix API

I am working on a Phoenix / phx API written using Elixir, and I am trying to test the functionality of the JSON API using Postman. I'm sending JSON data to the API to hopefully create a new user, but phx is giving me the following error.

Phoenix.ActionClauseError at POST /api/users


Not sure where the problem lies exactly, so I'll post the router and the controller for the phx project.

router.ex

defmodule KegCopRAPI.Web.Router do
use KegCopRAPI.Web, :router

pipeline :browser do
plug :accepts, ["html"]
plug :fetch_session
plug :fetch_flash
plug :protect_from_forgery
plug :put_secure_browser_headers
end

pipeline :api do
plug :accepts, ["json"]
plug Guardian.Plug.VerifyHeader, realm: "Bearer"
plug Guardian.Plug.LoadResource
end

scope "/", KegCopRAPI.Web do
pipe_through :browser # Use the default browser stack

get "/", PageController, :index
end

# Other scopes may use custom stacks.
scope "/api", KegCopRAPI.Web do
pipe_through :api

post "/sessions", SessionController, :create
delete "/sessions", SessionController, :delete
post "/sessions/refresh", SessionController, :refresh

resources "/users", UserController, except: [:show, :index, :new, :edit]
end
end


user_controller.ex

def create(conn, %{"user" => user_params}) do
# with {:ok, %User{} = user} <- Accounts.create_user(user_params) do
changeset = User.registration_changeset(%User{}, user_params)

case Repo.insert(changeset) do
{:ok, user} ->
new_conn = Guardian.Plug.api_sign_in(conn, user, :access)
jwt = Guardian.Plug.current_token(new_conn)
# conn
# |> put_status(:created)
# |> put_resp_header("location", user_path(conn, :show, user))
# |> render("show.json", user: user)
new_conn
|> put_status(:created)
|> render(KegCopRAPI.SessionView, "show.json", user: user, jwt: jwt)
{:error, changeset} ->
conn
|> put_status(:unprocessable_entity)
|> render(KegCopRAPI.ChangesetView, "error.json", changeset: changeset)
end
end


The entire project can be found here
Any and all help would greatly be appreciated.

I'm sending data to the API with the following Postman settings,
postman settings

The complete error message below,

[info] POST /api/users
[debug] Processing with KegCopRAPI.Web.UserController.create/2
Parameters: %{"email" => "foo@example.com", "password" => "[FILTERED]", "username" => "foo"}
Pipelines: [:api]
[info] Sent 400 in 10ms
[debug] ** (Phoenix.ActionClauseError) could not find a matching KegCopRAPI.Web.UserController.create clause
to process request. This typically happens when there is a
parameter mismatch but may also happen when any of the other
action arguments do not match. The request parameters are:

%{"email" => "foo@example.com", "password" => "password", "username" => "foo"}

(kegcopr_api) lib/kegcopr_api/web/controllers/user_controller.ex:14: KegCopRAPI.Web.UserController.create(%Plug.Conn{adapter: {Plug.Adapters.Cowboy.Conn, :...}, assigns: %{}, before_send: [#Function<1.33581574/1 in Plug.Logger.call/2>, #Function<0.72433304/1 in Phoenix.LiveReloader.before_send_inject_reloader/2>], body_params: %{"email" => "foo@example.com", "password" => "password", "username" => "foo"}, cookies: %Plug.Conn.Unfetched{aspect: :cookies}, halted: false, host: "localhost", method: "POST", owner: #PID<0.2357.0>, params: %{"email" => "foo@example.com", "password" => "password", "username" => "foo"}, path_info: ["api", "users"], path_params: %{}, peer: {{127, 0, 0, 1}, 59852}, port: 4000, private: %{KegCopRAPI.Web.Router => {[], %{}}, :guardian_default_resource => nil, :phoenix_action => :create, :phoenix_controller => KegCopRAPI.Web.UserController, :phoenix_endpoint => KegCopRAPI.Web.Endpoint, :phoenix_format => "json", :phoenix_layout => {KegCopRAPI.Web.LayoutView, :app}, :phoenix_pipelines => [:api], :phoenix_router => KegCopRAPI.Web.Router, :phoenix_view => KegCopRAPI.Web.UserView, :plug_session_fetch => #Function<1.131660147/1 in Plug.Session.fetch_session/1>}, query_params: %{}, query_string: "", remote_ip: {127, 0, 0, 1}, req_cookies: %Plug.Conn.Unfetched{aspect: :cookies}, req_headers: [{"cache-control", "no-cache"}, {"postman-token", "da608739-758b-40d7-bdef-23b3c2a63bed"}, {"content-type", "application/json"}, {"user-agent", "PostmanRuntime/3.0.11-hotfix.2"}, {"accept", "*/*"}, {"host", "localhost:4000"}, {"accept-encoding", "gzip, deflate"}, {"content-length", "76"}, {"connection", "keep-alive"}], request_path: "/api/users", resp_body: nil, resp_cookies: %{}, resp_headers: [{"cache-control", "max-age=0, private, must-revalidate"}, {"x-request-id", "slfdhshckenp3dinlqr22m5nlakhoaq4"}, {"access-control-allow-origin", "*"}, {"access-control-expose-headers", ""}, {"access-control-allow-credentials", "true"}, {"vary", ""}], scheme: :http, script_name: [], secret_key_base: "fIEpvi5ujSQEKgmkRpt83KiLPq068sSmvFKlWFZyNpi3nkNmUtYO24Em6cXIUblZ", state: :unset, status: nil}, %{"email" => "foo@example.com", "password" => "password", "username" => "foo"})
(kegcopr_api) lib/kegcopr_api/web/controllers/user_controller.ex:1: KegCopRAPI.Web.UserController.action/2
(kegcopr_api) lib/kegcopr_api/web/controllers/user_controller.ex:1: KegCopRAPI.Web.UserController.phoenix_controller_pipeline/2
(kegcopr_api) lib/kegcopr_api/web/endpoint.ex:1: KegCopRAPI.Web.Endpoint.instrument/4
(phoenix) lib/phoenix/router.ex:277: Phoenix.Router.__call__/1
(kegcopr_api) lib/kegcopr_api/web/endpoint.ex:1: KegCopRAPI.Web.Endpoint.plug_builder_call/2
(kegcopr_api) lib/plug/debugger.ex:123: KegCopRAPI.Web.Endpoint."call (overridable 3)"/2
(kegcopr_api) lib/kegcopr_api/web/endpoint.ex:1: KegCopRAPI.Web.Endpoint.call/2
(plug) lib/plug/adapters/cowboy/handler.ex:15: Plug.Adapters.Cowboy.Handler.upgrade/4
(cowboy) /opt/elixir/kegcopr_api/deps/cowboy/src/cowboy_protocol.erl:442: :cowboy_protocol.execute/4

Answer Source

Since you're using the data from the map inside the "user" key, you need to put all the fields under the "user" key in the request, like this:

{
  "user": {
    "username": "foo",
    "email": "foo@example.com",
    "password": "password"
  }
}