Walkthrough: Upload Images to S3 using Angular and Lambda

Uploading a file to S3 with Angular can be somewhat confusing at first so this article aims to demystify the process. In this article, I will walkthrough how to get the file data from an input field in angular, prepare it for upload using the FileReader API, setup an API endpoint with API Gateway, send the file to a lambda function, and save the file to an S3 bucket using the AWS SDK.

Tools for the Job:

NPM (https://docs.npmjs.com/getting-started/installing-node)
Angular2+ (https://angular.io/)
Angular CLI (https://cli.angular.io/)
AWS account (https://aws.amazon.com/)

Angular App Setup

We are going to use the angular cli to generate a new angular project. To generate a new project, run the following angular cli command from your terminal.
$ ng new angular-s3-upload

This will generate a new project in a folder named ‘angular-s3-upload’. Choose whatever folder name you want. Next, let’s serve the application so we can view our project in the browser.

Change directories into the project folder.
$ cd angular-s3-upload

Run the following command in your terminal.
$ ng serve

Your app should now be served at http://localhost:4200/

Form Setup

For the purpose of this article, we are going to make our changes in the root app component.

Let’s add a simple form with a single file input element. Open up src/app/app.component.html and add the form.

A very basic form w/ a file input element

We do a few important things here. 
1) Create a form with a submit action to handle the event. 
2) Add the template variable #fileInput so that we can easily reference the file input in our app component.
3) Add a change event to the file input element to handle file validation and preview functionality.

Getting the file information

Next, let’s update our component to handle the submission of the form. Open up src/app/app.component.ts and update it to reflect something similar to the following:

In this file we validate and update the image preview every time the file input changes.

A few things that stand out:

Using the@ViewChild() decorator we can access the file input as the typeElementRefusing the template variable fileInput .

With the element referenced we access the files array through the native element. Hence this.fileInput.nativeElement.files[0] . Because we are only using the one file input, we only need to access the first element of the files array.

We are using the FileReader web api to obtain the file information. Reading the file as a data url makes it easy to create the image preview and send the base64 string as part of an application/json request, the default Content-Type in Angular.


Setting Up the Lambda Using Nodejs

The following section requires an AWS account. Lambda is the serverless solution in AWS. For more information see https://aws.amazon.com/lambda/.

First, create a directory to develop our lambda deployment package in. The location and the name of this directory is not important. Name it whatever you want, I chose upload-file-to-s3/

With lambda we need to define a function and a handler. The handler is the method that gets called every time the lambda is invoked. Everything declared outside of the handler will persist for the life of the lambda. To begin, let’s create a file called lambda.js in our lambda deployment package directory. The file should reflect something similar to the following:

lambda.js

As you can see we set up most of the configuration outside of our handler. This keeps our function fast as we only need to run the configuration the first time the lambda is executed, or in other words, on cold starts.

We use a dependency library called file-type (https://www.npmjs.com/package/file-type) which helps validate the file.
We can install the library by running: npm install --save file-type inside of our lambda deployment directory, upload-file-to-s3 .

Using lambdas allows us to abstract application logic into single purpose functions. This makes it easy to reason with.

  1. We first configure S3 with the aws-sdk (aws-sdk is included in all lambdas).
  2. We load the base64 file we sent to the lambda from our angular app into a Buffer.
  3. We use the Buffer to validate the length of the file and the file type.
  4. We then use the S3 putObject method to upload the file to our bucket.

Note: the process.env variables are passed in using lambda environment variables. We will cover that in the next section.

Upload the Lambda Code

Login to your AWS account and navigate to the lambda service. Click on the “Create New Function” button.

On the next screen, click the “Author From Scratch” button.

When asked to add a trigger, click “Next”. We are not going to configure any triggers at this time.

Now it’s time to configure the lambda. Fill in the basic information with whatever you want. Just make sure the runtime is Node.js 6.10.

Now it’s time to upload our lambda code. To deploy our code we need to make a lambda deployment package. This is our lambda files and dependencies zipped up. It’s important to note we are not zipping up the whole folder, we only zip the required files. In this case, we need to zip lambda.js and the node_modules folder.

Under the “Lambda function code” section select “Upload a .Zip” file from the “Code entry type” dropdown.

At this point click “Upload” and select your zipped lambda package.

Before we move forward, we need an access and secret key to configure the AWS SDK. Let’s get those now from a programmatic user in IAM.

Create a Programmatic User in IAM

Open a new window and visit the IAM service in AWS and select users. We are going to create an S3 programmatic user.

Click “Add User” and enter a name such as s3programmatic. Under the “Access type” check the Programmatic access option.

On the next screen, we need to setup user permissions. AWS Permissions is a wide topic and permission settings can be as broad or granular as needed. For the sake of this tutorial, we will attach one policy to our s3Programmatic user, AmazonS3FullAccess.

Select “Attach existing policies directly” and check the “AmazonS3FullAccess” policy. Click “Next: Review”.

After reviewing the data, click next and create the user. You’ll want to click on “Download .csv” to save the credentials to your local machine. This is the last time you will see the secret key in the GUI. Let’s go back to our lambda and add the access and secret keys as environment variables.

Under the Lambda function code section, add the following environment variables.

ACCESS_KEY_ID: (Access key from s3programmatic user)
SECRET_ACCESS_KEY: (Secret key from s3programmatic user)
REGION: (the region for your bucket i.e. us-west-2. Pick the same region your lambda is in)
BUCKET: (Whatever bucket you intend to upload the file to. ie. lambda-s3-upload-example.)

Remember buckets are global and must be unique. If you do not have a bucket, go to S3 and create one.

Next, at the “Lambda function handler and role” section we need to change the handler field. Where it says index.handler we need to change it to lambda.handler . Our file is called lambda.js (not index.js) and our function is called handler.

Under role, select “Create new role from template(s)” and give the role a name. Something like lambda-execution works. Leave “Policy templates” blank.

Click next to Review. If all looks good, click “Create Function”.

Our lambda is ready.

API Gateway

API Gateway is a great service. It’s a fully managed API that is highly configurable. We are going to create a new API and setup an endpoint for uploading our file. To get started, visit the API Gateway service in your AWS dashboard and click on “Create API”

When prompted for information regarding our new API select the “New API” option and enter a name and description.

Click “Create API”.

You should see the API resources and its methods, of which there are none at this time.

At this time we have one resource, the root resource /. Resources are just paths we can access through the API. We could add a new method to the root path, but it makes more sense to add a new resource for handling our file upload endpoint.

Click on the “Actions” dropdown button and select “Create Resource”

Fill out the “Resource Name” field (i.e. upload-photo) and check the “Enable API Gateway CORS” checkbox. Enabling CORS will create an OPTIONS method to handle pre-flight request.

Click “Create Resource”

Next, we need to map this resource to our lambda function. We are going to be posting data so let’s create a new POST method. Make sure the resource we just created is selected. You should see an options method already created.

With the upload-photo resources selected, click on the same Actions dropdown button as before and select “Create Method”. In the dropdown for the method type select POST.

In our POST method setup choose Lambda Function as the Integration Type. Check the Use Lambda Proxy integration. Under region select the region your lambda exists in and enter the lambda name in the Lambda Function Field.

Click save. You will be prompted to “Add Permission to Lambda Function”. Select “OK”. This is granting permission for the API Gateway to invoke the lambda.

Our POST method is ready. At this point we are ready to deploy the API. From the same Actions dropdown button select Deploy API.

We will be prompted to select a Deployment Stage. Since we do not have one at this time, select [New Stage] from the “Deployment Stage” select options. I am going to enter Prod but feel free to enter anything you want. i.e. staging, dev, etc.

Click “Deploy”. We now have an API URL and a method to call our lambda. Let’s connect our angular app to our API.


Updating Our Angular App

Armed with our newly created API endpoint, it’s time to update the front end to send the image to lambda. We are going to add the API URL to our environment within angular. This will allow for easy separation between dev and production builds.

Open up src/app/app.module.ts and import the HttpClientModule .

Next, open up src/environments/environment.ts and add the key apiUrl with the value of your API URL.

Setup angular env to include the api url

Finally, open up the app.component.ts file and add the http post functionality.

Our updated component includes a few changes.

First, we import HttpClient and environment. HttpClient exposes the post method we use to make the request and we are using the environment to grab the apiUrl we added earlier.

import {HttpClient} from '@angular/common/http';
import {environment} from '../environments/environment';

Next, we inject the HttpClient into our component.

constructor(private http: HttpClient) {}

Finally, we use the http client to make the request and upload the file. Our data consists of an object containing the key “image” with the value of the base64 encoded image.

const data = {'image': base64File};
this.http.post(`${environment.apiUrl}/upload-photo`, data)
.subscribe(
res => {
// handle success
//reset file input
this.fileInput.nativeElement.value = '';
},
err => {
this.errorMsg = 'Could not upload image.';
}
);

At this point you should be able to select a file and upload it to S3!

Conclusion

You just built a highly available, (almost) infinitely scalable, serverless model using AWS and angular to handle uploading images. In a real world scenario, you would probably move the upload function to it’s own component. You might even have a service that can handle the http request. How you configure your app is up to you and depends on the project. At this time, you should have a pretty good understanding of how to get file information using angular, creating and deploying a lambda function, making programmatic users in IAM, creating an API using API Gateway, and orchestrating the services to work together.

Like what you read? Give Mike McCall a round of applause.

From a quick cheer to a standing ovation, clap to show how much you enjoyed this story.