Introduction

Serverless architecture eliminates the need for infrastructure management, allowing developers to focus on code rather than servers. In this guide, we will walk through the process of building a serverless web application using AWS services.

What is Serverless Architecture?

Serverless architecture allows applications to run without provisioning or managing servers. AWS provides services like:

  • AWS Lambda for executing backend logic.
  • Amazon API Gateway for handling HTTP requests.
  • Amazon DynamoDB as a NoSQL database.
  • Amazon S3 for static website hosting.
  • Amazon SNS for event-driven notifications.

Serverless Application Architecture on AWS

1. Hosting a Static Web Application

We use Amazon S3 to host a static website.

  1. Create an S3 bucket.
  2. Enable static website hosting in bucket properties.
  3. Upload the frontend files (HTML, CSS, JavaScript).
  4. Set bucket permissions for public access.

2. Setting Up API Gateway and Lambda

AWS API Gateway provides an interface for HTTP requests and routes them to AWS Lambda.

  1. Create an API Gateway with HTTP methods (GET, POST, etc.).
  2. Configure Lambda functions to process API requests.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    export const handler = async (event) => {
    let body;
    let statusCode = "200";
    const headers = {
    "Content-Type": "application/json",
    };

    try {
    console.log("Full event object:", JSON.stringify(event, null, 2));

    // Extract HTTP method and validate it
    const method = event.requestContext?.http?.method || event.httpMethod;
    if (!method) {
    throw new Error("HTTP method is missing in the event object.");
    }

    let tableName;
    if (method === "POST" && event.body) {
    // For POST, get TableName from the request body
    const requestBody = JSON.parse(event.body);
    tableName = requestBody.TableName;
    if (!tableName) {
    throw new Error("TableName is required in the request body.");
    }
    } else if (method === "GET") {
    // For GET, get TableName from the query string
    tableName = event.queryStringParameters?.TableName;
    if (!tableName) {
    throw new Error("TableName is required in the query string.");
    }
    }

    switch (method) {
    case "POST": {
    // Ensure required fields are present in the body for POST
    const requestBody = JSON.parse(event.body);
    if (!requestBody.Item) {
    throw new Error("Item is required in the request body.");
    }

    // Step 1: Get the current `nextid` from the record with `id = -1`
    const getParams = {
    TableName: tableName,
    Key: { id: -1 }, // Assuming `id` is a number
    };

    const { Item: metadata } = await dynamo.get(getParams);

    if (!metadata || !metadata.nextid) {
    throw new Error("Could not retrieve nextid from the metadata record (id = -1).");
    }

    const currentNextId = metadata.nextid;

    // Step 2: Increment `nextid` for the new item
    const newId = currentNextId;
    const updatedNextId = currentNextId + 1;

    // Step 3: Update the `nextid` in the metadata record
    const updateParams = {
    TableName: tableName,
    Key: { id: -1 },
    UpdateExpression: "SET nextid = :nextid",
    ExpressionAttributeValues: {
    ":nextid": updatedNextId,
    },
    };

    await dynamo.update(updateParams);

    // Step 4: Insert the new item with the assigned `id`
    const newItem = {
    ...requestBody.Item,
    id: newId, // Add the auto-incremented id
    };

    const putParams = {
    TableName: tableName,
    Item: newItem,
    };

    await dynamo.put(putParams);

    body = { message: "Item successfully created.", newItem };
    break;
    }
    case "GET":
    // Handle GET requests
    const scanParams = { TableName: tableName };
    const scanResult = await dynamo.scan(scanParams);
    body = scanResult;
    break;

    default:
    throw new Error(`Unsupported method "${method}"`);
    }
    } catch (err) {
    console.error("Error occurred:", err.message);
    statusCode = "400";
    body = { error: err.message };
    } finally {
    body = JSON.stringify(body);
    }

    return {
    statusCode,
    body,
    headers,
    };
    };
  3. Enable CORS in API Gateway to allow web access.
  4. Deploy the API and obtain the endpoint URL.

3. Database Integration with DynamoDB

Amazon DynamoDB is used as a NoSQL database for storing customer data.

  1. Create a DynamoDB table with a partition key id.
  2. Use AWS SDK in Lambda to interact with the database.
  3. Implement API Gateway methods for GET and POST requests.

4. Implementing a Notification System with SNS

AWS SNS allows sending notifications for database changes or file uploads.

  1. Create an SNS topic and subscribe an admin email.
  2. Configure Lambda functions to trigger SNS notifications.
  3. Set up S3 event notifications for file uploads.

Deployment and Testing

1. Testing API Gateway and Lambda

  • Use Postman or curl to send HTTP requests to the API Gateway endpoint.
  • Check CloudWatch logs for debugging Lambda execution.

2. Validating DynamoDB Data Storage

  • Use AWS Console to inspect DynamoDB records.
  • Run queries using the AWS CLI.

3. Ensuring Notification System Works

  • Upload a file to the S3 bucket and verify the email notification.
  • Add a record to DynamoDB and check SNS messages.

AWS Well-Architected Framework Considerations

Security

  • Use IAM roles and policies for access control.
  • Encrypt data at rest and in transit.

Reliability

  • Enable DynamoDB auto-scaling to handle variable loads.
  • Use multi-AZ deployment for resilience.

Performance Efficiency

  • Optimize Lambda function execution time to minimize costs.
  • Use CloudFront for caching and faster content delivery.

Cost Optimization

  • Configure S3 lifecycle policies for storage optimization.
  • Minimize Lambda execution time to reduce compute costs.

Video of Serverless Web Applications

Conclusion

Serverless architecture on AWS simplifies application deployment, reduces infrastructure costs, and scales dynamically. By integrating Lambda, API Gateway, DynamoDB, S3, and SNS, you can build a fully functional web application with minimal infrastructure overhead.


References