S3 compatible storage services are available from a number of cloud providers such as Vultr and Linode. This guide demonstrates how the AWS SDK for JavaScript and the Multer storage engine can be used to create a Node.js application to upload files to S3-compatible storage service buckets. The application is comprised of a Node.js server component and a web-based client component.
To complete this guide, you will need to have:
npm
.
The operating system commands in this guide are for Ubuntu. However, since the programming steps are identical between Ubuntu and Windows, Windows users can also complete this guide by substituting the analogous Windows operating system commands for the listed Ubuntu commands.
Create a new folder for the new Node.js project and change it to the directory once created. This guide will use the folder name s3-object-storage-nodejs
.
sudo mkdir s3-object-storage-nodejs
sudo cd s3-object-storage-nodejs
package.json
File in the New Project FolderThe package.json
file contains basic data about the new Node.js project, such as the project name, as well as a list of project dependencies.
package.json
Questionnaire ScriptEnsure you are in the new project directory created in the previous step. Run the npm init
command to start a questionnaire script that will guide you through creating a new package.json
file.
sudo npm init
package.json
Project ValuesWhen the questionnaire script prompts you to enter:
Enter
to select the default value, which is the folder name s3-object-storage-nodejs
set in Step 1.Enter
to select the default value 1.0.0
.Enter
to leave the description value empty.server.js
, and then Enter
.Enter
to leave the test command value empty.Enter
to leave the git repository value empty.Enter
to leave the value of the keyword empty.S3 User
) and then Enter
.Enter
to select the default value ISC
.
The questionnaire script will output a summary of the chosen package.json
values.
{
"name": "s3-object-storage-nodejs",
"version": "1.0.0",
"description": "",
"main": "server.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "S3 User",
"license": "ISC"
}
When prompted Is this OK? type yes
and then Enter
to save the new package.json
file.
packakge.json
Use a text editor to add "start": "node server.js",
above the test
key in package.json
. The updated package.json
file will have the following structure:
{
"name": "s3-object-storage-nodejs",
"version": "1.0.0",
"description": "",
"main": "server.js",
"scripts": {
"start": "node server.js",
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "S3 User",
"license": "ISC"
}
Navigate to the main project folder s3-object-storage-nodejs
.
Express.js provides the framework for the server component of the application.
npm install express
The AWS SDK for Javascript provides modular access to AWS resources, including S3-compatible storage services.
npm install @aws-sdk/client-s3
Multer middleware for Node.js is used for handling multipart/form-data
, which is typically used for uploading files.
npm install multer
Multer S3 provides streaming-based S3 client integration with the Multer storage engine.
npm install multer-s3
.aws
Folder and Empty credentials
FileCreate a new folder named .aws
with an empty text file credentials
in your user directory, not the project folder created in Step 1.
The path to the empty credentials file will be located at ~\.aws\credentials
on Ubuntu and C:\Users\USERNAME\.aws\credentials
on Windows. The AWS SDK for Javascript follows a default credentials provider chain and will look for your S3-compatible object storage credentials in either of the preceding directories depending on your operating system.
sudo mkdir .aws
sudo touch .aws\credentials
credentials
File to Set S3 Object Storage CredentialsOpen the empty credentials
file with a text editor and enter the profile name [default]
followed by your S3-compatible object storage credentials.
[default]
aws_access_key_id=YOUR_S3_OBJECT_STORAGE_ACCESS_KEY_GOES_HERE
aws_secret_access_key=YOUR_S3_OBJECT_STORAGE_SECRET_KEY_GOES_HERE
Do not enclose the access key or the secret key with quotes or any other character. Also, the credentials
file should be saved without a file extension.
In this step, you will create the server-side component of the application in your desired development environment (e.g. Visual Studio Code).
server.js
File and Specify Required Project ModulesCreate a new Javascript file in the project folder set in Step 1 named server.js
. Add the following require
statements at the beginning of the file:
const { S3, ListBucketsCommand } = require("@aws-sdk/client-s3");
const multer = require("multer");
const multerS3 = require("multer-s3");
const express = require("express");
Initialization of the AWS SDK S3 class requires that you specify the endpoint
and the region
of the S3 service. Add the following code to the server.js
file using the specified endpoint
and region
values for your S3-compatible object storage service. For example, the endpoint
and region
values used with Vultr Object Storage are https://ewr1.vultrobjects.com
and ewr1
respectively.
// Enter the appropriate endpoint and region values for your service provider.
const s3 = new S3({
endpoint: "YOUR_S3_SERVICE_ENDPOINT_URL_GOES_HERE",
region: "YOUR_S3_SERVICE_REGION_GOES_HERE"
});
upload
MethodAdd the upload
method which will call the Multer storage engine using client-side file upload form data.
const upload = multer({
storage: multerS3({
s3: s3,
bucket: (request, file, cb) => {
console.log(request.body.selectBucket);
cb(null, request.body.selectBucket);
},
key: (request, file, cb) => {
console.log(file);
cb(null, file.originalname);
}
})
});
Use the express()
method to create a new Express application instance.
const app = express();
When the client-side (i.e. browser-side) of the application is created in Step 6, those static files will be placed in a public
sub-folder within the project folder. The express.static
middleware function is used to set the directory from which to serve these static assets.
app.use(express.static("public"));
The routes of the Express application need to be defined. In the following code, the first route sets the root of the application and serves the client-side index.html
file, which will display a file upload form. The second route retrieves a list of your S3 compatible storage service buckets and returns that data to the client for use in the file upload form. The third route handles POST
requests from the browser, passing file upload form data to the upload
method defined earlier, and sending a response back to the client.
app.get("/", (request, response) => {
response.sendFile(__dirname + "/public/index.html");
});
app.get("/buckets", (request, response) => {
let listBuckets = async () => {
try{
const data = await s3.send(new ListBucketsCommand({}));
console.log("Success fetching buckets:", data.Buckets);
response.send(data);
}
catch(e){
console.log("There was an error when trying to fetch object store buckets: ", e);
}
};
listBuckets();
});
app.post("/upload", upload.array("uploadFilesInput"), (request, response) => {
console.log("File(s) uploaded successfully.");
response.send(request.files.length + " file(s) successfully uploaded.");
});
The last part of the server-side application sets the Express listen
method so that server.js
can listen for client connections on port 8080.
app.listen(8080, () => {
console.log("The server is listening on port 8080.");
});
server.js
FileThis is the completed server.js
file after following steps 5a through 5e above.
const { S3, ListBucketsCommand } = require("@aws-sdk/client-s3");
const multer = require("multer");
const multerS3 = require("multer-s3");
const express = require("express");
const s3 = new S3({
endpoint: "YOUR_S3_SERVICE_ENDPOINT_URL_GOES_HERE",
region: "YOUR_S3_SERVICE_REGION_GOES_HERE"
});
const upload = multer({
storage: multerS3({
s3: s3,
bucket: (request, file, cb) => {
console.log(request.body.selectBucket);
cb(null, request.body.selectBucket);
},
key: (request, file, cb) => {
console.log(file);
cb(null, file.originalname);
}
})
});
const app = express();
app.use(express.static("public"));
app.get("/", (request, response) => {
response.sendFile(__dirname + "/public/index.html");
});
app.get("/buckets", (request, response) => {
let listBuckets = async () => {
try{
const data = await s3.send(new ListBucketsCommand({}));
console.log("Success fetching buckets: ", data.Buckets);
response.send(data);
}
catch(e){
console.log("There was an error when trying to fetch object store buckets: ", e);
}
};
listBuckets();
});
app.post("/upload", upload.array("uploadFilesInput"), (request, response) => {
console.log("File(s) uploaded successfully.");
response.send(request.files.length + " file(s) successfully uploaded.");
});
app.listen(8080, () => {
console.log("Server listening on port 8080.");
});
server.js
Navigate to the main project folder s3-object-storage-nodejs
. Start server.js
using npm start
.
sudo npm start
The application will output its listen
method message to the console.
Server is listening on port 8080.
Use CTRL-C
to terminate the server.js
application.
In this step, you will create the client-side of the application which consists of an HTML form for uploading files, a CSS file for styling, and a client-side Javascript file to programmatically manipulate the HTML form based on server responses and user selections.
public
Sub-FolderNavigate to the main project folder s3-object-storage-nodejs
and create a public
sub-folder.
mkdir public
As mentioned in Step 5d, the client-side application files will be stored in and served from the public
sub-folder.
index.html
HTML FileCreate an empty HTML file within the public
sub-folder and name it index.html
. Copy and paste the following HTML code into the file.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>S3 Object Storage Upload with Node.js</title>
<link rel="stylesheet" href="./styles.css">
</head>
<body>
<h2>S3 Object Storage Upload with Node.js</h2>
<form id="uploadForm" method="post" enctype="multipart/form-data">
<div class="form-hint">Please select a bucket using the dropdown menu.</div>
<select name="selectBucket" id="selectBucket">
<option>Select a Bucket</option>
</select>
<div class="form-hint">Please select one or more files to upload.</div>
<label class="upload">
<input type="file" multiple name="uploadFilesInput" id="uploadFilesInput"/>
<span>Click Here to Select Files</span>
</label>
<div id="filesListCount" class="form-hint" for="filesList"></div>
<ul id="filesList"></ul>
<label id="submitButton" class="submit">
<input type="submit" value="submit">
<span>Submit</span>
</label>
</form>
<div id="uploadMessage" class="message"></div>
<label id="resetForm" class="reset">
<span>Click Here to Reset the Form</span>
</label>
<script src='client.js'></script>
</body>
</html>
The HTML code above displays an HTML form for uploading files. The <select id="selectBucket">
and <ul id="filesList">
elements are dynamically populated to display available S3 storage service buckets and the files chosen for upload respectively.
The <label id="resetForm">
element is initially hidden and then displayed upon a successful upload response from the server. A client-side Javascript file, client.js
, will be created to manage the dynamic components of the HTML form.
client.js
Javascript FileCreate an empty Javascript file within the public
sub-folder and name it client.js
. Copy and paste the following Javascript code into the file.
(() => {
"use strict";
const formElem = document.getElementById("uploadForm");
const selectBucketsElem = document.getElementById("selectBucket");
const filesListElem = document.getElementById("filesList");
const filesListLabelElem = document.getElementById("filesListCount");
const messageElem = document.getElementById("uploadMessage");
const resetLabelElem = document.getElementById("resetForm");
document.getElementById("uploadFilesInput").addEventListener("change", (e) => {filesToUploadHandler(e)});
document.getElementById("submitButton").addEventListener("click", (e) => {submitFormHandler(e)});
document.getElementById("resetForm").addEventListener("click", (e) => {resetFormHandler(e)});
let filesToUploadHandler = (e) => {
let numOfFiles = e.target.files.length;
filesListLabelElem.innerText = "You have selected " + numOfFiles + " file(s) for uploading.";
for(let fileKey in e.target.files){
if((undefined !== e.target.files[fileKey].name) && (Number.isInteger(parseInt(fileKey)))){
filesListElem.innerHTML += "<li>" + e.target.files[fileKey].name + "</li>";
}
};
}
let submitFormHandler = (e) => {
e.preventDefault();
messageElem.innerText = "Uploading...";
fetch("http://localhost:8080/upload", {
method: "POST",
body: new FormData(uploadForm)
})
.then(response => response.text())
.then(text => {
messageElem.innerText = text;
resetLabelElem.style.display = "block";
})
.catch(e => {
messageElem.innerText = "An error occurred during upload; check your network connection.";
resetLabelElem.style.display = "block";
})
}
let resetFormHandler = (e) => {
resetLabelElem.style.display = "none";
filesListLabelElem.innerText = "";
filesListElem.innerHTML = "";
messageElem.innerText = "";
formElem.reset();
}
let fetchBuckets = () => {
fetch("http://localhost:8080/buckets", {
method: "GET"
})
.then(response => response.json())
.then(json => {
let buckets = json.Buckets;
buckets.forEach(bucket => {
selectBucketsElem.innerHTML += "<option value='" + bucket["Name"] + "'>" + bucket["Name"] + "</option>";
})
})
.catch((e) => console.log("There was an error when trying to fetch buckets: ", e));
}
fetchBuckets();
})()
The fetchBuckets
method is executed immediately to fetch your available S3 storage service buckets and dynamically populates the <select id="selectBucket">
element of the HTML form with an <option>
for each bucket. The filesToUpload
method, and its associated event handler, are triggered when files are selected for uploading.
Each filename is dynamically added to the <ul id="filesList">
element of the HTML form with a <li>
list element corresponding to each file. The submitFormHandler
method is triggered when the Submit button is clicked in the HTML form and submits the upload form data to the server via a POST request. The submitFormHandler
method also receives the server response and outputs the response to the client browser.
styles.css
CSS FileCreate an empty CSS file within the public
sub-folder and name it styles.css
. Copy and paste the following CSS styles into the file.
:root{
--dark-grey: #333333;
--fancy-blue: #0066FF;
--fancy-blue-light: #4D94FF;
--white: #FFFFFF;
--aqua-light: #05BCB6;
--aqua: #04A29D;
}
html{
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
font-size: 14px;
box-sizing: border-box;
}
*, *:before, *:after {
box-sizing: inherit;
}
body {
margin: 1rem;
}
.form-hint{
margin: 1rem 0 0.5rem 0;
}
select[name="selectBucket"], label{
display: block;
width: 25rem;
height: 3rem;
border-radius: 0.25rem;
padding: 0.5rem 0;
cursor: pointer;
}
label{
line-height: 1.75;
text-align: center;
transition: all ease 0.25s;
}
input[name="uploadFilesInput"]{
display: none;
}
.upload{
background-color: var(--white);
border: 1px solid var(--dark-grey);
color: var(--dark-grey);
}
.upload:hover{
background-color: var(--dark-grey);
color: var(--white);
font-weight: bolder;
}
input[type="submit"]{
display: none;
}
.submit{
margin-top: 1rem;
background-color: var(--fancy-blue-light);
border: 1px solid var(--fancy-blue-light);
color: var(--white);
}
.submit:hover{
background-color: var(--fancy-blue);
border: 1px solid var(--fancy-blue);
font-weight: bolder;
}
.message{
margin-top: 1rem;
color: var(--dark-grey);
}
.reset{
display: none;
margin-top: 1rem;
background-color: var(--aqua-light);
border: 1px solid var(--aqua-light);
color: var(--white);
}
.reset:hover{
background-color: var(--aqua);
border: 1px solid var(--aqua);
font-weight: bolder;
}
These CSS styles generally modify the default browser styling for HTML forms.
s3-object-storage-nodejs
. Start server.js
on the server using sudo npm start
.Server is listening on port 8080.
using the server console.http://localhost:8080
. The HTML file upload form will be displayed.Uploading...
will be displayed below the Submit button while files are uploaded. When a response is received from the server, it will replace the upload status message, e.g. 2 file(s) were successfully uploaded.
.
You created a Node.js server and web client application to programmatically upload files to an S3-compatible storage service. However, this guide only scratches the surface of what is possible with programmatic control over S3-compatible storage services. The server and client code examples above could be easily extended to provide additional functionality such as deleting and creating buckets. Please refer to the following resources for more information.