Deploy a robust image handling/resizing solution with AWS Serverless Image Handler
Updated February 2020 to work with latest AWS template
Most applications have to deal with user uploaded images today but image handling and resizing on the back-end is often neglected or not implemented. User experience is impacted negatively as images take longer to load on the front-end. Luckily this has an easy solution!
A great image handler solution should work for any front-end, including the ability to resize images for any screen size. Images should also load fast thanks to a CDN. Let’s make it Serverless so we offload all the work outside of our main server. Coding work should be minimal or zero (so we can get back quickly to building our product 😉), and deployment quick and inexpensive.
AWS provide a great CloudFormation template called the AWS Serverless Image Handler that reduces the coding to zero and deploys :
- An AWS lambda function on top of the S3 bucket where your pictures are stored to take care of the resizing in a Serverless fashion
- An API Gateway that interpret the URL and resizing options
- A CDN (CloudFront)
You will just need to create a S3 bucket and deploy the CloudFormation template, then your app will be able to request any image and size from your S3 bucket by specifying its path and a resolution for it in the query URL. If an image for this resolution had previously been generated, then the CDN will return it from the cache, otherwise it will be resized on the fly by the Lambda function. This avoids storing multiple versions of the same image.
The image handler we will deploy supports heaps of other features as cropping (and smart cropping around a user's face for example) or adding a watermark to the picture. The latest template supports AWS Rekognition for detecting faces and smart croping. You can check them all as well as the expected URL format here.
It is worth noting that since the latest template version, the documentation hasn't been updated much and the way of requesting a picture has changed. It also introduces a bug when requesting images that are not at the bucket root (in folders our subfolders). We will go through these pitfalls.
Step 1 : Creating the S3 bucket
First, you will need to sign in or sign up for an AWS account.
If you are not using a S3 bucket for storing your pictures yet, let’s create one. Go to the S3 console. Then Click on “Create Bucket”, in “Bucket Name” specify a name of your choosing then select a region from the dropdown. Remember these two choices as we will need them later. Click “next”, then for options you can leave it as is and click “next” again.
On the permissions page, unselect “Block public access” (users will need access to these pictures). Then “next”, and “Create Bucket”.
We will need a bit of config to make the bucket public for read access, so that anyone can request and view a picture from within your app. Click on the newly created bucket in the list and go to the "Permissions" tab. Then select "Bucket Policy" you should see an empty editor like so :
Copy and paste the following code then replace the resource name with your own bucket arn followed by /*
. You can find your bucket's arn just above the editor. Then hit "Save".
{
"Version": "2012-10-17",
"Id": "Policy1530181895103",
"Statement": [
{
"Sid": "Stmt1530181890610",
"Effect": "Allow",
"Principal": "*",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::serverless-image-handler-example/*"
}
]
}
Finally let's take care of potential CORS issues. Click on "CORS configuration" on the right hand side of "Bucket Policy".
Copy and paste the following code and hit "Save".
<?xml version="1.0" encoding="UTF-8"?>
<CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
<CORSRule>
<AllowedOrigin>*</AllowedOrigin>
<AllowedMethod>GET</AllowedMethod>
<MaxAgeSeconds>3000</MaxAgeSeconds>
<AllowedHeader>Authorization</AllowedHeader>
</CORSRule>
</CORSConfiguration>
Step 2 : Deploy the solution
Click on this link to deploy the solution. On the newly opened page, make sure to have the right region (ideally the same one than the S3 bucket's) selected in the upper-right corner and click "Next".
Choose a stack name or leave it as is. Then it's time to copy your bucket name in the "SourceBuckets" field. Choose "No" for "Deploy UI". If you mistype the bucket name this will not work.
Hit "Next", then "Next" again. The template will start deploying its magic. While you’re waiting, here are a few blog articles you might find interesting 😏
Once it is deployed, let's test the solution! On the CloudFormation panel, click on your stack name on the left and go to the "Outputs" tab to get the url for the CloudFront on the "ApiEndpoint" line. If you already have images in the root of your bucket then you can try right away to request a resized version of an image by simply manipulating the URL. For example with an image “test.jpg” you can try https://your-cloudfront-url/fit-in/300x400/test.jpg . Check out the full doc on how to format your url there. If you want to explore the API more and have it work with images inside folders carry on to the next step.
Step 3 : Advanced features and pitfalls of last template
Requesting an image by appending filters to the URL the way it is described in the doc won't work anymore if your image is not in the root of the S3 bucket, which limits its usage.
To make it work again you will need to encode your URL a different way, using a JSON object listing the edits/filters, then encoding it into an URL.
This way you can also make use of Amazon Rekognition out of the box to crop/center the picture around someone's face.
You can also blur a picture, rotate, grayscale and other sharp.js features as AWS Serverless Image Handler implements this library.
Here is an example JSON object :
{
"bucket":"s3-bucket-name",
"key":"photo.jpg",
"edits": {
"normalize":true,
"grayscale":true,
"sharpen":true,
"blur":5,
"rotate":180,
"resize": { "width":300, "height":300, "fit":"cover" }
}
}
And an example implementation in JavaScript (uncomment the smartCrop line to use Rekognition!) :
Full source code here.
You now have a full image handling solution on the back-end. Start tweaking code on your front-end app to request pictures for only the sizes you need. Your users will thank you 🚀🍾💪
Thanks for reading!