Personal Bookmark Saver

The goal of this project is to create a simple web app to save your favorites, add tags and notes, search or export bookmarks.

What will be used
  • Cognito: Handle user authentication.
  • API Gateway: Expose API endpoints for frontend (add, list, delete).
  • Lambda: Handle backend logic (read/write bookmarks).
  • DynamoDB: Store bookmarks (URL, title, tags, timestamp, notes, etc.).
  • S3: Host the frontend (HTML/JS).
Create DynamoDB Table

Go to DynamoDB, create a table:

  • Table name: Bookmarks
  • Partition key: userId (String)
  • Sort key: timestamp (Number)

Add attributes:

  • url (String)
  • title (String)
  • tags (String)
  • notes (String)
Set Up Cognito

Go to Amazon Cognito:

  • Create a User Pool and a User Pool App Client.
  • Enable email/password sign-up.
  • Enable hosted UI if needed.
  • Use Cognito Identity Pool for temporary AWS credentials in frontend.
Create Lambda Functions

Here are the different functions:

  • addBookmark
  • import boto3
    import json
    import time
    
    dynamodb = boto3.resource('dynamodb')
    table = dynamodb.Table('Bookmarks')
    
    def lambda_handler(event, context):
        body = json.loads(event['body'])
    
        user_id = event['requestContext']['authorizer']['claims']['sub']  # Cognito user ID
        timestamp = int(time.time())
    
        item = {
            'userId': user_id,
            'timestamp': timestamp,
            'url': body['url'],
            'title': body['title'],
            'tags': body.get('tags', ''),
            'notes': body.get('notes', '')
        }
    
        table.put_item(Item=item)
    
        return {
            'statusCode': 200,
            'body': json.dumps({'message': 'Bookmark added'})
        }
  • listBookmarks
  • import boto3
    import json
    
    dynamodb = boto3.resource('dynamodb')
    table = dynamodb.Table('Bookmarks')
    
    def lambda_handler(event, context):
        user_id = event['requestContext']['authorizer']['claims']['sub']
    
        response = table.query(
            KeyConditionExpression=boto3.dynamodb.conditions.Key('userId').eq(user_id),
            ScanIndexForward=False  # latest first
        )
    
        return {
            'statusCode': 200,
            'body': json.dumps(response['Items'])
        }
  • deleteBookmark
  • import boto3
    import json
    
    dynamodb = boto3.resource('dynamodb')
    table = dynamodb.Table('Bookmarks')
    
    def lambda_handler(event, context):
        user_id = event['requestContext']['authorizer']['claims']['sub']
        body = json.loads(event['body'])
    
        table.delete_item(
            Key={
                'userId': user_id,
                'timestamp': body['timestamp']
            }
        )
    
        return {
            'statusCode': 200,
            'body': json.dumps({'message': 'Bookmark deleted'})
        }
  • exportBookmarks (CSV)
  • import boto3
    import json
    import csv
    import io
    
    dynamodb = boto3.resource('dynamodb')
    table = dynamodb.Table('Bookmarks')
    
    def lambda_handler(event, context):
        user_id = event['requestContext']['authorizer']['claims']['sub']
    
        response = table.query(
            KeyConditionExpression=boto3.dynamodb.conditions.Key('userId').eq(user_id)
        )
    
        bookmarks = response['Items']
    
        output = io.StringIO()
        writer = csv.DictWriter(output, fieldnames=['url', 'title', 'tags', 'notes', 'timestamp'])
        writer.writeheader()
        for item in bookmarks:
            writer.writerow({
                'url': item.get('url', ''),
                'title': item.get('title', ''),
                'tags': item.get('tags', ''),
                'notes': item.get('notes', ''),
                'timestamp': item.get('timestamp', '')
            })
    
        return {
            'statusCode': 200,
            'headers': {
                'Content-Type': 'text/csv',
                'Content-Disposition': 'attachment; filename="bookmarks.csv"'
            },
            'body': output.getvalue()
        }
Create API Gateway

Create a HTTP API.

Add routes:

  • POST /add-bookmark → addBookmark Lambda
  • GET /list-bookmarks → listBookmarks Lambda
  • POST /delete-bookmark -> deleteBookmark Lambda
  • GET /export-bookmarks -> exportBookmarks

Enable CORS for frontend use.

Fnable authorization for Cognito.

Build the Frontend

Simple HTML + JS interface:

<form onsubmit="saveBookmark(); return false;">
    <input id="url" placeholder="URL"><br>
    <input id="title" placeholder="Title"><br>
    <input id="tags" placeholder="Tags"><br>
    <textarea id="notes" placeholder="Notes"></textarea><br>
    <button type="submit">Save</button>
</form>

<pre id="response"></pre>

<script>
async function saveBookmark() {
    const data = {
    url: document.getElementById('url').value,
    title: document.getElementById('title').value,
    tags: document.getElementById('tags').value,
    notes: document.getElementById('notes').value
    };

    const res = await fetch("https://your-api-id.amazonaws.com/add-bookmark", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify(data)
    });

    const result = await res.json();
    document.getElementById("response").textContent = JSON.stringify(result, null, 2);
}
</script>

Create your S3 bucket:

  • Go to the AWS Console → S3 → Create bucket
  • Give it a name like my-bookmark-app
  • Region: same as your Lambda/API if possible
  • Uncheck "Block all public access"
  • Acknowledge the warning checkbox

Enable Static Website Hosting

  • After creating the bucket, go to Properties tab
  • Scroll to Static website hosting
  • Click Edit → Enable
  • Set:
    • Index document: index.html
    • (optional) Error document: error.html
  • Click Save changes

Upload Your HTML File defined earlier

Make the HTML Public

  • Go to the uploaded index.html
  • Click Permissions tab → Edit → Set object to public
  • Or use a Bucket Policy (recommended for multiple files):
  • {
        "Version": "2012-10-17",
        "Statement": [
            {
            "Sid": "PublicRead",
            "Effect": "Allow",
            "Principal": "*",
            "Action": "s3:GetObject",
            "Resource": "arn:aws:s3:::my-bookmark-app/*"
            }
        ]
    }

    Replace my-bookmark-app with your bucket name.

Your static site is now live at (replace yourRegion): http://my-bookmark-app.s3-website-yourRegion.amazonaws.com