Client-side S3 file(s) uploading

July 14, 2018

/!\ DRAFT ARTICLE /!\

I haven’t finished redacting this article, but still published it. Please be tender.

We’ll see how to upload a static file on Amazon S3 without consuming server bandwidth.

Can also be useful in a serverless architecture, to not deal with API Gateway’s weird support of binary files.

It works by asking our server for a signed upload URL and then uploading the file(s) directly to S3.

AWS side

Create your bucket on S3 and give it public read right.

Replace the bucket’s CORS configuration by the following:

<?xml version="1.0" encoding="UTF-8"?>
<CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
<CORSRule>
	<AllowedOrigin>*</AllowedOrigin>
	<AllowedMethod>GET</AllowedMethod>
	<AllowedMethod>POST</AllowedMethod>
	<AllowedMethod>PUT</AllowedMethod>
	<MaxAgeSeconds>3000</MaxAgeSeconds>
	<AllowedHeader>*</AllowedHeader>
</CORSRule>
</CORSConfiguration>

Server-side:

const AWS = require('aws-sdk')

let s3 = new AWS.S3()

app.post('/s3uploadurl', (req, res, next) => {

	let s3Params = {
		Bucket: 'my-dope-bucket',
		Key: req.body.name,
		ContentType: req.body.type,
		ACL: 'public-read'
	}

	let uploadURL = s3.getSignedUrl('putObject', s3Params);

	res.send({
		uploadURL: uploadURL
	})
})

Client-side:

In our example we use JQuery, but everything is easily replaceable with pure JS (use fetch for ajax call)

<form action='/whatever'>
	<!-- File selector, once we select files, they'll be automatically uploaded to s3 -->
	<input type="file"/>

	<!-- myFile will contain the URL of the file hosted on S3 -->
	<input type="hidden" name="myFile" value="">
</form>

<script>
let s3baseUrl = 'https://s3.eu-west-3.amazonaws.com/my-dope-bucket/' // CHANGE WITH YOUR OWN BUCKET AND REGION

let inputElement = $("form input[type='file']")[0]
inputElement.onchange = function(event) {
	let file = inputElement.files[0]

	let reader = new FileReader()
	reader.addEventListener('loadend', function(e){
		// $('.spinner').show() // Optional, if you have a spinner, now is a good time to show it

		$.ajax({
			type: 'POST',
			url: "/s3uploadurl",
			data: {
				name: file.name,
				type: file.type
			},
			success: function(result) {
				$.ajax({
					url: result.uploadURL,
					type: 'PUT',
					data: new Blob([reader.result], {type: file.type}),
					processData: false,
					contentType: false,
					success: function(result) {
						// $('.spinner').hide() // Hide the spinner
					
						$('[name="myFile"]').val(s3baseUrl + file.name)
					}
				})
			}
		})
	})

	reader.readAsArrayBuffer(file)
}
</script>