Uploading files to S3 from the browser using AWS API Gateway and Lambda

Michael Osinowo
Jul 20 · 4 min read

I have been trying to upload to S3 and it has stressed me a bit but I finally figured out how to get it working properly. The two major issues I faced was setting up the API Gateway properly to avoid CORS error and knowing how to handle files on the lambda and the front-end app. For this article I want to assume that you know how to create a Lambda function on AWS.

Setup for the Lambda

For our Lambda we will need some dependencies for file handling, so I used VS code to write the code for my lambda and installed all the dependencies, you can use any IDE for this. Create a folder for your Lambda.

npm init -y

Open your cmd and navigate to your Lambda folder and run the command above.

{
"name": "lambda",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"aws-sdk": "^2.493.0",
"file-type": "^12.0.1",
"mime-type": "^3.0.7",
"mime-types": "^2.1.24",
"moment": "^2.24.0",
"sha1": "^1.1.1",
"unixtime": "0.0.2"
}
}

Copy the code above into your package.json file for your Lambda and install the node modules for the dependencies by running.

npm i --save

create an index.js file and in there copy the code below.

const AWS = require('aws-sdk')
const s3 = new AWS.S3()
const moment = require('moment')
const fileType = require('file-type')
const sha1 = require('sha1')
const unixTime = require('unixtime')
const mime = require('mime-types')
let getFile = function(fileMime, fileExt, buffer){
let hash = sha1(new Buffer(new Date().toString()))
let now = moment().format('YYYY=MM-DD HH:mm:ss')
let filePath = hash + '/'
let fileName = unixTime(now)
let filefullName = filePath + fileName
let filefullPath = 'Your file path' + filefullName
let params = {
Bucket: 'Your bucket name',
Key: `${filefullName}.${fileExt}`,
Body: buffer
}
console.log(params.Key);let uploadFile ={
size: buffer.toString('ascii').length,
type: fileMime,
name: fileName,
full_path: filefullPath
}
return {
'params': params,
'uploadFile': uploadFile
}
}
exports.handler = function(event, context, callback){
console.log(event.body)
let request = event.body
let base64String = request
let buffer = new Buffer(base64String, 'base64')
let extension = mime.extension(event.headers['content-type'])
console.log(extension)

let response = {
statusCode: 200,
headers: {
'Access-Control-Allow-Headers': 'content-type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token',
'Access-Control-Allow-Methods': 'POST, GET, DELETE',
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Credentials': true,
},
body: JSON.stringify('Hello from new Lambda!'),
}
let file = getFile(event.headers["content-type"], extension, buffer);
let params = file.params
s3.putObject(params, function(err, data){
if(err){
return console.log(err)
}
return callback(null, response);
// return response
})

}

After all that is done zip the folder for you Lambda. Go back to your newly created Lambda function on aws and upload your zip folder to the Lambda.

before upload
After upload

Setting up your API Gateway on AWS

We need to create an API to handle our post request to S3.

  • Login to your AWS console
  • Under services navigate to API Gateway
  • Select Create API
  • Give it a name and click Create API
  • After creating the API click Actions and select create resource
  • Tick the Enable API Gateway CORS option and save
  • Click Actions again and select create method
  • Select POST

After you click save, this is the important bit where we have to set the correct response headers for the method response to avoid CORS error on the browser and remember to change the region of your API. Click on the method response , select 200. Under the Response Headers for 200 add the following response headers.

X-Requested-WithAccess-Control-Allow-HeadersAccess-Control-Allow-OriginAccess-Control-Allow-Methods

Then for the Response body for 200 add .

application/json

If after creating your API you don’t have an OPTIONS method under your resource path. Add the OPTIONS method same way the POST method was added. The OPTIONS method is important because when a request is made from the browser the OPTIONS method serves as a mock service to test the API before it goes ahead to originally requested method POST.

Post to the API from the front-end app

I am using React.js for this but it can work on any front-end application. The following piece of code lets you get files from the browser then converts the file to base64String so it can be accepted by the the API Gateway. To begin we need axios for POST request to the API so install axios

npm i axios

We are going to create an upload file function

uploadFile =  event => {let file = this.state.file[0]var reader = new FileReader();reader.readAsBinaryString(file)console.log(file.type)reader.onload = async () => {let file_string = window.btoa(reader.result)const url = 'your API link'console.log(file_string)axios.post(url ,file_string, {headers: {'Accept': `${file.type}`,'Content-Type': `${file.type}`,'Access-Control-Allow-Origin': "*"}}).then( res => {console.log(res)}).catch(err => {console.log(err)})}reader.onerror = error => console.log('Error :', error)}

In react this function should be added to your onClick() listener for the submit button. The complete react code can be found on my GitHub.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade