Skip to content

shubhmate/AWS_Lambda_Http_Function_Url_Tutorial

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

4 Commits
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Dynamic Form Handler with DynamoDB Integration

A serverless web application built with AWS Lambda that processes form submissions and stores them in DynamoDB, displaying both form results and existing database records in real-time.

πŸ“‹ Table of Contents

🎯 Overview

This project demonstrates:

  • Serverless form processing using AWS Lambda
  • Database integration with DynamoDB
  • Dynamic HTML generation server-side
  • Real-time data display of form submissions
  • HTTP Function URLs for direct web access

πŸ—οΈ Architecture

Internet β†’ Lambda Function URL β†’ Lambda Function β†’ DynamoDB β†’ Dynamic HTML Response

Components:

  • AWS Lambda: Processes HTTP requests and generates dynamic HTML
  • DynamoDB: Stores form submissions with composite keys
  • IAM Role: Provides necessary permissions for Lambda-DynamoDB interaction
  • Function URL: Enables direct HTTP access to the Lambda function

πŸ“‹ Prerequisites

  • AWS Account with appropriate permissions
  • Basic knowledge of AWS Lambda, DynamoDB, and IAM
  • Text editor for code editing
  • Web browser for testing

πŸš€ Step-by-Step Setup

Step 1: Create DynamoDB Table

  1. Navigate to DynamoDB Console

  2. Create Table

    • Click "Create table"
    • Table name: formStore
    • Partition key: PK (String)
    • Sort key: SK (String)
    • Settings: Use default settings (On-demand billing)
    • Click "Create table"
  3. Wait for Table Creation

    • Status should show "Active" before proceeding

Step 2: Create Lambda Function

  1. Navigate to Lambda Console

  2. Create Function

    • Click "Create function"
    • Choose "Author from scratch"
    • Function name: http-function-url-tutorial
    • Runtime: Node.js 22.x
    • Click "Create function"

Step 3: Configure Function URL

  1. Add Function URL

    • In your Lambda function, go to Configuration β†’ Function URL
    • Click "Create function URL"
    • Auth type: NONE (for public access)
    • CORS: Enable if needed
    • Click "Save"
  2. Copy Function URL

    • Save the generated URL for testing

Step 4: Update IAM Permissions

  1. Navigate to IAM Console

  2. Find Lambda Execution Role

    • Search for your Lambda function's role (e.g., http-function-url-tutorial-role-xxxxx)
  3. Add DynamoDB Policy

    • Click on the role β†’ Add permissions β†’ Create inline policy
    • Policy name: DynamoDBAccessPolicy
    • JSON Policy:
    {
        "Version": "2012-10-17",
        "Statement": [
            {
                "Effect": "Allow",
                "Action": [
                    "dynamodb:PutItem",
                    "dynamodb:Query",
                    "dynamodb:Scan",
                    "dynamodb:GetItem"
                ],
                "Resource": "arn:aws:dynamodb:us-east-1:YOUR_ACCOUNT_ID:table/formStore"
            }
        ]
    }
    
  • Replace YOUR_ACCOUNT_ID with your actual AWS account ID
  • Click "Create policy"

Step 5: Create Project Files

  • Create these files locally on your computer:

5.1 Create index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Dynamic Form Handler with Database</title>
    <style>
        body { 
            font-family: Arial, sans-serif; 
            max-width: 800px; 
            margin: 0 auto; 
            padding: 20px; 
            background-color: #f5f5f5;
        }
        .container { 
            background: white; 
            padding: 30px; 
            border-radius: 10px; 
            box-shadow: 0 2px 10px rgba(0,0,0,0.1); 
        }
        .result { 
            background: #d4edda; 
            border: 1px solid #c3e6cb; 
            color: #155724; 
            padding: 15px; 
            border-radius: 5px; 
            margin: 20px 0; 
        }
        .form-group {
            margin-bottom: 15px;
        }
        label {
            display: block;
            margin-bottom: 5px;
            font-weight: bold;
        }
        input, textarea, select { 
            width: 100%; 
            padding: 10px; 
            border: 1px solid #ddd; 
            border-radius: 4px; 
            box-sizing: border-box;
        }
        button { 
            background: #007bff; 
            color: white; 
            padding: 12px 24px; 
            border: none; 
            border-radius: 4px; 
            cursor: pointer; 
            font-size: 16px;
        }
        button:hover {
            background: #0056b3;
        }
    </style>
