DEV Community

Cover image for How to Build a Serverless Web Application
Ritik Banger
Ritik Banger

Posted on

How to Build a Serverless Web Application

Ah, serverless computing - the buzzword that's been taking the tech world by storm. But what exactly is it, and why should you care? Simply put, serverless computing allows you to build and run applications without worrying about provisioning or managing servers. It's like having an army of invisible servers at your beck and call, ready to handle your application's logic whenever it's needed.

One of the coolest use cases for serverless technology is building web applications. Imagine a world where you can create a website without having to worry about scaling, patching, or maintaining servers. Sounds too good to be true, right? Well, buckle up, because that's exactly what we're going to do in this article.

Serverless is a cloud computing execution model where the cloud provider dynamically manages the allocation and provisioning of servers. With serverless, you don't have to worry about provisioning, scaling, or managing servers yourself. Instead, you deploy your code, and the cloud provider handles the server management and execution for you.

In a serverless architecture, your application's code runs in stateless compute containers that are event-triggered, ephemeral (they only run when needed and shut down automatically after use), and fully managed by the cloud provider. These containers, called "functions" in the serverless world, are typically triggered by events such as HTTP requests, database events, queue services, monitoring alerts, or other custom triggers.

The key characteristics of serverless computing include:

  1. No Server Management: You don't have to provision, scale, or manage any servers. The cloud provider handles all the infrastructure management for you.

  2. Event-Driven: Functions are triggered by specific events or requests, rather than running continuously.

  3. Automatic Scaling: The cloud provider automatically allocates the necessary resources to handle the incoming workload, scaling out seamlessly as needed.

  4. Pay-per-Use Billing: You only pay for the compute time your functions consume when they are running, plus any other cloud services used. This can result in significant cost savings compared to maintaining always-on servers.

  5. High Availability and Fault Tolerance: Cloud providers ensure high availability and fault tolerance for serverless functions across multiple availability zones.

Popular serverless compute services include AWS Lambda, Google Cloud Functions, Azure Functions, and IBM Cloud Functions. These services allow you to deploy and run your code without provisioning or managing servers, enabling you to focus on writing application logic instead of infrastructure management.

Serverless architectures are well-suited for various workloads, including web applications, APIs, data processing, IoT backends, and more. They can help reduce operational overhead, increase scalability, and potentially lower costs for certain types of applications.

Setting the Stage

Before we dive into the code, let's set up our development environment. You'll need an AWS account and the AWS CLI installed on your machine. We'll also be using Node.js and the Serverless Framework, a popular open-source tool that makes building and deploying serverless applications a breeze.

First, install the Serverless Framework globally:

npm install -g serverless
Enter fullscreen mode Exit fullscreen mode

Next, create a new directory for your project and navigate to it:

mkdir serverless-web-app
cd serverless-web-app
Enter fullscreen mode Exit fullscreen mode

Initialize a new Serverless project:

serverless create --template aws-nodejs --path my-service
Enter fullscreen mode Exit fullscreen mode

This will create a new directory my-service with a basic Lambda function and other boilerplate files.

Building the Web Application

Our serverless web application will consist of two main components: a static website hosted on AWS S3, and a Lambda function to handle server-side logic. Let's start with the static website.

Create a new directory called website in your project's root directory, and add an index.html file with some basic HTML:

<!-- website/index.html -->
<!DOCTYPE html>
<html>
  <head>
    <title>Serverless Web App</title>
  </head>
  <body>
    <h1>Welcome to my Serverless Web App!</h1>
    <p>This is a static website hosted on AWS S3.</p>
  </body>
</html>
Enter fullscreen mode Exit fullscreen mode

Now, let's configure our Serverless project to deploy this website to an S3 bucket. Open serverless.yml and add the following resources:

# serverless.yml
service: serverless-web-app

provider:
  name: aws
  runtime: nodejs18.x

resources:
  Resources:
    WebsiteBucket:
      Type: AWS::S3::Bucket
      Properties:
        BucketName: ${self:service}-${opt:stage, self:provider.stage}
        WebsiteConfiguration:
          IndexDocument: index.html

# ... (existing Lambda function configuration)
Enter fullscreen mode Exit fullscreen mode

This will create an S3 bucket with the name serverless-web-app-[stage], where [stage] is the deployment stage (e.g., dev, prod). The bucket will be configured to serve the index.html file as the website index.

To deploy the website, run:

serverless deploy
Enter fullscreen mode Exit fullscreen mode

This will create the S3 bucket and upload the index.html file. The CLI output will include the website URL, which you can visit in your browser to see the static website.

Adding Server-Side Logic with Lambda

Now that we have our static website set up, let's add some server-side logic using AWS Lambda. We'll build a simple "contact form" functionality, where users can submit their name and email, and we'll save the data to a DynamoDB table.

First, we need to create the DynamoDB table. Add the following resource to serverless.yml:

