What plug do i need to accept FormData ("multipart/form-data") type of post request?

Hey besically the title says it,
I am trying to send a file to my elixir api and, i just noticed that in the router it has this plug: plug(:accepts, ["json"]), but since i am trying to send a file i am sending a multipart request not json.
Thoughts?

You shouldn’t need to change anything in the Plug pipeline - the file should be included as part of the form wrapped in a Plug.Upload struct. If you’re not seeing the file in your params it might be that you’re hitting the upload limits? The blog post on uploads would have more info: https://phoenixframework.org/blog/file-uploads

no i dont think i hit it, i am trying to send the file like this :

  let data = new FormData();
        data.append("xd", {
          recording: rec,
          challenge_id: 1,
          user_id: 1,
          mod_score: 3
        });
        console.log(data);
        axios
          .post(
            config.API_URL + "recordings",
            { data },
            {
              headers: {
                Authorization: "Bearer " + this.props.auth.token,
                "content-type": "multipart/form-data"
              }
            }
          ) 

but only an empty struct %{} show up

Can you also show the code of your controller?

glady :smile:

def create(conn, %{"recording" => recording_params}) do

    with {:ok, %Recording{} = recording} <- Web.create_recording(recording_params) do
      challenge = Web.get_challenge!(recording.challenge_id)
      number_of_days_between = Date.diff(challenge.due_date, recording.inserted_at)
      calculated_score = number_of_days_between * challenge.difficulty * 100
      user = Web.get_user!(recording.user_id)
      IO.inspect(user)
      score_to_insert = user.score + calculated_score

      updated_user = %{
        score: score_to_insert
      }

      Web.update_user(user, updated_user)
      send_resp(conn, 200, [])
      # conn
      # |> put_status(:created)
      # |> render("show.json", recording: recording)
    end
  end

youcould see most of the related code in this post: Sending and recieveing a file(audio) from app to elixir api issue

im chaing the request to try to match but no success yet

And where is it you see only an empty map?

i am runnign the server with foreground command and says this

info] POST /api/recordings
[debug] QUERY OK source="users" db=0.3ms decode=0.1ms queue=0.2ms
SELECT u0."id", u0."name", u0."password_hash", u0."score", u0."avatar", u0."role_id", u0."team_id", u
0."inserted_at", u0."updated_at" FROM "users" AS u0 WHERE (u0."id" = $1) [2]
[debug] QUERY OK source="roles" db=0.2ms queue=0.2ms
SELECT r0."id", r0."name", r0."inserted_at", r0."updated_at", r0."id" FROM "roles" AS r0 WHERE (r0."i
d" = $1) [1]
[debug] QUERY OK source="teams" db=0.2ms queue=0.5ms
SELECT t0."id", t0."name", t0."inserted_at", t0."updated_at", t0."id" FROM "teams" AS t0 WHERE (t0."i
d" = $1) [2]
[debug] Processing with Userteam1Web.RecordingController.create/2
  Parameters: %{}
  Pipelines: [:api, :jwt_authenticated]
[info] Sent 400 in 38ms
[debug] ** (Phoenix.ActionClauseError) could not find a matching Userteam1Web.RecordingController.cre
ate 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:
  %{}

here at the end

ok something intersting, if i remove this: ```
"content-type": "multipart/form-data"

then this shows up, still doesnt match but it shows up:

%{"data" =&gt; %{"_parts" =&gt; [["recording", %{"_duration" =&gt; -1, "_fsPath" =&gt; "/data/user/0/com.cobrn/files/filename.mp4", "_lastSync" =&gt; -1, "_options" =&gt; %{"autoDestroy" =&gt; true}, "_path" =&gt; "filename.mp4", "_position" =&gt; -1, "_recorderId" =&gt; 0, "_state" =&gt; -2}], ["challenge_id", 1], ["user_id", 1]]}}

it very much seems like our api does not accept formdata

According to axios’ documentation JS part should be like this:

let data = new FormData();
data.append("recording[file]", rec);
data.append("recording[challenge_id]", 1);
data.append("recording[user_id]", 1);
data.append("recording[mod_score]", 3);
console.log(data);
axios.post(
    config.API_URL + "recordings",
    data,
    {
        headers: {
            Authorization: "Bearer " + this.props.auth.token,
            "Content-Type": "multipart/form-data"
        }
    }
);

To match your controller.

1 Like

Hey really really appreciate the answer, i tried this code and got this reponse:

`%{"challenge_id" => "1", "mod_score" => "3", "user_id" => "1"}`

and a 422 response code
so it seems like the file is not there for some reason, but the request is correct

could you link that part of their docs? I can’t seem to find it :upside_down_face:

figured it out, the upload was incorrect, it is working like this:

data.append("recording[path_to_recording]", {
      uri: "file://" + rec._fsPath,
      name: "filename.mp4",
      type: "audio/mp4"
    });

figured it out, the upload was incorrect, it is working like this, you need to specify the type

data.append("recording[path_to_recording]", {
      uri: "file://" + rec._fsPath,
      name: "filename.mp4",
      type: "audio/mp4"
    });

Actually it was not the docs but an example https://github.com/axios/axios/tree/master/examples/upload .
You can also check this link https://serversideup.net/uploading-files-vuejs-axios/

1 Like

Are you using Plug.Parsers? https://hexdocs.pm/plug/Plug.Parsers.html

hm, in endpoint.ex i have it yes

Hello, I am having the same / similar issue.
I try including multipart in my header.

My endpoint.ex shows

  plug Plug.Parsers,
    parsers: [:urlencoded, :multipart, :json],
    pass: ["*/*"],
    json_decoder: Poison

I upload as a post with the body being a dictionary, key: photo, value: (the form data).

In logging conn and params in my controller I see nothing indicative of data being sent up.

Is there something else I have to configure in my controller to allow file upload? The JS side looks fine with the uploading of FormData.

Are you using nginx in front of Phoenix? I had this problem once and found out nginx had a maximum size for post data and was rejecting them before they got to Phoenix. I had to google to find out how to raise that maximum limit in nginx but it fixed my situation.

2 Likes

For me the issue was on the js side, the elixir side was fine as it turned out, your form data might be incorrect.
What we did was that we appended everything to the formdata and only sent that like this for example:

function uploadAvatar(user, file, token) {
  let fd = new FormData();
  fd.append("user[avatar]", {
uri: file.path,
name: "test.jpg",
type: file.mime
  });
  fd.append("id", user.id);

  return {
types: [
  userConstants.UPLOAD_AVATAR_REQUEST,
  userConstants.UPLOAD_AVATAR_SUCCESS,
  userConstants.UPLOAD_AVATAR_FAILURE
],
callAPI: () =>
  axios
    .put(config.API_URL + "user", fd, {
      headers: { Authorization: "Bearer " + token }
    })
    .then(res => res.data)
  };
}

and you see where you append you need to set the key like it is in the example like “id” and if it is a param then you do like “user[avatar]” so your controller can use it to do what yo want to do

2 Likes