</head>
<body>
    <div class="container">
        <h1>πŸ“ Dynamic Form Handler with Database</h1>
        <p>Submit the form below to see server-side processing and DynamoDB storage:</p>
        
        {formResults}
        
        <form method="GET">
            <div class="form-group">
                <label for="name">Name:</label>
                <input type="text" id="name" name="name" placeholder="Enter your name" required>
            </div>
            
            <div class="form-group">
                <label for="location">Location:</label>
                <input type="text" id="location" name="location" placeholder="Enter your location" required>
            </div>
            
            <div class="form-group">
                <label for="email">Email:</label>
                <input type="email" id="email" name="email" placeholder="Enter your email">
            </div>
            
            <div class="form-group">
                <label for="category">Category:</label>
                <select id="category" name="category">
                    <option value="">Select a category</option>
                    <option value="general">General Inquiry</option>
                    <option value="support">Support Request</option>
                    <option value="feedback">Feedback</option>
                </select>
            </div>
            
            <button type="submit">Submit Form</button>
        </form>
        
        <hr style="margin: 30px 0;">
        
        {table}
        
    </div>
</body>
</html>

5.2 Create index.js

const fs = require('fs');
const path = require('path');

// Read HTML file at startup
const html = fs.readFileSync(path.join(__dirname, 'index.html'), { encoding: 'utf8' });

// AWS SDK v3 imports
const { DynamoDBClient } = require('@aws-sdk/client-dynamodb');
const { DynamoDBDocumentClient, PutCommand, ScanCommand } = require('@aws-sdk/lib-dynamodb');

// Create DynamoDB client
const client = new DynamoDBClient({ region: process.env.AWS_REGION || 'us-east-1' });
const dynamo = DynamoDBDocumentClient.from(client);

exports.handler = async (event, context) => {
    try {
        console.log('πŸš€ Handler started');
        console.log('Event:', JSON.stringify(event, null, 2));
        
        // First, get existing records from DynamoDB
        let tableQuery = null;
        try {
            console.log('πŸ” Scanning DynamoDB table...');
            
            const scanCommand = new ScanCommand({
                TableName: "formStore"
            });
            
            tableQuery = await dynamo.send(scanCommand);
            console.log('βœ… DynamoDB Scan Success!');
            console.log('πŸ“Š Records found:', tableQuery.Count);
            
        } catch (scanError) {
            console.error('❌ DynamoDB Scan Error:', scanError);
            tableQuery = { Items: [] };
        }
        
        // Process form data and save to DynamoDB if present
        if (event.queryStringParameters && Object.keys(event.queryStringParameters).length > 0) {
            try {
                console.log('πŸ’Ύ Saving new form data...');
                
                const putCommand = new PutCommand({
                    TableName: "formStore",
                    Item: {
                        PK: "form",
                        SK: context.awsRequestId,
                        timestamp: new Date().toISOString(),
                        sourceIP: event.requestContext?.http?.sourceIp || 'unknown',
                        userAgent: event.headers?.['user-agent'] || 'unknown',
                        form: event.queryStringParameters
                    }
                });
                
                await dynamo.send(putCommand);
                console.log('βœ… Successfully saved to DynamoDB:', context.awsRequestId);
                
                // Refresh the table data after inserting new record
                const refreshScanCommand = new ScanCommand({
                    TableName: "formStore"
                });
                tableQuery = await dynamo.send(refreshScanCommand);
                console.log('πŸ”„ Refreshed data, new count:', tableQuery.Count);
                
            } catch (dbError) {
                console.error('❌ DynamoDB Put Error:', dbError);
            }
        }
        
        // Apply dynamic functions
        console.log('πŸ”§ Processing HTML template...');
        let modifiedHTML = dynamicForm(html, event.queryStringParameters);
        modifiedHTML = dynamictable(modifiedHTML, tableQuery);
        
        console.log('βœ… HTML processing complete');
        
        const response = {
            statusCode: 200,
            headers: {
                'Content-Type': 'text/html; charset=utf-8',
                'Cache-Control': 'no-cache'
            },
            body: modifiedHTML,
        };
        
        return response;
        
    } catch (error) {
        console.error('πŸ’₯ Handler Error:', error);
        return {
            statusCode: 500,
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ 
                error: 'Internal Server Error', 
                message: error.message,
                requestId: context.awsRequestId 
            })
        };
    }
};

