Skip to main content

Deploying a Scalable Serverless API with AWS CloudFormation

Hands-On Lab


Photo of

Training Architect





In this live AWS environment we will deploy a scaleable serverless API using CloudFormation. Specifically, we'll deploy our very own API, using a pre-configured CloudFormation template, with API Gateway, AWS Lambda, and DynamoDB. Then we'll review the CloudFormation template and test the API. When we're done, you'll understand how CloudFormation can be used effectively to describe entire architectures, without having to manually configure things inside of the AWS console or with other AWS tools. Grab the CloudFormation template over here.

What are Hands-On Labs?

Hands-On Labs are scenario-based learning environments where learners can practice without consequences. Don't compromise a system or waste money on expensive downloads. Practice real-world skills without the real-world risk, no assembly required.

Deploying a Scalable Serverless API with AWS CloudFormation

In this lab, we will deploy a scalable serverless API using CloudFormation. Specifically, we'll deploy our very own API, using a pre-configured CloudFormation template, with API Gateway, AWS Lambda, and DynamoDB. Then, we'll review the CloudFormation template and test the API. When we're done, we'll have an understanding of how CloudFormation can be used effectively to describe entire architectures, without having to manually configure things inside of the AWS console or with other AWS tools.

For this scenario, we have a music agency that needs to store some song information, as well as retrieve the songs from the API. Once we've created everything using CloudFormation, we'll add some data to our DynamoDB table and test it in a web browser.

To begin, log in to our lab environment as cloud_user with the password provided on the lab page. Make sure you are using us-east-1 (N. Virginia) as your region throughout the lab.

Under AWS services at the top, type "CloudFormation." It's also in the All services list (you can find it there by clicking All services and clicking CloudFormation under the Management & Governance header).

There may already be a CloudFormation stack listed once you get to the Stacks page — we don't need to worry about this one, as this is a side effect of how we create labs. We can just leave it be.

Creating Your CloudFormation Stack

Now, open a new browser tab and access the premade CloudFormation template here. Select all and copy everything.

Head back to our CloudFormation console page. Click Create Stack. Notice that we can either design a template ourselves or choose from sample templates. In this case, we're going to use the CloudFormation template we just copied. Click Design template. Click the Template tab at the bottom, and paste the template into that section. It's important to make sure we're pasting the entire template into the Template section instead of the Components section.

Once we've pasted the template, click the double-arrow refresh icon at the top. Now, we should see a visualization of our entire CloudFormation template. Scroll in and take a look at the resources that should be created by CloudFormation to get an understanding of what's happening here.

Note, however, if we move anything in the visualization, it will change the template. Scroll back out to make sure the visualization looks like it originally did — if not, paste the template back into the Template section and refresh the visualization to make sure it looks alright.

Now, check the checkbox in the top menu to validate the template. We should see a "Template is valid." message briefly at the top of the screen.

Before we upload our template, let's take a look at what resources we'll be creating. Scroll all the way to the top of the template.

First, we have our DynamoDB table. We're going to create this resource, which will have a key for the artist and a key for the song title. Our artist key has a KeyType of HASH (also known as the partition key), and the song title has a KeyType of RANGE (also known as the sort key).

These resources have a standard provisioned capacity, with five units each for read capacity and write capacity. If we wanted to make this API more scalable, we would want to set up auto-scaling here. For this lab, we're just going to leave it as-is.

The table is named PrometheonMusic, which is the name of the company we made up for this lab.

Next, we see the template will create an execution role for our Lambda functions, which will allow our Lambda functions to have access to DynamoDB. We're actually giving them access to do anything on our DynamoDB table. We can see here there are some statements that make up the policy, including doing things with the logs they'll be outputting just so we can debug them later if we need to, as well as just making sure they're allowed to take certain actions on DynamoDB, such as adding or reading information from the table.

After we create that role, we're going to create a few different Lambda functions inside this template. We're going to create the ListLambdaFunction, which will list all the contents of the table. Keep in mind, in an actual production environment, it would be rare to want to list everything inside a DynamoDB table since that isn't an efficient operation for a NoSQL system like DynamoDB. What we're doing here is creating a Lambda function that's going to get its code from an S3 bucket we've already created for this lab. Once it gets the code, it creates a Lambda function called list with a Handler of list.list. Then, it sets up a role for this Lambda function, which is the LambdaExecutionRole. Additionally, we want to make sure the ListLambdaFunction depends on the LambdaExecutionRole.

Next, we have a GetLambdaFunction, which looks basically identical — it just has a different name and takes a different .zip file to have a different function. The main difference is we're going to use one of these functions to list everything inside the DynamoDB table, and we're going to use the other function to get a particular item in the DynamoDB table efficiently.

Scroll down to see the PrometheonApi we're creating. The name we're giving it is prometheon, which is what we'll have in our URL path to interact with these API gateway endpoints.

Next, we'll see we've set a permission on Lambda, which essentially allows it to get invoked by the API gateway. We're doing this for both ListLambdaFunction and GetLambdaFunction.

We'll also see further down we're creating an ApiDeployment, which is required to have a deployed API we can access.

