Crafting a Toy Backend Adventure with AWS CloudFormation, Lambda, and GitHub

Julia Yang
7 min readNov 27, 2023
Photo by Praveesh Palakeel on Unsplash

Hey there👋. As a beginner in the vast world of cloud computing, I found myself overwhelmed by the myriad of services and tools available. The idea of starting small helped me quickly grasp the gist of building backend infrastructure: my simple toy project harnessing AWS, Lambda functions, and GitHub turned out to be an exciting adventure that I’m eager to share with fellow beginners.

Setting the Stage

Before we dive into the technical details, let’s outline our toy project. We’ll create a basic backend that can handle simple calculations — addition and subtraction. (Github repository: https://github.com/juliayyy/sde101) This might seem trivial, but it’s a fantastic starting point for understanding the core concepts. I will (and recommend beginners do the same too) recreate the process in two iterations:

  • One with simply manual button-click operations on AWS console;
  • One with code-first approach;

Iteration One: Creating a backend with simply manual button-click operations on AWS console

The AWS Management Console provides a user-friendly interface for creating Lambda functions and API Gateway, allowing you to set up the backend infrastructure with just a few clicks. Here’s a simplified, step-by-step guide:

Step 1: Create Lambda Functions

  1. Navigate to the Lambda Service: Open the AWS Management Console -> In the “Find Services” search bar, type “Lambda” and select it.
  2. Create the Plus Function: Click on the “Create function” button -> Choose “Author from scratch” -> Fill in the basic information: Function name: plus; Runtime: Python 3.11 -> Scroll down to the function code section -> Replace the existing code with the following -> Click on “Deploy” to save your function.
import json

print('Loading function')

def lambda_handler(event, context):
result = float(event['queryStringParameters']['number1']) + float(event['queryStringParameters']['number2'])
print (result)
return result # Echo back the first key value

3. Create the Minus Function: Repeat the above steps to create a new Lambda function named minus, and in the function code section, use the following code -> Click on “Deploy” to save your function.

import json

print('Loading function')

def lambda_handler(event, context):
result = float(event['queryStringParameters']['number1']) - float(event['queryStringParameters']['number2'])
print (result)
return result # Echo back the first key value

Voila, we created the core of the calucaltor using the Lamdba function, however, the function is not available outside the cloud yet, to be able to access, we will need to set up API Gateway in the next step.

Step 2: Create API Gateway

  1. Navigate to the API Gateway Service on aws console;
  2. Create a New API: Click on the “Create API” button -> Choose “HTTP API” and click on “Build” under the HTTP API section.
  3. Configure Your API: Give your API a name, e.g., Calculator -> Click on “Add Integration” in the “Routes” section -> Choose “Lambda Function” as the integration type. -> Choose “Lambda Function” as the integration type -> Select the plus Lambda function for the plus route -> Repeat the process for the minus route, selecting the minus Lambda function.
  4. Deploy Your API: Click on the “Deployments” tab -> Click on the “Deployments” tab -> Once the deployment is complete, note down the URL provided.

Step 3: Test Your API

Now our function is callable from outside the cloud by using an “API”, we can test it with a API testing tool (like Postman ) on a web browser as below:

We have successfully created a simple calculation backend using Lambda functions and API Gateway on AWS, all through the console with just a few clicks. This provides a quick and accessible way for beginners to explore serverless computing and API development on AWS. Now we will proceed to the second iteration: to automate the above process using CloudFormation as a code-first approach.

Iteration Two: Creating the backend with CloudFormation

AWS CloudFormation allows us to define and provision AWS infrastructure as code. It simplifies the process of creating and managing AWS resources as we did in iteration one above. As a beginner, this was a game-changer for me, providing a structured way to organize my cloud resources.

Building blocks needed:

  • a github repo
  • a local IDE (Pycharm)
  • aws CloudFormation

Step 1: Create a new repository in your github account and clone to local IDE

  • Log in your github account→ create a new repository → Clone the repo
  • Open your local IDE (eg: Pycharm) → Git → Clone the repo to local

Step 2: Create an deploy.yml file to auto deploy the code changes from github to your CloudFormation

  • IDE → create a folder .github-> create a folder workflows->create a file deploy.yml; (reference) , example as below:
name: 'Deploy to AWS CloudFormation'

on:
push:
branches: [ main ]


jobs:
# This workflow contains a single job called "deploy"
deploy:
# The type of runner that the job will run on
runs-on: ubuntu-latest
# Steps represent a sequence of tasks that will be executed as part of the job
steps:
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
- name: Checkout code from master branch
uses: actions/checkout@v2

# Configure AWS Credential
- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: ${{ secrets.ACCESS_KEY }}
aws-secret-access-key: ${{ secrets.ACCESS_KEY_SECRET }}
aws-region: us-east-1

# Deploy to AWS CloudFormation
- name: Deploy to AWS CloudFormation
uses: aws-actions/aws-cloudformation-github-deploy@v1
with:
name: calculation
template: test-stack.json
no-fail-on-empty-changeset: "1"

Step 3: Create the test-stack.json to maintain the infrastructure code

Major building blocks include:

  • AWS::Lambda::Function
  • AWS::IAM::Role
  • AWS::ApiGatewayV2::Api
  • AWS::ApiGatewayV2::Integration
  • AWS::ApiGatewayV2::Route
  • AWS::Lambda::Permission
  • AWS::ApiGatewayV2::Stage

Template:

{
"AWSTemplateFormatVersion": "2010-09-09",
"Resources": {
"Plus": {
"Type": "AWS::Lambda::Function",
"Properties": {
"Code": {
"ZipFile": "import json\nprint('Loading function')\ndef lambda_handler(event, context):\n result = float(event['queryStringParameters']['number1']) + float(event['queryStringParameters']['number2'])\n print (result)\n return result # Echo back the first key value"
},
"Role": {
"Fn::GetAtt": [
"PlusRole",
"Arn"
]
},
"Runtime": "python3.11",
"Handler": "index.lambda_handler"
}
},
"PlusRole": {
"Type": "AWS::IAM::Role",
"Properties": {
"AssumeRolePolicyDocument": {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": [
"lambda.amazonaws.com"
]
},
"Action": [
"sts:AssumeRole"
]
}
]
},
"ManagedPolicyArns": [
"arn:aws:iam::aws:policy/AWSLambdaExecute"
]
}
},
"Minus": {
"Type": "AWS::Lambda::Function",
"Properties": {
"Code": {
"ZipFile": "import json\nprint('Loading function')\ndef lambda_handler(event, context):\n result = float(event['queryStringParameters']['number1']) - float(event['queryStringParameters']['number2'])\n print (result)\n return result # Echo back the first key value"
},
"Role": {
"Fn::GetAtt": [
"MinusRole",
"Arn"
]
},
"Runtime": "python3.11",
"Handler": "index.lambda_handler"
}
},
"MinusRole": {
"Type": "AWS::IAM::Role",
"Properties": {
"AssumeRolePolicyDocument": {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": [
"lambda.amazonaws.com"
]
},
"Action": [
"sts:AssumeRole"
]
}
]
},
"ManagedPolicyArns": [
"arn:aws:iam::aws:policy/AWSLambdaExecute"
]
}
},
"CalculationApi": {
"Type": "AWS::ApiGatewayV2::Api",
"Properties": {
"Name": "CalculationApi",
"ProtocolType": "HTTP"
}
},
"PlusIntegration": {
"Type": "AWS::ApiGatewayV2::Integration",
"Properties": {
"ApiId": {
"Ref": "CalculationApi"
},
"Description": "Lambda Integration",
"IntegrationType": "AWS_PROXY",
"IntegrationUri": {
"Fn::Join": [
"",
[
"arn:",
{
"Ref": "AWS::Partition"
},
":apigateway:",
{
"Ref": "AWS::Region"
},
":lambda:path/2015-03-31/functions/",
{
"Fn::GetAtt": [
"Plus",
"Arn"
]
},
"/invocations"
]
]
},
"IntegrationMethod": "POST",
"PayloadFormatVersion": "2.0"
}
},
"PlusRoute": {
"Type": "AWS::ApiGatewayV2::Route",
"Properties": {
"ApiId": {
"Ref": "CalculationApi"
},
"RouteKey": "ANY /plus",
"Target": {
"Fn::Join": [
"/",
[
"integrations",
{
"Ref": "PlusIntegration"
}
]
]
}
}
},
"MinusIntegration": {
"Type": "AWS::ApiGatewayV2::Integration",
"Properties": {
"ApiId": {
"Ref": "CalculationApi"
},
"Description": "Lambda Integration",
"IntegrationType": "AWS_PROXY",
"IntegrationUri": {
"Fn::Join": [
"",
[
"arn:",
{
"Ref": "AWS::Partition"
},
":apigateway:",
{
"Ref": "AWS::Region"
},
":lambda:path/2015-03-31/functions/",
{
"Fn::GetAtt": [
"Minus",
"Arn"
]
},
"/invocations"
]
]
},
"IntegrationMethod": "POST",
"PayloadFormatVersion": "2.0"
}
},
"MinusRoute": {
"Type": "AWS::ApiGatewayV2::Route",
"Properties": {
"ApiId": {
"Ref": "CalculationApi"
},
"RouteKey": "ANY /minus",
"Target": {
"Fn::Join": [
"/",
[
"integrations",
{
"Ref": "MinusIntegration"
}
]
]
}
}
},
"PlusAPIInvokePermission": {
"Type": "AWS::Lambda::Permission",
"Properties": {
"FunctionName": {
"Fn::GetAtt": [
"Plus",
"Arn"
]
},
"Action": "lambda:InvokeFunction",
"Principal": "apigateway.amazonaws.com",
"SourceArn": {
"Fn::Sub": "arn:${AWS::Partition}:execute-api:${AWS::Region}:${AWS::AccountId}:${CalculationApi}/*/*/plus"
}
}
},
"MinusAPIInvokePermission": {
"Type": "AWS::Lambda::Permission",
"Properties": {
"FunctionName": {
"Fn::GetAtt": [
"Minus",
"Arn"
]
},
"Action": "lambda:InvokeFunction",
"Principal": "apigateway.amazonaws.com",
"SourceArn": {
"Fn::Sub": "arn:${AWS::Partition}:execute-api:${AWS::Region}:${AWS::AccountId}:${CalculationApi}/*/*/minus"
}
}
},
"MyStage": {
"Type": "AWS::ApiGatewayV2::Stage",
"Properties": {
"StageName": "Prod",
"Description": "Prod Stage",
"AutoDeploy": true,
"ApiId": {
"Ref": "CalculationApi"
}
}
}
}
}

Step 4: Add AWS Credentials in Github Repo

  • github → Add AWS Credentials in Github Repo → Settings → Secrets → New repository secret → Enter the Name & Value for the Secret. reference link

Step 5: Test Your API

Now we can test it with a API testing tool (like Postman ) on a web browser as mentioned in Step 3 of Iteration One.

Voila, we’ve just built a simple calculation backend using AWS CloudFormation, Lambda functions, and GitHub. This project serves as a solid foundation for understanding key concepts in CI-CD process and cloud development.

Conclusion:

As a beginner, embarking on this project was an eye-opener. I learned the basics of infrastructure as code, serverless computing, and collaborative coding. The journey might seem challenging at first, but breaking it down into manageable steps makes the process both educational and enjoyable. I encourage fellow beginners to explore and experiment, as this is just the tip of the iceberg in the world of cloud computing.

Happy coding!

Github repository: https://github.com/juliayyy/sde101

--

--