In one of our Rails 4 app, we decided to move file and image uploads to another microservice so that the load on server is reduced when a big file is uploaded. We decided to do this in Phoenix.
In Phoenix, we have ex_aws package which makes file uploads to S3 very simple just like Rails. So lets get started.
Add ex_aws
Update mix.exs to include following dependencies.
defp deps do
  [
    ...,
    {:ex_aws, "~> 1.0"},
    {:poison, "~> 2.0"},
    {:hackney, "~> 1.6"},
    {:sweet_xml, "~> 0.6"},
  ]
end
We need :sweet_xml so that we can parse XML response from S3.
Don't forget to update the applications list.
 def application do
   [
     mod: {ApplicationName, []},
     extra_applications: [..., :ex_aws, :hackney, :poison]
   ]
 end
Finally run mix deps.get to install above dependencies.
Set AWS credentials
I prefer to add all important keys specific to a project as environment variables. So we will create .env file in root directory of the project and add following code to it.
export AWS_ACCESS_KEY_ID=<......>
export AWS_SECRET_ACCESS_KEY=<.....>
export BUCKET_NAME=<.....>
Don't forget to run source .env on console to make sure all environment variables are loaded before you compile the project.
Next, update config.exs to include the AWS credentials.
# Configure :ex_aws
config :ex_aws,
  access_key_id: System.get_env("AWS_ACCESS_KEY_ID"),
  secret_access_key: System.get_env("AWS_SECRET_ACCESS_KEY"),
  region: "us-east-1"
System.get_env() is used to read environment variables.
Write code
When Phoenix receives a file to upload, a Plug.Upload struct is received in the params. Here's an example of what the params would look like for an image upload:
%{
  "upload" => %Plug.Upload{
       content_type: "image/png",
       filename: "some_image.png",
       path: "/var/folders/nd/snztgzpd6t92bdkfm6lnm9l00000gn/T//plug-1484/multipart-890782-844137-2"
  }
}
Phoenix will put the image in a temporary location. The image is saved till the request is completed i.e. once the conn is returned, the image will be deleted.
Now we create upload service (UploadService) under /lib directory.
defmodule S3Upload.UploadService do
  def upload_to_s3(upload_params) do
    file = upload_params.path
    bucket_name = System.get_env("BUCKET_NAME")
    s3_path = "path/on/s3"
    file
      |> ExAws.S3.Upload.stream_file
      |> ExAws.S3.upload(bucket_name, s3_path)
      |> ExAws.request!
    s3_url = "http://#{bucket_name}.s3.amazonaws.com/#{s3_path}"
    %{
      s3_url: s3_url
    }
  end
end
Next we will add a controller UploadController -
defmodule S3UploadWeb.UploadController do
  use S3UploadWeb, :controller
  alias S3Upload.UploadService
  def upload(conn, %{"upload" => upload_params}) do
    resp = UploadService.upload_to_s3(upload_params)
    render(conn, "s3_response.json", resp: resp)
  end
end
And a view s3_response.ex -
defmodule S3UploadWeb.UploadView do
  use S3UploadWeb, :view
  def render("s3_response.json", %{resp: resp}) do
    resp
  end
end
Lastly, we add route in router.ex to accept files
scope "/", S3UploadWeb do
  ...
  post "/upload", UploadController, :upload
end
Now you're set to start uploading files.
Be sure to checkout the documentation on Plug.Upload and ex_aws.