# serverless.yml
resources:
  Resources:
    # ... (existing resources)
    ContactsTable:
      Type: AWS::DynamoDB::Table
      Properties:
        TableName: ${self:service}-contacts-${opt:stage, self:provider.stage}
        AttributeDefinitions:
          - AttributeName: id
            AttributeType: S
        KeySchema:
          - AttributeName: id
            KeyType: HASH
        BillingMode: PAY_PER_REQUEST
Enter fullscreen mode Exit fullscreen mode

This will create a DynamoDB table called serverless-web-app-contacts-[stage] with a hash key id.

Next, let's create the Lambda function that will handle the contact form submissions. In the my-service directory, create a new file called handler.js with the following code:

// handler.js
import AWS from 'aws-sdk';

const dynamoDb = new AWS.DynamoDB.DocumentClient();

const TABLE_NAME = process.env.CONTACTS_TABLE;

export const handler = async (event) => {
  const { name, email } = JSON.parse(event.body);

  const params = {
    TableName: TABLE_NAME,
    Item: {
      id: `contact-${Date.now()}`,
      name,
      email,
    },
  };

  try {
    await dynamoDb.put(params).promise();
    return {
      statusCode: 200,
      body: JSON.stringify({ message: 'Contact form submitted successfully' }),
    };
  } catch (err) {
    console.error(err);
    return {
      statusCode: 500,
      body: JSON.stringify({ message: 'Error submitting contact form' }),
    };
  }
};
Enter fullscreen mode Exit fullscreen mode

This Lambda function expects a JSON payload with name and email properties in the event body. It generates a unique ID for the contact, and stores the contact data in the DynamoDB table.

Finally, we need to configure the Lambda function in serverless.yml:

# serverless.yml
functions:
  app:
    handler: handler.handler
    events:
      - http:
          path: submit-contact
          method: post
          cors: true
    environment:
      CONTACTS_TABLE: ${self:service}-contacts-${opt:stage, self:provider.stage}
Enter fullscreen mode Exit fullscreen mode

This sets up an HTTP endpoint (/submit-contact) that will trigger the Lambda function when a POST request is made. The function will have access to the DynamoDB table name through the CONTACTS_TABLE environment variable.

To deploy the Lambda function and DynamoDB table, run:

serverless deploy
Enter fullscreen mode Exit fullscreen mode

The complete severless.yml file looks like this:

service: serverless-web-app

provider:
  name: aws
  runtime: nodejs18.x

resources:
  Resources:
    WebsiteBucket:
      Type: AWS::S3::Bucket
      Properties:
        BucketName: ${self:service}-${opt:stage, self:provider.stage}
        WebsiteConfiguration:
          IndexDocument: index.html
    ContactsTable:
      Type: AWS::DynamoDB::Table
      Properties:
        TableName: ${self:service}-contacts-${opt:stage, self:provider.stage}
        AttributeDefinitions:
          - AttributeName: id
            AttributeType: S
        KeySchema:
          - AttributeName: id
            KeyType: HASH
        BillingMode: PAY_PER_REQUEST

functions:
  app:
    handler: handler.handler
    events:
      - http:
          path: submit-contact
          method: post
          cors: true
    environment:
      CONTACTS_TABLE: ${self:service}-contacts-${opt:stage, self:provider.stage}
Enter fullscreen mode Exit fullscreen mode

Connecting the Frontend

With the backend logic in place, let's update the frontend to allow users to submit the contact form. Open website/index.html and add a form:

<!-- website/index.html -->
<!DOCTYPE html>
<html>
  <head>
    <title>Serverless Web App</title>
  </head>
  <body>
    <h1>Welcome to my Serverless Web App!</h1>
    <p>This is a static website hosted on AWS S3.</p>

    <h2>Contact Us</h2>
    <form id="contact-form">
      <label for="name">Name:</label>
      <input type="text" id="name" name="name" required />
      <br />
      <label for="email">Email:</label>
      <input type="email" id="email" name="email" required />
      <br />
      <button type="submit">Submit</button>
    </form>

    <script>
      const form = document.getElementById('contact-form');
      form.addEventListener('submit', async (event) => {
        event.preventDefault();

        const name = document.getElementById('name').value;
        const email = document.getElementById('email').value;

        const response = await fetch('/submit-contact', {
          method: 'POST',
          headers: {
            'Content-Type': 'application/json',
          },
          body: JSON.stringify({ name, email }),
        });

        const data = await response.json();
        alert(data.message);
        form.reset();
      });
    </script>
  </body>
</html>
Enter fullscreen mode Exit fullscreen mode

This adds a simple contact form to the HTML, and includes a JavaScript script that sends a POST request to the /submit-contact endpoint (which is the Lambda function we deployed earlier) when the form is submitted.

To deploy the updated website, run:

serverless client deploy
Enter fullscreen mode Exit fullscreen mode

