File Upload using Elixir, Phoenix, Absinthe and ExAws
Tags: elixir, absinthe, phoenix, aws
Published: 2021-09-25 06:23:57

Hello, I'm quite new in the elixir world. I want to write some notes so it will help me for later if I want to try this again. This guide will not really details. I will assume that you familiar enough to know about Elixir, Phoenix, Absinthe or ExAws itself. I will assume you are also familiar with AWS especially S3.

Alternative library that you might be interested: https://github.com/stavro/arc

## Preparation

- Make sure you are already install Elixir in your computer/PC.
- Generate new Phoenix Project. (If you are already have the Phoenix Project, you can skip this step).

`mix archive.install hex phx_new`

`mix phx.new elixir_exploration`

Note:

1. [Phoenix Installation Documentation](https://hexdocs.pm/phoenix/installation.html#content)
2. [Phoenix Generate Project](https://hexdocs.pm/phoenix/up_and_running.html#content)
3. Make sure you are already have PostgreSQL, if you not want to use database, please refer to note number 2, how to generate without `Ecto` which handle the database side.

- Add Absinthe and ExAWS as Dependencies. Add these to `mix.exs`

```elixir
defp deps do
    [
      ... # rest of dependencies, please not input this too
      {:absinthe, "~> 1.6"},
      {:absinthe_plug, "~> 1.5"},
      {:absinthe_phoenix, "~> 2.0"},
      {:ex_aws, "~> 2.2"},
      {:ex_aws_s3, "~> 2.0"},
      {:poison, "~> 3.0"},
      {:hackney, "~> 1.9"},
      {:sweet_xml, "~> 0.6"},
    ]
  end
```

- Download all deps, use `mix deps.get`

- Prepare your AWS S3. :) Please read more in their documentation. https://aws.amazon.com/s3/

- Prepare your account, we will use `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY`. Please visit here for more information about `ExAWS`: https://github.com/ex-aws/ex_aws

## Let's Code

My code structure will be like this:

```
- root
-- elixir_exploration
-- elixir_exploration_web
--- resolvers # store all GraphQL resolvers
--- schema # store all GraphQL schemas
-- schema.ex # store all GraphQL query/mutations
```

- I will code at GraphQL side first (Absinthe). Please setup the Absinthe first.

You can refer this guide: https://hexdocs.pm/absinthe/plug-phoenix.html

> I really love most of popular library, they bring us a nice guide. :)

- Write the resolver, I will put in `elixir_exploration_web/resolvers/uploads.ex`

```elixir
defmodule ElixirExplorationWeb.Resolvers.Uploads do
  alias ExAws.S3

  def upload_to_aws(_, %{input: input}, _) do
    IO.inspect(input.file)
    request = input.file.path
      |> S3.Upload.stream_file()
      # take care the filename, you should validate this
      |> S3.upload("upload-test-berv", "uploads/#{input.file.filename}")
      |> ExAws.request()
    case request do
      {:ok, _} -> {:ok, "Success"}
      {:error, _} -> {:error, "Please try again"}
    end
  end
end
```

Don't forget to update the bucket name. In that example, I use `upload-test-berv` as the bucket name. I use `uploads/#{input.file.filename}` as the target file location.

- Write the schema, I will put in `elixir_exploration_web/schema/upload_types.ex`

```elixir
defmodule ElixirExplorationWeb.Schema.UploadTypes do
  use Absinthe.Schema.Notation

  alias ElixirExplorationWeb.Resolvers
  import_types(Absinthe.Plug.Types)

  object :upload_file do
    @desc """
      Upload a file
    """
    field :upload_file, :string do
      arg(:input, non_null(:file_input))
      resolve(&Resolvers.Uploads.upload_to_aws/3)
    end
  end

  input_object :file_input do
    field :file, non_null(:upload)
  end
end
```

- Import those files into `elixir_exploration_web/schema.ex`

```elixir
defmodule ElixirExplorationWeb.Schema do
  use Absinthe.Schema

  alias ElixirExplorationWeb.Schema

  import_types(Schema.UploadTypes)

  query do
    
  end

  mutation do
    import_fields(:upload_file)
  end
end
```

- Update config, I want to setup the default region. Currently I update to `config/config.exs`, but feel free to add different approach for different env.

```elixir
config :ex_aws,
  region: {:system, "AWS_DEFAULT_REGION"}
```

- Write the `env.sh`, this for setup our environment variable in our system.

```bash
export AWS_ACCESS_KEY_ID=<your_access_key_id>
export AWS_SECRET_ACCESS_KEY=<your_secret_access_key>
export AWS_DEFAULT_REGION=ap-southeast-1
```

Feel free to use another region. :)

- Apply the environment, you can use `source env.sh`.

- Run your Phoenix Server. `mix phx.server`

- Test your API. Because my API locate at `localhost:4000/api/graphql`, so I will use that. If you set another path, please update it from my script below.

```bash
curl -X POST -F query="mutation { uploadFile(input: {file: \"my_data\"})}" -F my_data=@mix.exs -H "Accept:application/json" localhost:4000/api/graphql
```

![Elixir Test](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8wq532bi3g96jo1l1mb6.png)

- Check your S3

![S3](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vu5n4bc3fw7b95g8f7nc.png)

## My Repo

You also can visit my repositories. I will write more in there if I have any updates.

{% github berviantoleo/elixir-exploration %}


## Congrats

Yey! Congrats! You've implemented file upload and upload the file to AWS S3. In the next section, maybe I will write to show the result after you upload the file. The approach I will use:

1. Use database as the listing
2. Call AWS S3 API Directly to get the list

I hope I will write in the next month and write each approach in different article.

Thank you. :)

![Thank you image](https://source.unsplash.com/VyC0YSFRDTU)

<figcaption>The city at night via <a href="https://unsplash.com/photos/VyC0YSFRDTU">unsplash</a></figcaption>