BlogTechnology and KnowHow

Serverless for Web Developers

Symphony logo
December 16th, 2021

Why should you read this?

If you are a web developer who wants to achieve faster time to market for new products and features, cut (almost eliminate) time to provision and manage infrastructure, minimize downtime and security risks, along with costs - this is the place for you. This article will teach you how easy it is to make a simple serverless app that is scalable and highly available. 

We will cover this topic using Serverless Framework for managing AWS services with a small amount of coding since you already know how to do that. Previous AWS experience or knowledge isn’t required for understanding this article.

What is Serverless?

Serverless characteristics:

Serverless is generally defined as an execution model in cloud computing where a cloud provider allocates and bills machine resources on demand. This means that you do not care about the underlying infrastructure, have flexible scaling and high availability while focusing on solving the problem at hand. You only care about the what, not the how

The Serverless Framework allows you to manage resources with ease and without any previous knowledge of the cloud platform (in our case, AWS).

How is web development different when using Serverless?

When we develop an API, we usually choose:

  • Programming language
  • Web framework
  • Database

When developing an API as a Serverless we choose:

  • Programming language
  • Cloud platform
  • Database

The list of services is usually more comprehensive in real-world scenarios (Message broker, email service, etc.), but for the sake of simplicity, we will stick with the aforementioned services. As you can see, cloud platforms can be considered a development framework, where you use cloud services as building blocks of your application. While having a vendor-locking as a potential downside, you substantially benefit from the well-connected ecosystem. You are not “reinventing the wheel”, but focusing on your business case.

Lambda function that handles REST request

AWS Lambda is what it ultimately promises - A LAMBDA, a.k.a., a function.


Lambda in a nutshell

Let's write some “code”

You can find an example code on GitHub to follow through, or write the code along the way.

Create folder structure

Update file ‘handler.js’ with the following code

module.exports.hello = async () => {

 return {

   statusCode: 200,

   body: 'Hello there!'



Code part - DONE.

Update file ‘serverless.yaml’ with the following configuration

service: myapp


frameworkVersion: '2'



 name: aws

 runtime: nodejs12.x




   handler: handler.hello

Configuration part - DONE.

  • service: the name of the service we are writing (it can contain multiple Lambda functions).
  • provider: the provider-specific configuration
  • functions: all the functions

In this example, the “hello” function handler is in “handler.hello” which means a file named “handler.js” and “hello” being the name of the exported property.

We can now execute sls deploy and have our first Lambda function created.

Right now, our Serverless environment looks like this:

At this point, we still haven't put the Lambda function we created into use. What Serverless framework did in the background:

  • Create myapp Stack - (Stack = collection of AWS resources)
  • Create IAM Role - Lambda runs under a specific execution role and communicates with the rest of the AWS services as an assigned execution role.
  • Create hello Lambda; By default, Lambda will create an execution IAM role with permissions to upload logs to Amazon CloudWatch Logs.

How can we use our Lambda function?

Since we want to create an HTTP endpoint, we need to subscribe our hello Lambda function to an HTTP event.

Create an HTTP trigger for our Lambda function



   handler: handler.hello


     - http:

         path: /hello

         method: get

Again: sls deploy

What did the “sls” do for us now:

  • Create an API Gateway resource
  • Expose HTTP API endpoint and invoke hello Lambda function on GET /hello request.
    • Adds permission to invoke our Lambda function

Create a database and endpoints to manipulate the data

Every web application works with some data. We will create the following endpoints:

  • POST /tasks - create a new task
  • GET /tasks/:id - get created tasks

When creating a database, we need to consider what kind of database we need. Next, since the Lambda function will consume the database, we should consider the risk of losing the Serverless characteristics due to the database being a bottleneck.

Our example does not require a relational database, so we choose DynamoDB.

Update configuration to create DynamoDB. Details of the DynamoDB configuration are a separate topic, and we will not cover it in this post. In short, the table will have an “id” that is a string as the primary key. You can find details about workload (provisioned throughput) here.




     Type: AWS::DynamoDB::Table


       TableName: tasksTable


         - AttributeName: id

           AttributeType: S


         - AttributeName: id

           KeyType: HASH


         ReadCapacityUnits: 1

         WriteCapacityUnits: 1

Lambda function (its underlying role) must have appropriate permissions for the DynamoDB table (no username/password/connection string in the configuration, just specific permissions) in order to use the DynamoDB table. When enabling permissions, try to be as conservative as possible. We do not want a role to have particular permission if it does not need it.

We will now add permissions for database access:




   - Effect: Allow


       - dynamodb:GetItem

       - dynamodb:PutItem

     Resource: !GetAtt TasksTable.Arn

!GetAtt logicalNameOfResource.attributeName is used to pull the “arn” (resource identifier) of the DynamoDB table we created.

Now we will create two lambdas to create and fetch records from the database.

module.exports.createTask = async (event) => {

 const id = new Date().getTime().toString();


 var params = {

   TableName: "tasksTable",

   Item: {


     data: JSON.parse(event.body),



 await documentClient.put(params).promise();

 return {

   statusCode: 201,

   headers: {

     Location: `/tasks/${id}`,





module.exports.getTaskById = async (event) => {

 const id =;

 var params = {

   TableName: "tasksTable",

   Key: {





 const item = await documentClient.get(params).promise();

 return {

   statusCode: 200,

   body: JSON.stringify(,



We also need a configuration to make this code a lambda function.




   handler: handler.createTask


     - http:

         path: /tasks

         method: post


   handler: handler.getTaskById


     - http:

         path: /tasks/{id}

         method: get

Aaaaand we sls deploy once again.

Now sls created:

  • DynamoDB table
  • createTask and getTaskById Lambdas
    • Permissions to write logs into the CloudWatch - implicit
    • Permissions to Get/Put items in DynamoDB table - explicit
  • Updated API Gateway with new handlers
    • Permissions for API Gateway to invoke new Lambdas

We can start creating records in the database and pulling those records using record ID.

And that's it! With zero effort in designing the infrastructure (VMs, networks, load balancers, etc.) we now have a scalable and highly available application that is billed per usage - this is the true power of serverless.

About the author

Robert Sebescen is a Solutions Architect working at our engineering hub in Novi Sad.

Robert’s strongest points are problem-solving skills and figuring out how stuff works. Most experience and most comfortable working with .NET Core framework on the backend and React on the front-end. Practices Scrum and considers all of its components equally important. As a solution architect, he did estimations for a few integration projects covering: scoping the functional and non-functional requirements, investigating the feasibility of the integration by analyzing platforms (and their APIs) that participate in the integration, defining a solution, and estimating required effort and workforce.