This will upload the index.html file to the S3 bucket again.

Now, if you visit the website URL and submit the contact form, you should see a success message. You can also check the DynamoDB table in the AWS console to see the submitted contact data.

Wrapping Up

And there you have it, folks! We've built a serverless web application using AWS Lambda, S3, and DynamoDB. We've covered setting up the development environment, deploying a static website, creating a Lambda function for server-side logic, and connecting the frontend to the backend.

Serverless architectures offer numerous benefits, including scalability, cost-efficiency, and reduced operational overhead. By leveraging services like AWS Lambda, you can focus on writing code and building features, without worrying about server management. It's like having a team of hardworking elves handling the tedious infrastructure tasks for you.

Of course, this is just a simple example, and real-world serverless applications can become much more complex. But hey, we all have to start somewhere, right? The principles and services demonstrated in this article provide a solid foundation for building more advanced serverless web applications.

So, what are you waiting for? Embrace the serverless revolution and start building your next big idea today!

Top comments (12)

Collapse
 
capsule profile image
Thibaut Allender

“and a Lambda function to handle server-side logic”. You mean the serverless-side logic? Oh wait. It’s all cool, hyped and fancy but really you’re just using someone else’s server instead of your own, which has been the case with shared hosting since, well, Geocities.

Collapse
 
ritikbanger profile image
Ritik Banger

Serverless architecture abstracts away server management, allowing developers to focus solely on writing code without worrying about infrastructure. While it utilizes cloud servers, the emphasis is on the "serverless" model, where developers only pay for what they use and scale effortlessly. It does not mean that there is no servers.

Collapse
 
capsule profile image
Thibaut Allender

You didn't get the sarcasm but that's not a reason to ask ChatGPT what serverless really means and paste it here.

Thread Thread
 
ritikbanger profile image
Ritik Banger

LOL. No AI assistance needed for this one. That comment was all human-crafted.

Thread Thread
 
capsule profile image
Thibaut Allender

Sure, if that makes you feel better about using it to write the article…

Thread Thread
 
ritikbanger profile image
Ritik Banger

Dear Thibaut, for the past five years, I've been crafting articles across various websites. If you're unsure about how to write an article, that's perfectly fine – learning is always an option. However, if you suspect this article was generated by AI, feel free to run it through an AI detector and you will know why you need to grow your mindset.

Thread Thread
 
capsule profile image
Comment marked as low quality/non-constructive by the community. View Code of Conduct
Info Comment hidden by post author - thread only accessible via permalink
Thibaut Allender • Edited

Dude, one year ago you couldn't write a 2 sentences comment without 3 grammar errors and now you're the prodigal son of articles? Give us a break. I don't need an AI detector to do that, my integrated bullshit detector (aka common sense) already went through the roof. i.e. you're telling us the same person wrote dev.to/ritikbanger/how-to-write-gi... and this article? It's insulting to think we're stupid enough to believe so. Bye now.

Collapse
 
610470416 profile image
NotFound404

AWS service is not backed by servers ?
How evil it is to call this serverless.

Collapse
 
ritikbanger profile image
Ritik Banger

Although AWS services are indeed powered by servers, the term "serverless" in this context means developers can build and deploy applications without managing server infrastructure directly. It's a convenience term emphasizing the abstraction of server management tasks, enabling a more streamlined development process.

Collapse
 
ben10 profile image
Ben Honda

Good read. One question I have - what kind of URL is outputted when we deploy the S3 bucket? Is it a “s3.website”-type url, or a special serverless url? The reason I ask is I’m confused how we are able to submit the form to ‘/submit-contact’ and don’t have to use a fully qualified domain or lambda function URL. Thanks for the article

Collapse
 
ritikbanger profile image
Ritik Banger

When you deploy the S3 bucket for your serverless web application, the URL typically takes the form of "bucket-name.s3-website-region.amazonaws.com." This is known as a "static website hosting" URL, specific to S3 hosting.

Regarding submitting the form to '/submit-contact' without using a fully qualified domain or Lambda function URL, in a typical serverless setup with AWS, you would likely employ API Gateway to create a custom RESTful API endpoint like '/submit-contact'. This API Gateway endpoint would be configured to trigger your Lambda function directly. So, although the endpoint appears to be relative, behind the scenes, API Gateway is handling the routing to the appropriate Lambda function.

In the current scenerio, you can see that we have created this endpoint.

functions:
  app:
    handler: handler.handler
    events:
      - http:
          path: submit-contact
          method: post
          cors: true
    environment:
      CONTACTS_TABLE: ${self:service}-contacts-${opt:stage, self:provider.stage}
Enter fullscreen mode Exit fullscreen mode
Collapse
 
c9hp profile image
C9 • Edited

Serverless is just a joke.

Some comments may only be visible to logged-in visitors. Sign in to view all comments. Some comments have been hidden by the post's author - find out more