June 4, 2026
Building a Cloud-Native File Upload and Analytics Platform Using AWS (Step-by-Step Guide)
I recently built a cloud-native project where users can upload videos or files through a web page, have them stored securely in Amazon S3…
Harshal Jethwa
9 min read
I recently built a cloud-native project where users can upload videos or files through a web page, have them stored securely in Amazon S3, and track how many times each file is downloaded — all without managing any servers.
The goal was simple: Create a fully automated, serverless system that could handle uploads, store metadata, and show real-time analytics — using AWS as the backbone.
In this post, I'll walk you through exactly how I built it: each service, every configuration, and the little lessons learned along the way.
What We're Building
Here's what the app does:
- Users select and upload a file from their browser.
- The file is sent directly to Amazon S3 via a presigned URL (no backend bottlenecks).
- A PHP backend running on AWS Elastic Beanstalk saves metadata (filename, S3 path, and view count) in DynamoDB.
- Whenever a user clicks "Download," the app increments the view count.
- Everything scales automatically.
Architecture Overview
Each part has a specific job:
Press enter or click to view image in full size
- Frontend: clean UI for uploads and analytics
- API Gateway + Lambda: safely create temporary upload links
- S3: durable storage for files
- Elastic Beanstalk: runs PHP backend for metadata
- DynamoDB: fast, serverless database for file info and counts
Step 1 — Create S3 Buckets
We'll use two buckets (for example):
kc-us-bucket(US region)harshal-raw-bucket-demo(secondary region)
CORS Configuration
[
{
"AllowedHeaders": ["*"],
"AllowedMethods": ["GET", "PUT", "POST"],
"AllowedOrigins": ["*"]
}
][
{
"AllowedHeaders": ["*"],
"AllowedMethods": ["GET", "PUT", "POST"],
"AllowedOrigins": ["*"]
}
]This lets the browser talk directly to S3 through presigned URLs.
Step 2 — DynamoDB Setup
Create a table called FileAnalytics with:
- Partition key:
FileName (String)
Each record will look like:
{
"FileName": "demo.pdf",
"Views": 5,
"S3Path": "https://kc-us-bucket.s3.amazonaws.com/demo.pdf"
}{
"FileName": "demo.pdf",
"Views": 5,
"S3Path": "https://kc-us-bucket.s3.amazonaws.com/demo.pdf"
}Simple, lightweight, and scales to millions of entries.
Step 3 — IAM Role
Elastic Beanstalk's EC2 instance needs permission to talk to DynamoDB and S3.
EC2 Instance Role
Attach:
AmazonS3FullAccessAmazonDynamoDBFullAccess
Elastic Beanstalk Service Role
Attach:
AWSElasticBeanstalkServiceAWSElasticBeanstalkManagedUpdatesCustomerRolePolicy
Name them clearly (e.g., aws-elasticbeanstalk-ec2-role).
Wait a few minutes after creating IAM roles — propagation takes time.
Step 4 — Deploy PHP Backend on Elastic Beanstalk
Install the EB CLI:
pip install awsebcli
eb --versionpip install awsebcli
eb --versionInitialize:
eb init -p "PHP 8.2" file-core-app --region us-east-1eb init -p "PHP 8.2" file-core-app --region us-east-1Create a single-instance environment:
eb create file-core-env --singleeb create file-core-env --singleYou'll get a domain like http://file-core-env.eba-xxxx.us-east-1.elasticbeanstalk.com
Step 5 — Backend Files
add_file.php
Registers new files.
<?php
header("Content-Type: application/json");
header("Access-Control-Allow-Origin: *");
header("Access-Control-Allow-Headers: Content-Type");
// Include AWS SDK
require 'vendor/autoload.php';
use Aws\DynamoDb\DynamoDbClient;
$data = json_decode(file_get_contents("php://input"), true);
if(!$data || empty($data['FileName'])) {
echo json_encode(["status"=>"error","message"=>"FileName is required"]);
exit;
}
// Connect to DynamoDB
$dynamodb = new DynamoDbClient([
'region' => 'us-east-1',
'version' => 'latest'
]);
$table = 'FileAnalytics';
try{
$dynamodb->putItem([
'TableName' => $table,
'Item' => [
'FileName' => ['S' => $data['FileName']],
'Views' => ['N' => (string)($data['Views'] ?? 0)],
'S3Path' => ['S' => $data['S3Path'] ?? '']
]
]);
echo json_encode(["status"=>"success","message"=>"Item added successfully"]);
} catch(Exception $e){
echo json_encode(["status"=>"error","message"=>$e->getMessage()]);
}
?><?php
header("Content-Type: application/json");
header("Access-Control-Allow-Origin: *");
header("Access-Control-Allow-Headers: Content-Type");
// Include AWS SDK
require 'vendor/autoload.php';
use Aws\DynamoDb\DynamoDbClient;
$data = json_decode(file_get_contents("php://input"), true);
if(!$data || empty($data['FileName'])) {
echo json_encode(["status"=>"error","message"=>"FileName is required"]);
exit;
}
// Connect to DynamoDB
$dynamodb = new DynamoDbClient([
'region' => 'us-east-1',
'version' => 'latest'
]);
$table = 'FileAnalytics';
try{
$dynamodb->putItem([
'TableName' => $table,
'Item' => [
'FileName' => ['S' => $data['FileName']],
'Views' => ['N' => (string)($data['Views'] ?? 0)],
'S3Path' => ['S' => $data['S3Path'] ?? '']
]
]);
echo json_encode(["status"=>"success","message"=>"Item added successfully"]);
} catch(Exception $e){
echo json_encode(["status"=>"error","message"=>$e->getMessage()]);
}
?>get_files.php
Lists all files.
<?php
header("Content-Type: application/json");
header("Access-Control-Allow-Origin: *");
require 'vendor/autoload.php';
use Aws\DynamoDb\DynamoDbClient;
$dynamodb = new DynamoDbClient([
'region' => 'us-east-1',
'version' => 'latest'
]);
$table = 'FileAnalytics';
try {
$result = $dynamodb->scan([
'TableName' => $table
]);
$items = [];
foreach($result['Items'] as $item){
$items[] = [
'FileName' => $item['FileName']['S'] ?? '',
'Views' => isset($item['Views']['N']) ? (int)$item['Views']['N'] : 0,
'S3Path' => $item['S3Path']['S'] ?? ''
];
}
echo json_encode(["status"=>"success","items"=>$items]);
} catch(Exception $e){
echo json_encode(["status"=>"error","message"=>$e->getMessage()]);
}
?><?php
header("Content-Type: application/json");
header("Access-Control-Allow-Origin: *");
require 'vendor/autoload.php';
use Aws\DynamoDb\DynamoDbClient;
$dynamodb = new DynamoDbClient([
'region' => 'us-east-1',
'version' => 'latest'
]);
$table = 'FileAnalytics';
try {
$result = $dynamodb->scan([
'TableName' => $table
]);
$items = [];
foreach($result['Items'] as $item){
$items[] = [
'FileName' => $item['FileName']['S'] ?? '',
'Views' => isset($item['Views']['N']) ? (int)$item['Views']['N'] : 0,
'S3Path' => $item['S3Path']['S'] ?? ''
];
}
echo json_encode(["status"=>"success","items"=>$items]);
} catch(Exception $e){
echo json_encode(["status"=>"error","message"=>$e->getMessage()]);
}
?>update_views.php
Increments view count.
<?php
require 'vendor/autoload.php';
use Aws\DynamoDb\DynamoDbClient;
use Aws\DynamoDb\Exception\DynamoDbException;
header("Content-Type: application/json");
header("Access-Control-Allow-Origin: *");
header("Access-Control-Allow-Methods: GET, POST, OPTIONS");
header("Access-Control-Allow-Headers: Content-Type");
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
http_response_code(200);
exit;
}
$data = json_decode(file_get_contents('php://input'), true);
if (!isset($data['FileName'])) {
echo json_encode(["status"=>"error","message"=>"Missing FileName"]);
exit;
}
$client = new DynamoDbClient([
'region' => 'us-east-1',
'version' => 'latest'
]);
try {
$client->updateItem([
'TableName' => 'FileAnalytics',
'Key' => [
'FileName' => ['S' => $data['FileName']]
],
'UpdateExpression' => 'SET #v = #v + :inc',
'ExpressionAttributeNames' => ['#v' => 'Views'],
'ExpressionAttributeValues' => [':inc' => ['N' => '1']]
]);
echo json_encode(["status"=>"success","message"=>"View count updated successfully"]);
} catch (DynamoDbException $e) {
echo json_encode(["status"=>"error","message"=>$e->getMessage()]);
}
?>a<?php
require 'vendor/autoload.php';
use Aws\DynamoDb\DynamoDbClient;
use Aws\DynamoDb\Exception\DynamoDbException;
header("Content-Type: application/json");
header("Access-Control-Allow-Origin: *");
header("Access-Control-Allow-Methods: GET, POST, OPTIONS");
header("Access-Control-Allow-Headers: Content-Type");
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
http_response_code(200);
exit;
}
$data = json_decode(file_get_contents('php://input'), true);
if (!isset($data['FileName'])) {
echo json_encode(["status"=>"error","message"=>"Missing FileName"]);
exit;
}
$client = new DynamoDbClient([
'region' => 'us-east-1',
'version' => 'latest'
]);
try {
$client->updateItem([
'TableName' => 'FileAnalytics',
'Key' => [
'FileName' => ['S' => $data['FileName']]
],
'UpdateExpression' => 'SET #v = #v + :inc',
'ExpressionAttributeNames' => ['#v' => 'Views'],
'ExpressionAttributeValues' => [':inc' => ['N' => '1']]
]);
echo json_encode(["status"=>"success","message"=>"View count updated successfully"]);
} catch (DynamoDbException $e) {
echo json_encode(["status"=>"error","message"=>$e->getMessage()]);
}
?>aDeploy with:
eb deployeb deployPress enter or click to view image in full size
Press enter or click to view image in full size
Step 6 — Lambda Function for Presigned URLs
import boto3, json
s3=boto3.client('s3')
def lambda_handler(event, context):
b=json.loads(event['body'])
fn=b['filename']
reg=b.get('region','us')
bucket="kc-us-bucket" if reg=="us" else "harshal-raw-bucket-demo"
put=s3.generate_presigned_url('put_object',Params={'Bucket':bucket,'Key':fn},ExpiresIn=3600)
get=f"https://{bucket}.s3.amazonaws.com/{fn}"
return {"statusCode":200,"headers":{"Access-Control-Allow-Origin":"*"},"body":json.dumps({"upload_url":put,"download_url":get})}import boto3, json
s3=boto3.client('s3')
def lambda_handler(event, context):
b=json.loads(event['body'])
fn=b['filename']
reg=b.get('region','us')
bucket="kc-us-bucket" if reg=="us" else "harshal-raw-bucket-demo"
put=s3.generate_presigned_url('put_object',Params={'Bucket':bucket,'Key':fn},ExpiresIn=3600)
get=f"https://{bucket}.s3.amazonaws.com/{fn}"
return {"statusCode":200,"headers":{"Access-Control-Allow-Origin":"*"},"body":json.dumps({"upload_url":put,"download_url":get})}Analytics Lambda — User Actions Tracker
To extend observability, I added an Analytics Lambda Function that records every upload or view event. This is triggered asynchronously via the frontend after uploads or downloads.
Features:
- Receives user actions (upload/view)
- Logs them to a DynamoDB table (
AnalyticsTable or of your choice) - Enables tracking of system usage over time
Lambda Code (Analytics)
import json, boto3, os
s3 = boto3.client('s3')
def lambda_handler(event, context):
print("Received event:", json.dumps(event, indent=2))
for record in event['Records']:
bucket = record['s3']['bucket']['name']
key = record['s3']['object']['key']
# Skip already processed files
if key.startswith("processed/"):
print(f"Skipping already processed file: {key}")
continue
# Define new processed key
processed_key = f"processed/{os.path.basename(key)}"
# Copy file to processed/ folder
s3.copy_object(
Bucket=bucket,
CopySource={'Bucket': bucket, 'Key': key},
Key=processed_key
)
print(f"Processed {key} → {processed_key} in {bucket}")
# Optionally update analytics (e.g., DynamoDB or CloudWatch)
print(f" Analytics updated for {os.path.basename(key)}")
return {"status": "done"}import json, boto3, os
s3 = boto3.client('s3')
def lambda_handler(event, context):
print("Received event:", json.dumps(event, indent=2))
for record in event['Records']:
bucket = record['s3']['bucket']['name']
key = record['s3']['object']['key']
# Skip already processed files
if key.startswith("processed/"):
print(f"Skipping already processed file: {key}")
continue
# Define new processed key
processed_key = f"processed/{os.path.basename(key)}"
# Copy file to processed/ folder
s3.copy_object(
Bucket=bucket,
CopySource={'Bucket': bucket, 'Key': key},
Key=processed_key
)
print(f"Processed {key} → {processed_key} in {bucket}")
# Optionally update analytics (e.g., DynamoDB or CloudWatch)
print(f" Analytics updated for {os.path.basename(key)}")
return {"status": "done"}Link it with API Gateway → POST /new/presign, enable CORS, and deploy.
Step 7 — Create API Gateway
- Go to API Gateway → Create API → REST API
- Create a Resource named
/new - Add a POST Method
- Integration Type: Lambda Function
- Choose your Lambda (the one you just created)
- Enable CORS
- Deploy API:
- Click "Actions → Deploy API"
- Create new stage (e.g.,
new) - Note the endpoint: https://your-api-id.execute-api.us-east-1.amazonaws.com/new/presign
Press enter or click to view image in full size
Test with:
curl -X POST https://your-api-id.execute-api.us-east-1.amazonaws.com/new/presign \
-H "Content-Type: application/json" \
-d '{"filename":"test.pdf","region":"us"}'curl -X POST https://your-api-id.execute-api.us-east-1.amazonaws.com/new/presign \
-H "Content-Type: application/json" \
-d '{"filename":"test.pdf","region":"us"}'Step 8 — Frontend (index.html)
<!-- <!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>S3 Presigned Upload</title>
</head>
<body>
<h2>Upload File to S3</h2>
<label for="region">Choose region/bucket:</label>
<select id="region">
<option value="us">US (kc-us-bucket)</option>
<option value="saudi">Saudi (harshal-raw-bucket-demo)</option>
</select>
<br><br>
<input type="file" id="fileInput">
<button onclick="uploadFile()">Upload</button>
<h3>Results</h3>
<div id="results"></div>
<script>
async function uploadFile() {
const fileInput = document.getElementById("fileInput");
const file = fileInput.files[0];
const region = document.getElementById("region").value;
if (!file) {
alert("Please select a file!");
return;
}
// Call Lambda API to get presigned URLs
const response = await fetch("https://rvemi42kze.execute-api.us-east-1.amazonaws.com/new/presign", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
filename: file.name,
region: region
})
});
if (!response.ok) {
alert("Failed to get presigned URL");
return;
}
const data = await response.json();
const uploadUrl = data.upload_url;
const downloadUrl = data.download_url;
// Upload the file to S3 using the presigned URL
const uploadResponse = await fetch(uploadUrl, {
method: "PUT",
body: file
});
if (uploadResponse.ok) {
document.getElementById("results").innerHTML = `
<p>Upload successful!</p>
<p>Download URL: <a href="${downloadUrl}" target="_blank">${downloadUrl}</a></p>
`;
} else {
document.getElementById("results").innerText = "Upload failed!";
}
}
</script>
</body>
</html> -->
<!-- <!doctype html>
<html>
<head>
<meta charset="utf-8"/>
<title>Upload Video / File for Presigning</title>
<style>body{font-family:sans-serif;background:#0f172a;color:#fff;padding:40px;text-align:center}input,select{padding:8px;margin:6px}button{padding:8px 14px;background:#3b82f6;border:none;border-radius:6px;color:white;cursor:pointer}</style>
</head>
<body>
<h1>Upload Video / File for Presigning</h1>
<select id="region">
<option value="us">US (kc-us-bucket)</option>
<option value="saudi">Saudi Arabia (harshal-raw-bucket-demo)</option>
</select>
<input type="file" id="file"/>
<br/>
<button id="uploadBtn">Upload & Process</button>
<p id="status"></p>
<script>
const API = "https://rvemi42kze.execute-api.us-east-1.amazonaws.com/new/presign"; // replace with your API Gateway endpoint (POST)
// const ANALYTICS_API = "https://<YOUR_API_HOST>/new/analytics"; // optional
document.getElementById("uploadBtn").onclick = async function() {
const f = document.getElementById("file").files[0];
const region = document.getElementById("region").value;
const status = document.getElementById("status");
if (!f) { alert("choose file"); return;}
status.innerText = "Requesting presigned URL...";
try {
const res = await fetch(API, {
method: "POST",
headers: {"Content-Type":"application/json"},
body: JSON.stringify({filename: f.name, region})
});
if (!res.ok) throw new Error("presign failed");
const data = await res.json();
const put = data.upload_url || data.uploadUrl || data.put_url || data.uploadUrl;
const get = data.download_url || data.downloadUrl || data.get_url || data.getUrl;
if (!put) throw new Error("no upload URL returned");
status.innerText = "Uploading file to S3...";
const r2 = await fetch(put, {method: "PUT", body: f});
if (!r2.ok) throw new Error("upload failed: "+r2.status);
status.innerText = "Triggering analytics/process...";
// optional analytics
try {
await fetch(ANALYTICS_API, {method:"POST", headers:{"Content-Type":"application/json"}, body: JSON.stringify({userId:"demo", action:"upload", region})});
} catch(err){ console.warn("analytics failed", err);}
status.innerHTML = `Upload complete! Download: <a href="${get}" target="_blank">open file</a>`;
} catch (e) {
status.innerText = "Error: "+(e.message||e);
console.error(e);
}
};
</script>
</body>
</html> -->
<!-- <!doctype html>
<html>
<head>
<meta charset="utf-8"/>
<title>Upload Video / File</title>
<style>
body{font-family:sans-serif;background:#0f172a;color:#fff;padding:40px;text-align:center}
input,select{padding:8px;margin:6px}
button{padding:8px 14px;background:#3b82f6;border:none;border-radius:6px;color:white;cursor:pointer}
table{margin:20px auto; border-collapse: collapse; width: 80%;}
th,td{padding:8px;border:1px solid #ccc;text-align:center;color:#fff;}
</style>
</head>
<body>
<h1>Upload Video / File</h1>
<select id="region">
<option value="us">US (kc-us-bucket)</option>
<option value="saudi">Saudi Arabia (harshal-raw-bucket-demo)</option>
</select>
<input type="file" id="file"/>
<br/>
<button id="uploadBtn">Upload & Register</button>
<p id="status"></p>
<h2>Uploaded Files</h2>
<table id="fileTable">
<thead><tr><th>File Name</th><th>Views</th><th>Download</th></tr></thead>
<tbody></tbody>
</table>
<script>
const PRESIGN_API = "https://rvemi42kze.execute-api.us-east-1.amazonaws.com/new/presign";
const EB_API = "http://php-core-env1.eba-mhmr3sak.us-east-1.elasticbeanstalk.com";
async function refreshFileList() {
const tbody = document.querySelector("#fileTable tbody");
tbody.innerHTML = '';
try {
const res = await fetch(`${EB_API}/get_files.php`);
const data = await res.json();
if(data.status === "success" && Array.isArray(data.items)){
data.items.forEach(item=>{
const tr = document.createElement('tr');
const name = item.FileName?.S || item.id || 'N/A';
const views = item.Views?.N || 0;
const downloadUrl = item.S3Path?.S || '#';
tr.innerHTML = `<td>${name}</td><td>${views}</td><td><a href="${downloadUrl}" target="_blank" onclick="incrementViews('${name}')">Download</a></td>`;
tbody.appendChild(tr);
});
}
} catch(err){ console.warn(err); }
}
async function incrementViews(fileName){
try{
await fetch(`${EB_API}/update_views.php`, {
method:"POST",
headers:{"Content-Type":"application/json"},
body: JSON.stringify({FileName:fileName})
});
refreshFileList();
} catch(e){ console.error(e); }
}
document.getElementById("uploadBtn").onclick = async function() {
const f = document.getElementById("file").files[0];
const region = document.getElementById("region").value;
const status = document.getElementById("status");
if(!f){ alert("Choose a file"); return; }
status.innerText = "Requesting presigned URL...";
try{
const res = await fetch(PRESIGN_API,{
method:"POST",
headers:{"Content-Type":"application/json"},
body: JSON.stringify({filename: f.name, region})
});
const data = await res.json();
const putUrl = data.upload_url || data.uploadUrl;
const getUrl = data.download_url || data.downloadUrl;
status.innerText = "Uploading to S3...";
const r2 = await fetch(putUrl, {method:"PUT", body:f});
if(!r2.ok) throw new Error("Upload failed");
status.innerText = "Registering file in DynamoDB...";
await fetch(`${EB_API}/add_file.php`,{
method:"POST",
headers:{"Content-Type":"application/json"},
body: JSON.stringify({FileName:f.name, Views:0, S3Path:getUrl})
});
status.innerHTML = `Upload complete! <a href="${getUrl}" target="_blank">Download File</a>`;
refreshFileList();
} catch(e){
status.innerText = "Error: "+(e.message||e);
console.error(e);
}
};
refreshFileList();
</script>
</body>
</html> -->
<!doctype html>
<html>
<head>
<meta charset="utf-8"/>
<title>Upload Video / File</title>
<style>
body{font-family:sans-serif;background:#0f172a;color:#fff;padding:40px;text-align:center}
input,select{padding:8px;margin:6px}
button{padding:8px 14px;background:#3b82f6;border:none;border-radius:6px;color:white;cursor:pointer}
table{margin:20px auto; border-collapse: collapse; width: 80%;}
th,td{padding:8px;border:1px solid #ccc;text-align:center;color:#fff;}
</style>
</head>
<body>
<h1>Upload Video / File</h1>
<select id="region">
<option value="us">US (kc-us-bucket)</option>
<option value="saudi">Saudi Arabia (harshal-raw-bucket-demo)</option>
</select>
<input type="file" id="file"/>
<br/>
<button id="uploadBtn">Upload & Register</button>
<p id="status"></p>
<h2>Uploaded Files</h2>
<table id="fileTable">
<thead><tr><th>File Name</th><th>Views</th><th>Download</th></tr></thead>
<tbody></tbody>
</table>
<script>
const PRESIGN_API = "https://rvexxxxxx.execute-api.us-east-1.amazonaws.com/new/presign";
const EB_API = "http://php-xxx-xxx.eba-xxxxxx.us-east-1.elasticbeanstalk.com";
// Refresh files table
async function refreshFileList() {
const tbody = document.querySelector("#fileTable tbody");
tbody.innerHTML = '';
try {
const res = await fetch(`${EB_API}/get_files.php`);
const data = await res.json();
if(data.status === "success" && Array.isArray(data.items)){
data.items.forEach(item=>{
const tr = document.createElement('tr');
const name = item.FileName || 'N/A';
const views = item.Views || 0;
const downloadUrl = item.S3Path || '#';
tr.innerHTML = `<td>${name}</td><td>${views}</td><td><a href="${downloadUrl}" target="_blank" onclick="incrementViews('${name}')">Download</a></td>`;
tbody.appendChild(tr);
});
}
} catch(err){ console.warn(err); }
}
// Increment views when file is downloaded
async function incrementViews(fileName){
try{
await fetch(`${EB_API}/update_views.php`, {
method:"POST",
headers:{"Content-Type":"application/json"},
body: JSON.stringify({FileName:fileName})
});
refreshFileList();
} catch(e){ console.error(e); }
}
// Upload & register file
document.getElementById("uploadBtn").onclick = async function() {
const f = document.getElementById("file").files[0];
const region = document.getElementById("region").value;
const status = document.getElementById("status");
if(!f){ alert("Choose a file"); return; }
status.innerText = "Requesting presigned URL...";
try{
const res = await fetch(PRESIGN_API,{
method:"POST",
headers:{"Content-Type":"application/json"},
body: JSON.stringify({filename: f.name, region})
});
const data = await res.json();
const putUrl = data.upload_url || data.uploadUrl;
const getUrl = data.download_url || data.downloadUrl;
status.innerText = "Uploading to S3...";
const r2 = await fetch(putUrl, {method:"PUT", body:f});
if(!r2.ok) throw new Error("Upload failed");
status.innerText = "Registering file in DynamoDB...";
await fetch(`${EB_API}/add_file.php`,{
method:"POST",
headers:{"Content-Type":"application/json"},
body: JSON.stringify({FileName:f.name, Views:0, S3Path:getUrl})
});
status.innerHTML = `Upload complete! <a href="${getUrl}" target="_blank">Download File</a>`;
refreshFileList();
} catch(e){
status.innerText = "Error: "+(e.message||e);
console.error(e);
}
};
refreshFileList();
</script>
</body>
</html><!-- <!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>S3 Presigned Upload</title>
</head>
<body>
<h2>Upload File to S3</h2>
<label for="region">Choose region/bucket:</label>
<select id="region">
<option value="us">US (kc-us-bucket)</option>
<option value="saudi">Saudi (harshal-raw-bucket-demo)</option>
</select>
<br><br>
<input type="file" id="fileInput">
<button onclick="uploadFile()">Upload</button>
<h3>Results</h3>
<div id="results"></div>
<script>
async function uploadFile() {
const fileInput = document.getElementById("fileInput");
const file = fileInput.files[0];
const region = document.getElementById("region").value;
if (!file) {
alert("Please select a file!");
return;
}
// Call Lambda API to get presigned URLs
const response = await fetch("https://rvemi42kze.execute-api.us-east-1.amazonaws.com/new/presign", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
filename: file.name,
region: region
})
});
if (!response.ok) {
alert("Failed to get presigned URL");
return;
}
const data = await response.json();
const uploadUrl = data.upload_url;
const downloadUrl = data.download_url;
// Upload the file to S3 using the presigned URL
const uploadResponse = await fetch(uploadUrl, {
method: "PUT",
body: file
});
if (uploadResponse.ok) {
document.getElementById("results").innerHTML = `
<p>Upload successful!</p>
<p>Download URL: <a href="${downloadUrl}" target="_blank">${downloadUrl}</a></p>
`;
} else {
document.getElementById("results").innerText = "Upload failed!";
}
}
</script>
</body>
</html> -->
<!-- <!doctype html>
<html>
<head>
<meta charset="utf-8"/>
<title>Upload Video / File for Presigning</title>
<style>body{font-family:sans-serif;background:#0f172a;color:#fff;padding:40px;text-align:center}input,select{padding:8px;margin:6px}button{padding:8px 14px;background:#3b82f6;border:none;border-radius:6px;color:white;cursor:pointer}</style>
</head>
<body>
<h1>Upload Video / File for Presigning</h1>
<select id="region">
<option value="us">US (kc-us-bucket)</option>
<option value="saudi">Saudi Arabia (harshal-raw-bucket-demo)</option>
</select>
<input type="file" id="file"/>
<br/>
<button id="uploadBtn">Upload & Process</button>
<p id="status"></p>
<script>
const API = "https://rvemi42kze.execute-api.us-east-1.amazonaws.com/new/presign"; // replace with your API Gateway endpoint (POST)
// const ANALYTICS_API = "https://<YOUR_API_HOST>/new/analytics"; // optional
document.getElementById("uploadBtn").onclick = async function() {
const f = document.getElementById("file").files[0];
const region = document.getElementById("region").value;
const status = document.getElementById("status");
if (!f) { alert("choose file"); return;}
status.innerText = "Requesting presigned URL...";
try {
const res = await fetch(API, {
method: "POST",
headers: {"Content-Type":"application/json"},
body: JSON.stringify({filename: f.name, region})
});
if (!res.ok) throw new Error("presign failed");
const data = await res.json();
const put = data.upload_url || data.uploadUrl || data.put_url || data.uploadUrl;
const get = data.download_url || data.downloadUrl || data.get_url || data.getUrl;
if (!put) throw new Error("no upload URL returned");
status.innerText = "Uploading file to S3...";
const r2 = await fetch(put, {method: "PUT", body: f});
if (!r2.ok) throw new Error("upload failed: "+r2.status);
status.innerText = "Triggering analytics/process...";
// optional analytics
try {
await fetch(ANALYTICS_API, {method:"POST", headers:{"Content-Type":"application/json"}, body: JSON.stringify({userId:"demo", action:"upload", region})});
} catch(err){ console.warn("analytics failed", err);}
status.innerHTML = `Upload complete! Download: <a href="${get}" target="_blank">open file</a>`;
} catch (e) {
status.innerText = "Error: "+(e.message||e);
console.error(e);
}
};
</script>
</body>
</html> -->
<!-- <!doctype html>
<html>
<head>
<meta charset="utf-8"/>
<title>Upload Video / File</title>
<style>
body{font-family:sans-serif;background:#0f172a;color:#fff;padding:40px;text-align:center}
input,select{padding:8px;margin:6px}
button{padding:8px 14px;background:#3b82f6;border:none;border-radius:6px;color:white;cursor:pointer}
table{margin:20px auto; border-collapse: collapse; width: 80%;}
th,td{padding:8px;border:1px solid #ccc;text-align:center;color:#fff;}
</style>
</head>
<body>
<h1>Upload Video / File</h1>
<select id="region">
<option value="us">US (kc-us-bucket)</option>
<option value="saudi">Saudi Arabia (harshal-raw-bucket-demo)</option>
</select>
<input type="file" id="file"/>
<br/>
<button id="uploadBtn">Upload & Register</button>
<p id="status"></p>
<h2>Uploaded Files</h2>
<table id="fileTable">
<thead><tr><th>File Name</th><th>Views</th><th>Download</th></tr></thead>
<tbody></tbody>
</table>
<script>
const PRESIGN_API = "https://rvemi42kze.execute-api.us-east-1.amazonaws.com/new/presign";
const EB_API = "http://php-core-env1.eba-mhmr3sak.us-east-1.elasticbeanstalk.com";
async function refreshFileList() {
const tbody = document.querySelector("#fileTable tbody");
tbody.innerHTML = '';
try {
const res = await fetch(`${EB_API}/get_files.php`);
const data = await res.json();
if(data.status === "success" && Array.isArray(data.items)){
data.items.forEach(item=>{
const tr = document.createElement('tr');
const name = item.FileName?.S || item.id || 'N/A';
const views = item.Views?.N || 0;
const downloadUrl = item.S3Path?.S || '#';
tr.innerHTML = `<td>${name}</td><td>${views}</td><td><a href="${downloadUrl}" target="_blank" onclick="incrementViews('${name}')">Download</a></td>`;
tbody.appendChild(tr);
});
}
} catch(err){ console.warn(err); }
}
async function incrementViews(fileName){
try{
await fetch(`${EB_API}/update_views.php`, {
method:"POST",
headers:{"Content-Type":"application/json"},
body: JSON.stringify({FileName:fileName})
});
refreshFileList();
} catch(e){ console.error(e); }
}
document.getElementById("uploadBtn").onclick = async function() {
const f = document.getElementById("file").files[0];
const region = document.getElementById("region").value;
const status = document.getElementById("status");
if(!f){ alert("Choose a file"); return; }
status.innerText = "Requesting presigned URL...";
try{
const res = await fetch(PRESIGN_API,{
method:"POST",
headers:{"Content-Type":"application/json"},
body: JSON.stringify({filename: f.name, region})
});
const data = await res.json();
const putUrl = data.upload_url || data.uploadUrl;
const getUrl = data.download_url || data.downloadUrl;
status.innerText = "Uploading to S3...";
const r2 = await fetch(putUrl, {method:"PUT", body:f});
if(!r2.ok) throw new Error("Upload failed");
status.innerText = "Registering file in DynamoDB...";
await fetch(`${EB_API}/add_file.php`,{
method:"POST",
headers:{"Content-Type":"application/json"},
body: JSON.stringify({FileName:f.name, Views:0, S3Path:getUrl})
});
status.innerHTML = `Upload complete! <a href="${getUrl}" target="_blank">Download File</a>`;
refreshFileList();
} catch(e){
status.innerText = "Error: "+(e.message||e);
console.error(e);
}
};
refreshFileList();
</script>
</body>
</html> -->
<!doctype html>
<html>
<head>
<meta charset="utf-8"/>
<title>Upload Video / File</title>
<style>
body{font-family:sans-serif;background:#0f172a;color:#fff;padding:40px;text-align:center}
input,select{padding:8px;margin:6px}
button{padding:8px 14px;background:#3b82f6;border:none;border-radius:6px;color:white;cursor:pointer}
table{margin:20px auto; border-collapse: collapse; width: 80%;}
th,td{padding:8px;border:1px solid #ccc;text-align:center;color:#fff;}
</style>
</head>
<body>
<h1>Upload Video / File</h1>
<select id="region">
<option value="us">US (kc-us-bucket)</option>
<option value="saudi">Saudi Arabia (harshal-raw-bucket-demo)</option>
</select>
<input type="file" id="file"/>
<br/>
<button id="uploadBtn">Upload & Register</button>
<p id="status"></p>
<h2>Uploaded Files</h2>
<table id="fileTable">
<thead><tr><th>File Name</th><th>Views</th><th>Download</th></tr></thead>
<tbody></tbody>
</table>
<script>
const PRESIGN_API = "https://rvexxxxxx.execute-api.us-east-1.amazonaws.com/new/presign";
const EB_API = "http://php-xxx-xxx.eba-xxxxxx.us-east-1.elasticbeanstalk.com";
// Refresh files table
async function refreshFileList() {
const tbody = document.querySelector("#fileTable tbody");
tbody.innerHTML = '';
try {
const res = await fetch(`${EB_API}/get_files.php`);
const data = await res.json();
if(data.status === "success" && Array.isArray(data.items)){
data.items.forEach(item=>{
const tr = document.createElement('tr');
const name = item.FileName || 'N/A';
const views = item.Views || 0;
const downloadUrl = item.S3Path || '#';
tr.innerHTML = `<td>${name}</td><td>${views}</td><td><a href="${downloadUrl}" target="_blank" onclick="incrementViews('${name}')">Download</a></td>`;
tbody.appendChild(tr);
});
}
} catch(err){ console.warn(err); }
}
// Increment views when file is downloaded
async function incrementViews(fileName){
try{
await fetch(`${EB_API}/update_views.php`, {
method:"POST",
headers:{"Content-Type":"application/json"},
body: JSON.stringify({FileName:fileName})
});
refreshFileList();
} catch(e){ console.error(e); }
}
// Upload & register file
document.getElementById("uploadBtn").onclick = async function() {
const f = document.getElementById("file").files[0];
const region = document.getElementById("region").value;
const status = document.getElementById("status");
if(!f){ alert("Choose a file"); return; }
status.innerText = "Requesting presigned URL...";
try{
const res = await fetch(PRESIGN_API,{
method:"POST",
headers:{"Content-Type":"application/json"},
body: JSON.stringify({filename: f.name, region})
});
const data = await res.json();
const putUrl = data.upload_url || data.uploadUrl;
const getUrl = data.download_url || data.downloadUrl;
status.innerText = "Uploading to S3...";
const r2 = await fetch(putUrl, {method:"PUT", body:f});
if(!r2.ok) throw new Error("Upload failed");
status.innerText = "Registering file in DynamoDB...";
await fetch(`${EB_API}/add_file.php`,{
method:"POST",
headers:{"Content-Type":"application/json"},
body: JSON.stringify({FileName:f.name, Views:0, S3Path:getUrl})
});
status.innerHTML = `Upload complete! <a href="${getUrl}" target="_blank">Download File</a>`;
refreshFileList();
} catch(e){
status.innerText = "Error: "+(e.message||e);
console.error(e);
}
};
refreshFileList();
</script>
</body>
</html>Press enter or click to view image in full size
Step 9 — CloudFront Hosting
You can host the frontend via:
- Uploading
index.htmlto S3 - Enabling static website hosting
- Creating a CloudFront distribution for global CD
- Adding HTTPS using ACM (if available in your region)
Press enter or click to view image in full size
Step 10 — Test It
- Open the web page.
- Choose a file → click Upload.
- File appears in S3 and in DynamoDB.
- Click Download → view count increments automatically.
Press enter or click to view image in full size
Press enter or click to view image in full size
Lessons Learned
- IAM roles are crucial — even one missing policy can stop deployment.
- Presigned URLs make uploads faster and safer.
- Elastic Beanstalk's managed PHP platform is a great middle ground between full server management and pure Lambda.
- DynamoDB is ideal for small, fast, serverless metadata storage.
- CORS and HTTPS need to be configured everywhere — S3, API Gateway, and backend.
Wrapping Up
This project demonstrates how a developer or DevOps engineer can integrate multiple AWS services — S3, Lambda, API Gateway, DynamoDB, and Elastic Beanstalk — to create a real, production-style solution without managing servers.
The result is a lightweight, fully scalable, pay-as-you-go application that handles file uploads, tracks analytics, and can grow globally with minimal effort.
Follow me :
Linkedin: https://www.linkedin.com/in/harshaljethwa/
GitHub: https://github.com/HARSHALJETHWA19/
Twitter: https://twitter.com/harshaljethwaa
Thank You!!!