function dynamicForm(html, queryStringParameters) {
    let formResults = '';
    
    if (queryStringParameters && Object.keys(queryStringParameters).length > 0) {
        formResults = '<div class="result"><h4>βœ… Form Submission Saved Successfully!</h4><ul>';
        
        Object.entries(queryStringParameters).forEach(([key, value]) => {
            if (value && value.trim() !== '') {
                formResults += `<li><strong>${key}:</strong> ${value}</li>`;
            }
        });
        
        formResults += '</ul><p><em>Data has been stored in DynamoDB table "formStore"</em></p></div>';
        
    } else {
        formResults = '<div class="result" style="background: #fff3cd; border-color: #ffeaa7; color: #856404;"><h4>ℹ️ Ready to Process Form Data</h4><p>Fill out the form below to see dynamic processing in action!</p></div>';
    }
    
    return html.replace('{formResults}', formResults);
}

function dynamictable(html, tableQuery) {
    console.log('πŸ—οΈ Building table HTML...');
    
    let table = "";
    
    if (tableQuery && tableQuery.Items && tableQuery.Items.length > 0) {
        console.log('βœ… Found items, building table...');
        
        // Create a proper HTML table
        table = `
        <div style="overflow-x: auto; margin: 15px 0; border: 2px solid #007bff; border-radius: 8px; padding: 15px; background: #f8f9fa;">
            <h5 style="color: #007bff; margin-top: 0;">πŸ“Š Form Submissions Database</h5>
            <table style="width: 100%; border-collapse: collapse; margin: 10px 0; font-size: 14px; background: white;">
                <thead>
                    <tr style="background-color: #007bff; color: white;">
                        <th style="border: 1px solid #ddd; padding: 12px; text-align: left;">Submission ID</th>
                        <th style="border: 1px solid #ddd; padding: 12px; text-align: left;">Timestamp</th>
                        <th style="border: 1px solid #ddd; padding: 12px; text-align: left;">Name</th>
                        <th style="border: 1px solid #ddd; padding: 12px; text-align: left;">Location</th>
                        <th style="border: 1px solid #ddd; padding: 12px; text-align: left;">Details</th>
                    </tr>
                </thead>
                <tbody>
        `;
        
        for (let i = 0; i < tableQuery.Items.length; i++) {
            const item = tableQuery.Items[i];
            const formData = item.form || {};
            
            table += `
                <tr style="background-color: ${i % 2 === 0 ? '#ffffff' : '#f9f9f9'};">
                    <td style="border: 1px solid #ddd; padding: 8px; font-family: monospace; font-size: 10px;">${(item.SK || 'N/A').substring(0, 8)}...</td>
                    <td style="border: 1px solid #ddd; padding: 8px;">${item.timestamp ? new Date(item.timestamp).toLocaleString() : 'N/A'}</td>
                    <td style="border: 1px solid #ddd; padding: 8px;"><strong>${formData.name || 'N/A'}</strong></td>
                    <td style="border: 1px solid #ddd; padding: 8px;">${formData.location || 'N/A'}</td>
                    <td style="border: 1px solid #ddd; padding: 8px;">
                        <details>
                            <summary style="cursor: pointer; color: #007bff;">View All</summary>
                            <pre style="margin: 5px 0; font-size: 11px; background: #f8f9fa; padding: 5px; border-radius: 3px;">${JSON.stringify(formData, null, 2)}</pre>
                        </details>
                    </td>
                </tr>
            `;
        }
        
        table += `
                </tbody>
            </table>
            <p style="color: #6c757d; font-size: 12px; margin-bottom: 0;"><em>πŸ“Š Total records: ${tableQuery.Items.length} | Last updated: ${new Date().toLocaleString()}</em></p>
        </div>
        `;
        
        console.log('βœ… Table HTML built successfully');
        
    } else {
        console.log('⚠️ No items found');
        table = '<div style="padding: 20px; background: #f8f9fa; border-radius: 5px; text-align: center; color: #6c757d; border: 2px dashed #dee2e6;"><p>πŸ“‹ No records found in DynamoDB table.</p><p><em>Submit the form above to create your first record!</em></p></div>';
    }
    
    return html.replace("{table}", `<h4>πŸ“Š DynamoDB Records:</h4>${table}`);
}

Step 6: Deploy Code to Lambda

  1. Create Deployment Package

    • Create a folder named lambda-function.
    • Add both index.js and index.html to the folder.
    • Select both files and create a ZIP archive.
  2. Upload to Lambda

    • Go to your Lambda function in the AWS Console.
    • In the Code source section, click Upload from β†’ .zip file.
  3. Deploy

    • Upload your ZIP file.
    • Click Deploy.

