In part 1 we created a simple photo gallery application which provided a good introduction to Arc. Today we’ll expand on our application by adding the ability to upload multiple files at a time and swap out local storage for AWS S3.
If you’d rather grab the completed source code directly rather than follow along, it’s available on GitHub, otherwise let’s get started!
Getting started
If you’ve been following along you can continue with the code from part 1, or you can grab the part 1 code from GitHub.
Clone the Repo
If cloning:
Terminal
Let’s create a branch for today’s work:
Terminal
And then let’s get our dependencies and database set-up.
Terminal
Terminal
Now we’ll run ecto.setup
to create our database. Note: before running ecto.setup
you may need to update the username / password database settings in dev.config
to match your local postgres settings, i.e.
/config/dev.config …line 69
Terminal
With all that out of the way… let’s see where we are starting from.
Terminal
If we navigate to http://localhost:4000/, sign in and navigate to our photos page we’ll see our basic application.
Nothing fancy, but it gets the job done. The first thing we’ll tackle today is adding the ability to upload multiple files at a time.
Uploading multiple files at a time
One minor annoyance with our current implementation is that users need to upload their files one photo at a time. What if someone has 20 or 30 photos to upload… it’s going to get very tedious if they are required to upload these files one by one.
So let’s implement multiple uploads.
The first change we need to make is to update form.html.eex
to accept multiple files. This is very simple, we just need to add multiple: :true
to the file_input
control.
/lib/photo_gallery_web/templates/photo/form.html.eex
We’ve also updated the form_for
tag to include an as
attribute. This is due to the fact that our multiple files aren’t backed by an actual model field.
Next we need to update our controller code. In the controller we’ll simply call into a new context method create_photos
that we will be creating shortly. We’ll also update the success message.
/lib/photo_gallery_web/controllers/photo_controller.ex …line 19
So now for that context method. This will be simple; we will just iterate over the photos and call out to the existing create_photo
method passing in each photo.
/lib/photo_gallery/gallery/gallery.ex …line 16
Sweet! We can now select and upload multiple images.
Handling large file sizes
Let’s see what happens if we select a bunch of fairly large files.
We get an error! So we want to do a few things when this scenario presents itself. We’ll want to provide a decent message to the user, and we also want to increase the maximum request size (the default is 8MB).
Improving the error message
Let’s start with the error message. To view what our end users are going to see, we need to switch debug_errors
to false
in dev.config
.
/config/dev.config …line 9
With any configuration change we need to restart the server.
Terminal
If we now attempt our file upload again, we’ll see the following.
Not exactly a friendly error! We won’t spend time creating a pretty error page, but let’s at least improve on things a bit.
We can customize the error shown for a particular status code by creating a template in /templates/error
with a name of the status we want to customize.
So in our case we’ll create a 413.html.eex
file.
Terminal
We’re just going to create a very simple page.
/lib/photo_gallery_web/templates/error/413.html.eex
And with that, we end up with a bit more user friendly situation.
We should revert our dev.config
and restart the server now that we’ve got our error message figured.
/config/dev.config …line 9
Increasing the maximum request size
To facilitate large files and multiple uploads we’re also going to increase the maximum request size. This is done in endpoint.ex
.
/lib/photo_gallery_web/endpoint.ex …line 34
We’ve added length: 200_000_000
, this will increase our request size to 200 MB. With this change we can upload our previously too large photos all in one go… fantastic!
That does it for implementing multiple file uploads, next let’s swap out our local storage for AWS S3
.
Storing our files on AWS S3
Often in production we’re going to want to use something like AWS S3
for file storage. It is pretty easy to implement this using Arc
.
This section assumes you have already set up a bucket on S3
and a CloudFront
distribution. If you haven’t yet done so, check out this post for instructions on doing so.
You might also want to delete any images you have currently uploaded as these will not be valid after we switch over to S3
.
Adding dependencies and updating the Arc configuration
The first step to getting things setup is to add a few new dependencies in mix.exs
.
/lib/mix.exs …line 34
We’ve added 5 new dependencies starting with ex_aws
, let’s update our dependencies.
Terminal
We now need to update our Arc
configuration.
/config/config.exs …line 39
Note you may need to change the region
value above depending on the region of your bucket. See this document for the various region codes.
We’re using a number of environment variables in the configuration, so we need to set these somewhere.
We’ll accomplish this via an .env
file. Since this file will contain sensitve information we don’t want it checked into source control, it’s very important we add it to our .gitignore file. So let’s do that first.
/.gitignore …line 44
Now let’s create the actual file. When creating configuration files that won’t be checked into source control, I like to also create a template
version of the file. This means when grabbing the source code it should be fairly easy for someone to see what configuration values need to be set. So we’ll start by creating the template file.
Terminal
/.env.template
Now we’ll copy the file to create the actual .env
file.
Terminal
Now replace the values in the .env
file with your actual bucket name, key, secret and cloudfront distribution. This might look something like:
Whenever we make a change or addition to our .env
file we need to run source .env
to load the values, so let’s do that now.
Terminal
Updating the uploader module
One final change to get S3
working is to change our uploader module to use public_read
as the ACL (access control list). This will allow our application to access the images and display them.
/lib/photo_gallery_web/uploaders/photo.ex
With all the above in place when we fire up our application it should work as before… but we’ll see our images are being uploaded to S3
.
Likewise when we delete an image it will be removed from S3
.
Also note that our image URL’s are using our CloudFront distribution.
So that’s it! AWS… done!
Changing up the Arc configuration
One final task before ending for the day. We likely don’t want to be uploading and downloading files from S3
when we’re developing. Therefore I think we should move the Arc
configuration into environment specific configuration files.
So remove the current Arc
configuration from config.exs
and place it in prod.exs
.
/config/prod.exs …line 69
And then in dev.exs
and test.exs
we’ll add an Arc
configuration pointing at local storage.
/config/dev.exs …line 77
/config/test.exs …line 20
Now when we run our application in development or against a test suite our file uploads will be handled locally.
Summary
So this concludes our tour of Arc
. I feel it provides a convenient way of handling file uploads and is pretty easy to setup and integrate into a Phoenix application.
In terms of the application we built; it is sufficient for demonstration purposes but would require some changes for a real world application. A better upload control or at the very least a spinner widget would be desirable as would a better gallery page. Maybe I’ll write up a follow-up post in the future addressing these items… but for now we’re done and dusted!
Thanks for reading and I hope you enjoyed the post!