Further down, we'll see we're creating a couple of different API Gateway resources:

  • PrometheonApiResourceRoot with a path of prometheon
  • PrometheonApiResourceId with a path of id (and a parent of the PrometheonApiResourceRoot)

This allows us to establish a URL path like prometheon/id. We'll take a closer look at this in a moment.

Now, we see our ApiMethodList, which can be accessed using the HttpMethod of GET. We also have ApiMethodGet, which will access the GetLambdaFunction instead of the ListLambdaFunction. The main difference here is we're going to have these GET methods assigned to different URL endpoints for our API gateway: One will be /prometheon, and the other will be /prometheon/id.

Now that we're done looking at some of this code, let's create the stack and see what it builds in the background inside CloudFormation. Click the upload icon at the top, which should upload it to S3. Click Next.

On the Specify Details page, give it a Stack name of sls-api and click Next. On the Options page, we'll leave everything as-is, so click Next. On the Review page, click the checkbox acknowledging that AWS CloudFormation might create IAM resources with custom names. Click Create.

In the CloudFormation console, we should see our API stack is now being created. (Note: If it doesn't appear or seems to be taking a while for the Status to change to CREATE_COMPLETE, click the refresh icon above the console.) Once it's created, we can click sls-api to see all the steps that happened in the creation process.

Reviewing Your CloudFormation Infrastructure

Now, let's take a look at what some of these resources look like. Click Services in the toolbar, and then right-click DynamoDB under Database to open it in a new browser tab. Then, right-click Lambda under Compute, and right-click API Gateway under Networking & Content Delivery. Alternatively, we could search for these sections in the search bar at the top.

On the API Gateway page, click prometheon under APIs in the sidebar. This is what we created inside CloudFormation, and we can see it has the following endpoints:

  • The root endpoint (/)
  • A /prometheon endpoint
    • A GET method beneath the /prometheon endpoint, which is configured with a LAMBDA_PROXY to interact with one of the Lambda functions
  • An /id endpoint

Click the Lambda Management Console browser tab we opened a minute ago, and we can see two different functions: get and list. If we go into one of them, we can see the actual code that's inside the function that makes it interact effectively with the DynamoDB table. In this case, we don't need to modify anything because it's already been loaded.

Click the DynamoDB browser tab and click Tables in the sidebar. Click the PrometheonMusic table in the list. Here, click the Items tab. One thing CloudFormation doesn't necessarily let us bring all the data into a DynamoDB table. We could write a custom Lambda function to load data into the table, which could work as long as we integrated it effectively with CloudFormation so that it knew when the Lambda function was done and when to start it. But for this lab, we're just going to click Create item and test out the API in action. Essentially, we've spun up an entirely blank slate for an API. When the Create item window appears, we should see some characteristics of the table: Artist and SongTitle.

For the Artist String, enter a value of Tom. For the SongTitle String, enter a value of FavSong. Click Save.

Now, our table should show this information.

Click back to the API Gateway browser tab, and let's see how we can access the data we just added to the table outside of the AWS environment, using the API gateway we created.

First, click Stages under prometheon in the sidebar, and click dev in the list. In the dev Stage Editor window, there will be an Invoke URL. Copy the URL and paste it in a new browser tab. Add /prometheon at the end of the URL. The browser will automatically make a GET request to the /prometheon endpoint when we hit Enter, meaning it's going to do the GET method, go to the Lambda function, and attempt to get a list of what we have inside the DynamoDB table. It might take a second to load everything, but we should see all the items inside the DynamoDB table. Let's verify it by adding one more item.

Click the DynamoDB browser tab, and click Create item. Now, for the Artist String, enter a value of Jane. For the SongTitle String, enter a value of CoolSong. Click Save.

Refresh the browser tab with our invoke URL in it, and the information we just added to the table should now show up.

Now, let's see how the additional endpoint, /prometheon/id, works. In the browser with the invoke URL, if we add /id to the URL right now, we'd get an error message because it isn't configured to specify a particular item.

We could, however, do a specific search, like if we were looking for Jane's "CoolSong." In the URL in the browser tab, after /prometheon/id, add the query string parameter:


When we hit Enter, we should get back the data we're looking for. This doesn't look too impressive, though, so let's add a bit more info.

Back in the DynamoDB browser tab, select the Jane row. Click Actions, and select Edit from the dropdown. Click the + by the SongTitle, click Append, and select String from the dropdown. For field, enter "supercoolinfo", and type in whatever you'd like as the value. (It can even just be a bunch of nonsense letters.) Click Save. We should see this added a new column, supercoolinfo, to our table.

Now, refresh our invoke URL browser tab with the same query string parameter, and we should see the data we just entered.

If we needed to shut this down, we can go back to the AWS console, click Services in the toolbar, and search for "CloudFormation." In the CloudFormation page, check the box next to our sls-api stack, click Actions at the top, click Delete Stack from the dropdown, and then click Yes, Delete. After the deletion process finishes, we won't be able to access the information if we try refreshing our browser search.


We've now seen the power of CloudFormation templates and how they can efficiently create resources in the background to spin up fairly complicated solutions inside AWS.

Congratulations on completing this lab!