Step 7: Test the Application

  1. Access Function URL

    • Use the Function URL created in your Lambda settings.
    • Open the URL in your web browser.
  2. Test Form Submission

    • Fill out the form with sample data.
    • Click Submit Form.
    • Verify the data appears in the results table on the page.
  3. Verify DynamoDB

    • Go to the DynamoDB Console.
    • Click Explore table items.
    • Confirm your submissions are stored in the formStore table.
    • Check that the database table shows the new record

πŸ“ Project Structure

lambda-function/
β”œβ”€β”€ index.js          # Lambda function logic (Node.js)
β”œβ”€β”€ index.html        # Frontend template
└── README.md         # Documentation

πŸ§ͺ Testing

Manual Testing

  • Form Submission: Fill out the form and submit to ensure data is captured.
  • Data Persistence: Refresh the page to see if stored records appear in the table.
  • Error Handling: Submit an empty form to test client/server-side validation.

CloudWatch Logs

  • Monitor Logs: Open the CloudWatch console to track execution.
  • Identify Issues: Look specifically for success confirmations or error stack traces in the log streams.

πŸ”§ Troubleshooting

Common Issues & Solutions

🚫 Permission Denied Error

  • Error: AccessDeniedException: User is not authorized to perform: dynamodb:Scan
  • Solution: Go to the IAM Role attached to your Lambda and add a policy that allows dynamodb:Scan and dynamodb:PutItem.

🌐 Function URL Not Working

  • Verify: Ensure the Function URL is generated in the Lambda configuration.
  • Auth Type: Check that the Authorization type is set to NONE for public access.
  • Connectivity: Try accessing the URL from an incognito browser window.

πŸ“Š Records Not Displaying

  • Permissions: Confirm the IAM role includes dynamodb:Scan.
  • Template Check: Ensure your index.html contains the {table} placeholder for the Lambda function to inject data.
  • Logs: Check CloudWatch for specific scan failure errors.

πŸ’Ύ Form Data Not Saving

  • Permissions: Confirm the IAM role includes dynamodb:PutItem.
  • Table Name: Verify the table name in your code matches your DynamoDB table exactly (e.g., formStore).
  • PUT Errors: Review logs for validation exceptions or throughput issues.

Debug Steps

  1. Check CloudWatch Logs for detailed error messages.
  2. Verify IAM Permissions in the IAM console.
  3. Test DynamoDB Access directly using the AWS Console or CLI.
  4. Validate the HTML Template contains all required placeholders.

✨ Features

  • πŸ“ Dynamic Form Processing: Real-time form submission handling.
  • πŸ’Ύ Database Integration: Automatic storage in DynamoDB.
  • πŸ“Š Live Data Display: Shows existing records in a formatted table.
  • πŸ”„ Real-time Updates: Table refreshes after new submissions.
  • πŸ“± Responsive Design: Works on desktop and mobile devices.
  • πŸ›‘οΈ Error Handling: Graceful error catching and logging.
  • 🎨 Modern UI: Clean, professional interface.

πŸ”’ Security Considerations

  • IAM Permissions: Always follow the principle of least privilege.
  • Input Validation: Validate form inputs on both client and server side.
  • CORS Configuration: Configure CORS settings as needed for your domain.
  • Authentication: Consider adding AWS Cognito for production environments.
  • Rate Limiting: Implement limits to prevent API abuse or high costs.
  • Data Encryption: Enable encryption at rest for DynamoDB.

πŸ“š Additional Resources


πŸ“Έ Project Screenshots

Application Interface

image

Dynamic form with real-time validation and modern UI

Form Submission Success

image

Confirmation message after successful form submission

Database Records Display

image

Live display of all form submissions from DynamoDB

AWS Lambda Function

image

Lambda function code and configuration in AWS Console

DynamoDB Table

image

Form data stored in DynamoDB with composite keys

CloudWatch Logs

image

Function execution logs showing successful operations


Built with ❀️ using AWS Lambda, DynamoDB, and Node.js


About

AWS Lambda HTTP Form Handler Tutorial | Step-by-step guide for building a serverless form handler with AWS Lambda | Store submissions in DynamoDB | Real-time results display | Includes IAM setup & troubleshooting | Ideal for AWS and serverless development beginners

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors