Skip to main content
Skip to main content
Still in beta — questions, comments or suggestions? aramb@aramb.dev

Add Detail View and File Links for a More Complete Upload Dashboard

Add a detail view for uploaded files and return presigned S3 URLs to allow safe, temporary access to private files.

30 min
Introductory
AWS Free TierFREE TIER

All services used in this lesson are covered by the AWS Free Tier.

AWS Services Used

S312-month free tierLambdaAlways free tierAPI Gateway12-month free tierDynamoDBAlways free tier

Learning Outcomes

By the end of this lesson, you will be able to:

  1. Add a detail view for one uploaded file.
  2. Return a presigned S3 URL so a private file can be opened safely from the browser.
  3. Explain why presigned URLs are better than making the whole bucket public.
  4. Update the frontend to show file details and a temporary download/view link.
  5. Explain why the Lambda function that generates the presigned URL must have permission to read the object.

The Core Idea

Your upload bucket has been private so far, and that is the right default. By default, Amazon S3 objects are private, and one standard way to share access temporarily is to create a presigned URL.

A presigned URL uses the creator’s AWS credentials to grant time-limited permission to access an object, and the URL can be opened in a browser. This means you do not need to make the bucket public just to let a user open one file.

Presigned URL Access Flow

Why presigned URLs are the right fit here

A presigned URL is ideal for this lesson because:

  • Your objects stay private by default.
  • The browser can still open one selected file.
  • Access is temporary, not permanent.
  • You do not need a public bucket policy for every object.

Part 1: Upgrade the Detail Lambda Function

You already built a read endpoint in a previous lesson. Now upgrade that Lambda so it returns:

  • The metadata item from DynamoDB.
  • A presigned S3 URL for the object.

Add an environment variable URL_EXPIRES_SECONDS=900.

Use this Python code:

import json
import os
import boto3

dynamodb = boto3.resource("dynamodb")
table = dynamodb.Table(os.environ["TABLE_NAME"])
s3_client = boto3.client("s3")

URL_EXPIRES_SECONDS = int(os.environ.get("URL_EXPIRES_SECONDS", "900"))

def lambda_handler(event, context):
    query = event.get("queryStringParameters") or {}

    bucket = query.get("bucket")
    object_key = query.get("key")

    if not bucket or not object_key:
        return {
            "statusCode": 400,
            "headers": {"Content-Type": "application/json"},
            "body": json.dumps({
                "error": "Missing required query parameters: bucket and key"
            })
        }

    response = table.get_item(
        Key={
            "bucket": bucket,
            "object_key": object_key
        }
    )

    item = response.get("Item")

    if not item:
        return {
            "statusCode": 404,
            "headers": {"Content-Type": "application/json"},
            "body": json.dumps({
                "error": "Metadata not found"
            })
        }

    download_url = s3_client.generate_presigned_url(
        "get_object",
        Params={
            "Bucket": bucket,
            "Key": object_key
        },
        ExpiresIn=URL_EXPIRES_SECONDS
    )

    result = {
        "item": item,
        "download_url": download_url,
        "expires_in": URL_EXPIRES_SECONDS
    }

    return {
        "statusCode": 200,
        "headers": {"Content-Type": "application/json"},
        "body": json.dumps(result)
    }

Part 2: Update the Lambda Execution Role

The Lambda function needs permission to perform s3:GetObject on the uploaded object, because presigned URLs use the permissions of the AWS principal that generated them.

Add this statement to your role:

{
  "Sid": "ReadUploadedObjects",
  "Effect": "Allow",
  "Action": ["s3:GetObject"],
  "Resource": "arn:aws:s3:::YOUR_UPLOAD_BUCKET/*"
}

Part 3: Update the Frontend

Update your frontend so each listed file can load details and show an “Open file” link.

Add a detail section to your HTML:

<div id="detail"></div>

And update your JavaScript to include a loadDetail function that fetches from your existing /metadata endpoint (now upgraded with the download URL).


Part 4: Test the Flow

  1. Open the S3-hosted frontend.
  2. Search using your bucket and optional prefix.
  3. Click View details on one file.
  4. Confirm the detail panel appears with an Open file link.
  5. Verify the file opens while the bucket remains private.

Lab Checklist

StepSuccess Condition
Update detail LambdaFunction returns metadata plus download_url
Add s3:GetObject permissionLambda can generate working presigned URLs
Update frontendDetail panel with "Open file" link appears
Click open linkFile opens using temporary URL
Keep bucket privateNo public bucket policy is needed

Micro-activity 1: Reflection

Think about it

After the lab, reflect: Which object key did you open? Did the detail endpoint return a download_url? How many seconds was the link valid for? Did the file open without making the bucket public?


Micro-activity 2: Presigned URL Concepts

Micro-Activity

Match each presigned URL concept to its description

Examples

Choose one, then match it on the right

Characteristics

Select an example first

0 of 4 matched so far.


Summary

In this lesson, you upgraded a simple list page into a more complete private upload dashboard. The backend now returns both metadata and a temporary S3 access link, and the frontend lets the user open a file without exposing the entire bucket.


Quiz

Knowledge Check
1 / 5

Why is a presigned URL useful in this lesson?