# Project 19: Building and Deploying Serverless Architectures with AWS SAM and CI/CD (Step by Step Implementation)

### **Introduction:**

This project simplifies and streamlines the development and deployment of Serverless Architectures using AWS SAM (Serverless Application Model) and CI/CD practices. By combining the power of AWS SAM with a robust CI/CD pipeline, the project addresses common challenges in serverless development, such as efficient management of AWS resources, seamless integration with API Gateway and DynamoDB, and the automation of deployment processes. With SamServeCI, developers can enjoy a smooth and automated workflow, ensuring rapid iteration, reliable deployments, and optimal performance of serverless applications.

### **Technologies Used in This Project:**

1. **AWS SAM (Serverless Application Model):** AWS SAM is utilized to define and deploy serverless applications on AWS. It simplifies the process of defining serverless resources such as AWS Lambda functions, API Gateway APIs, and DynamoDB tables. In this project, AWS SAM provides a standardized and efficient way to define the serverless architecture.
    
2. **AWS Lambda (Python):** AWS Lambda, with Python as the runtime, is employed for executing serverless functions. Python offers a versatile and developer-friendly environment, making it an excellent choice for building the serverless components of the application. It allows for modular, scalable, and easy-to-maintain code.
    
3. **Amazon API Gateway:** Amazon API Gateway is used to create, publish, and manage APIs at scale. It acts as the frontend for serverless applications, handling HTTP requests and seamlessly connecting them to AWS Lambda functions. API Gateway ensures secure and efficient communication between clients and the serverless backend.
    
4. **Amazon S3 (Simple Storage Service):** Amazon S3 is employed to store and retrieve static assets, such as the web application's frontend files. It provides a scalable and durable storage solution, enabling efficient content delivery and storage for the serverless blog application.
    
5. **Amazon DynamoDB:** Amazon DynamoDB serves as the NoSQL database for storing dynamic data in this project. It offers seamless scalability and low-latency access, making it well-suited for managing blog posts, user data, and other dynamic content in a serverless architecture.
    
6. **AWS CodePipeline:** AWS CodePipeline is utilized for orchestrating the CI/CD workflow. It automates the build, test, and deployment phases of the serverless application. CodePipeline enhances development agility by providing a continuous delivery pipeline that integrates with other AWS services.
    
7. **AWS CodeBuild:** AWS CodeBuild is employed for building and packaging the serverless application artifacts. It offers a fully managed build service, ensuring consistent and reproducible builds. CodeBuild integrates seamlessly with other AWS services, facilitating a streamlined CI/CD process.
    
8. **AWS CodeCommit:** AWS CodeCommit is used as the version control repository for storing and managing the source code of the serverless application. It provides a secure and scalable Git-based version control system, supporting collaborative development and code sharing within the project.
    

### **Prerequisites:**

Before diving into the ServerlessBlog project, ensure you have the following prerequisites in place:

1. **AWS Account:** A valid AWS account is necessary to leverage cloud services like Lambda, API Gateway, S3, and DynamoDB.
    
2. **Domain Name:** Obtain a domain name for your blog, as it will be essential for securing and accessing your ServerlessBlog through a custom web address.
    
3. **Basic HTML, Bootstrap, and jQuery Knowledge:** Familiarity with HTML for structuring web content, Bootstrap for responsive design, and jQuery for enhanced front-end interactivity is recommended for a smoother development experience.
    
4. **Python Skills:** Understanding of Python programming language is crucial as it powers the serverless functions implemented using AWS Lambda.
    

### **Project Overview:**

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1709394854023/3d8c0a45-397d-4a35-b2b1-199fd04a56eb.png align="center")

### **Project:**

We will be using this project repo: [https://github.com/patelajay745/SAM-project](https://github.com/patelajay745/SAM-project)

`Step-1 : Create CodeCommit Repo`

Navigate to AWS CodeCommit through the AWS Console and select "Create repository."

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1709483959595/4545f33a-92c6-48c6-a270-da6bae569f10.png align="center")

Assign a name to the repository and click "Create."

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1709484078497/64289721-2144-425a-b530-891ed6f6aaeb.png align="center")

To generate Git credentials, refer to the following link:

[https://docs.aws.amazon.com/codecommit/latest/userguide/setting-up-gc.html?icmpid=docs\_acc\_console\_connect\_np](https://docs.aws.amazon.com/codecommit/latest/userguide/setting-up-gc.html?icmpid=docs_acc_console_connect_np)

Now Copy the Git link provided in CodeCommit.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1709557368900/2bfc714c-1077-4034-809e-dd938486609c.png align="center")

Now, proceed to clone the repository from GitHub to your local machine.

[https://github.com/patelajay745/SAM-project](https://github.com/patelajay745/SAM-project)

Open the cloned folder in the terminal and enter the following command:

```bash
git remote set-url origin <CodeCommit repository URL>
```

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1709558306275/39fad30b-40be-429a-8619-666a7f64f142.png align="center")

Run the following commands:

```bash
git add .
git commit -m "All Files are added"
git push
```

It will prompt for the Git ID and password that you created earlier.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1709558712134/cac86dda-5055-4dcc-8db2-a3507ad59e77.png align="center")

Now Goto Project Folder &gt; sam-project then edit template.yaml

Change then name of the S3 Buckets . Set it to your domain or subdomain name. It should be unique. (There will be multiple places where it is written, so make sure you update all.)

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1709559669820/6150fce3-f390-423e-b248-e3aab52e1252.png align="center")

Now edit buildspec.yml in same folder. Update the S3 name and region in code.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1709559804020/5761dda4-cfe9-4dfb-9350-ac134f297fe1.png align="center")

`Step-2 : Setup CodeBuild`

Navigate to CodeBuild on the AWS Console.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1709568660194/1c514719-36d6-44de-a623-97ae5fbb3b39.png align="center")

Provide a name for the project. Select "AWS CodeCommit" as the source provider, and then choose the repository.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1709568819524/2c1df054-ce86-4f10-8a52-91d5b772bf79.png align="center")

Configure the environment as shown below.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1709569209889/8fb858d7-2fad-49dc-bf82-994572cb6c87.png align="center")

Opt for "New service role," and then select "Use a buildspec file," providing the path as illustrated below.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1709569322384/2f2dbc23-9049-4ea1-9059-b1b5a0319258.png align="center")

Click on "Create build Project". Now we need to add some permission, to execute and deploy our Serverless infrastructure. Click on service role as shown below.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1709569632449/fdb5b2ec-e79a-4b25-97b4-f7f767728879.png align="center")

It will open new tab. Click on Add permissions &gt; Attach policies.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1709569803781/10d7f6a4-9799-4295-b775-08760cc6cf37.png align="center")

Add Following permissions.and close tab.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1709570181465/017a5b6a-8ea6-4e97-9abe-438008238008.png align="center")

Click on "Start build"

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1709570280467/d00fa857-f6cf-4198-934a-fa614661375e.png align="center")

click on trail log to see more details. It will take time to build everything.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1709571130170/e4903dae-4a12-4040-a322-815bd9fc39c7.png align="center")

and if see the log you will find url, which we need further.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1709571247316/510c8f82-5784-4227-af49-060d97960fc9.png align="center")

`Step 3: Update database and all the urls in html code.`

Navigate to DynamoDB on console. Click on tables. you will see two tables. click on Login table.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1709571498219/85409edf-7b95-427a-b50f-e62a4d38dbbb.png align="center")

Click on "Actions" and Click on "Create item"

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1709571744903/d92d3f3c-b7b8-4308-acb7-5d5bdb35f204.png align="center")

add username and password. but make sure you entered exact "password" as attributes name. click on "Create item"

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1709571883622/10a7e235-b03d-4aec-86ad-54831e65b900.png align="center")

This is Problem now \* (Don't worry, I have solution for that, but it is manual , If you know why it is happening then please comment below with solution.)

To make every ApiGateway work, You need to do this.

Navigate to APIGateway in AWS Console.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1709579815760/8529410e-4a6b-486a-ba33-c2f2becb3ac6.png align="center")

Click on every method(\* POST, DELETE,GET,OPTIONS,POST,PUT one by one). Click on Integration request

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1709579936026/7860dafb-39bd-4725-86c5-e0967843628f.png align="center")

Click on Edit.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1709580043807/abd509a4-f7e4-42d7-a3f6-6f3000f860dd.png align="center")

Just click on "Save". Nothing to edit.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1709580112671/d3f27967-9356-4f5b-8f40-7d74295641d4.png align="center")

Repeat this for every methods.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1709580236389/cc1cecd0-5c6c-48a5-8bdb-8e580c004910.png align="center")

Then click on "Deploy API". Select stage and click on "Deploy"

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1709580287947/0987f76b-a845-4772-9e18-3a9e89d74c3d.png align="center")

Now Check your Cloned repo in your computer.

In website folder, open following files,

add-post.html , edit-post.html , index.html . Search for "fetch(" and paste your api gateway Invoke url.

<mark>fetch('&lt;ur invoke url&gt;/ManageBlogPost', {</mark>

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1707966634450/c2d6e393-b815-49c2-94c4-22f5ebbb41a0.png?auto=compress,format&format=webp align="left")

For edit-post.html

<mark>fetch('&lt;ur invoke url&gt;/ManageBlogPost?postId=${postId}', {</mark>

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1707966777956/0772f295-e09a-4e5b-bb48-8173b3e193f6.png?auto=compress,format&format=webp align="left")

for index.html

<mark>fetch('&lt;ur invoke url&gt;/ManageBlogPost', {</mark>

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1707966875706/9f007bac-c76b-4744-9c1c-42da8e5ef6e9.png?auto=compress,format&format=webp align="left")

Now change to show-post.html

<mark>fetch('&lt;ur invoke url&gt;/ManageBlogPost?postId=${postId}', {</mark>

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1707967100150/5b32ffdb-988f-4352-be2c-23be4fc7218c.png?auto=compress,format&format=webp align="left")

Now at last, make 3 changes in admin.html

search for "amazonaws"

<mark>fetch('&lt;ur invoke url&gt;/ManageBlogPost', {</mark>

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1707967326184/e55b2320-92e1-4e34-907e-0c1ff8b2f82b.png?auto=compress,format&format=webp align="left")

<mark>fetch('&lt;ur invoke url&gt;/ManageBlogPost?postId=${postId}', {</mark>

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1707967400926/e4defbf6-f54e-4c4c-9c15-2e2ecf82be6f.png?auto=compress,format&format=webp align="left")

### **<mark>&lt;ur invoke url&gt;login?username=</mark>**

Here make sure you enter login instead of <mark>ManageBlogPost</mark>

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1707967552763/bd70a67d-5263-468b-8b03-67b01029f8d2.png?auto=compress,format&format=webp align="left")

`Step 4: Setup Codepipeline.`

Navigate to CodePipeline in AWS Console. Click on "Create Pipeline"

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1709643722356/e6eb1c3f-80ce-4354-8a61-da609a239c64.png align="center")

provide the name of pipeline and click on "Next"

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1709643820196/06b405d2-af09-4adc-af28-2d5084f6e63f.png align="center")

Select "AWS CodeCommit" as Source provider. Choose Repository name and branch name. For output select "Full clone". Click on "Next"

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1709643947720/f237a660-81f6-4daa-85ea-b73e483b2358.png align="center")

Select "AWS CODEBuild" as build provider. Choose Project name.Click "Next"

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1709644079660/acf1d847-e931-4041-9778-2f6140da5aff.png align="center")

Click on "Skip deploy stage" for deploy stage.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1709644179292/8589dd07-5e57-4a4c-9437-7bfbac052f91.png align="center")

Review it and Click on "Create Pipeline". It will start building the pipeline.

After few minutes , your pipeline will be finish successfully.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1709644432551/2e7081eb-b7ae-47ae-a30c-b4fd34a4fd60.png align="center")

`Step 5: Final Step. Setup DNS for domain in Route53`

Navigate to Route 53 and Click on your domain name.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1707968676258/9bf33848-b5f9-43ce-a120-cf3567d6ccc9.png?auto=compress,format&format=webp align="left")

Click on "Create record"

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1707968753478/f7615158-b4ba-42fa-bd11-e24af0a135d7.png?auto=compress,format&format=webp align="left")

Write "www" if you are not using sub domain. I am using subdomain so I am writing subdomain name.

Select "A Record" as Record type.

Select "Alias".

Choose Endpoint to "Alias to 53 website endpoint"

Choose region to "Region of S3 bucket"

Your s3 Endpoint will pop up.

Click on "Create Records"

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1709644679462/e3146377-1960-4ff9-b143-73324199e272.png align="center")

Now If you ope url of your domain then you will see your Serverless Application running.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1709644823768/6470e782-9380-4800-8121-4300f7e1eee1.png align="center")

To login and add post , Click on "Admin panel" from Menu. It will ask for username and password. Enter username and password which you entered in dynamodb table.

Click on "Add Post" Bingo your Serverless blog is ready to use.

If you are encountering problem then make sure your s3 name and allowed origin are same.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1709647735822/94e066d6-7fd4-4af2-aa2d-fc1b4d66a5b7.png align="center")

Now whenever there is push in CodeCommit, our pipeline will trigger automatically.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1709647878292/29035fde-8b63-44ba-8a7f-2526c45f741f.png align="center")

If you want to learn SAM then go through this file.How I create Serverless infrastructure.

```bash
AWSTemplateFormatVersion: "2010-09-09"
Transform: AWS::Serverless-2016-10-31
Description: >

Globals:
  Function:
    Timeout: 3
    MemorySize: 128

    LoggingConfig:
      LogFormat: JSON
Resources:
  # All Lambda function
  ManageBlogPostFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: ../lambda/ManageBlog
      Handler: ManageBlogPostFunction.lambda_handler
      Runtime: python3.8
      Architectures:
        - x86_64
      Environment:
        Variables:
          BLOG_TABLE: !Ref BlogPostsTable
          S3_NAME: myserverlesblog01.ajayproject.com
      Policies:
        - DynamoDBCrudPolicy:
            TableName: !Ref BlogPostsTable
        - S3FullAccessPolicy:
            BucketName: !Ref ServerlessBlogS3Bucket

  LoginFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: ../lambda/Login
      Handler: login.lambda_handler
      Runtime: python3.8
      Architectures:
        - x86_64
      Environment:
        Variables:
          LOGIN_TABLE: !Ref LoginTable
      Policies:
        - DynamoDBCrudPolicy:
            TableName: !Ref LoginTable

  # ALL DynamoDB table
  BlogPostsTable:
    Type: AWS::Serverless::SimpleTable
    Properties:
      PrimaryKey:
        Name: postId
        Type: String

  LoginTable:
    Type: AWS::Serverless::SimpleTable
    Properties:
      PrimaryKey:
        Name: username
        Type: String

  # IAM Roles
  DynamoDBCrudPolicy:
    Type: AWS::IAM::Policy
    Properties:
      PolicyName: DynamoDBCrudPolicy
      PolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Action:
              - dynamodb:PutItem
              - dynamodb:GetItem
              - dynamodb:UpdateItem
              - dynamodb:DeleteItem
              - dynamodb:Scan
              - dynamodb:Query
            Resource: !Sub "arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/${LoginTable}"

          - Effect: Allow
            Action:
              - dynamodb:PutItem
              - dynamodb:GetItem
              - dynamodb:UpdateItem
              - dynamodb:DeleteItem
              - dynamodb:Scan
              - dynamodb:Query
            Resource: !Sub "arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/${BlogPostsTable}"
      Roles:
        - Ref: ManageBlogPostFunctionRole
        - Ref: LoginFunctionRole

  # S3 Bucket
  ServerlessBlogS3Bucket:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: myserverlesblog01.ajayproject.com
      PublicAccessBlockConfiguration:
        BlockPublicAcls: false
        BlockPublicPolicy: false
        IgnorePublicAcls: false
        RestrictPublicBuckets: false
      WebsiteConfiguration:
        IndexDocument: index.html

  # S3 Bucket Policy
  ServerlessBlogS3BucketPolicy:
    Type: AWS::S3::BucketPolicy
    Properties:
      Bucket: !Ref ServerlessBlogS3Bucket

      PolicyDocument:
        Version: "2012-10-17"
        Id: "Policy1707666026999"
        Statement:
          - Sid: "Stmt1707666025128"
            Effect: "Allow"
            Principal: "*"
            Action: "s3:GetObject"
            Resource: !Sub "arn:aws:s3:::${ServerlessBlogS3Bucket}/*"

  #API Gateway
  ServerlessBlogAPIGateway:
    Type: AWS::Serverless::Api
    Properties:
      StageName: v1
      OpenApiVersion: "2.0"
      DefinitionBody:
        swagger: "2.0"
        info:
          title: "ServerlessBlogAPIGateway"
          version: "1.0"
        paths:
          /login:
            post:
              responses:
                "200":
                  description: "Successful response"
                  schema:
                    $ref: "#/definitions/Empty"
              x-amazon-apigateway-integration:
                uri:
                  Fn::Sub: arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${LoginFunction.Arn}/invocations
                responses:
                  default:
                    statusCode: "200"
                httpMethod: "POST"
                type: aws_proxy
          /ManageBlogPost:
            get:
              responses:
                "200":
                  description: "Successful response"
                  schema:
                    $ref: "#/definitions/Empty"
              x-amazon-apigateway-integration:
                uri:
                  Fn::Sub: arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${ManageBlogPostFunction.Arn}/invocations
                responses:
                  default:
                    statusCode: "200"
                httpMethod: "GET"
                type: aws_proxy
            post:
              responses:
                "200":
                  description: "Successful response"
                  schema:
                    $ref: "#/definitions/Empty"
              x-amazon-apigateway-integration:
                uri:
                  Fn::Sub: arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${ManageBlogPostFunction.Arn}/invocations
                responses:
                  default:
                    statusCode: "200"
                httpMethod: "POST"
                type: aws_proxy
            delete:
              responses:
                "200":
                  description: "Successful response"
                  schema:
                    $ref: "#/definitions/Empty"
              x-amazon-apigateway-integration:
                uri:
                  Fn::Sub: arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${ManageBlogPostFunction.Arn}/invocations
                responses:
                  default:
                    statusCode: "200"
                httpMethod: "DELETE"
                type: aws_proxy
            put:
              responses:
                "200":
                  description: "Successful response"
                  schema:
                    $ref: "#/definitions/Empty"
              x-amazon-apigateway-integration:
                uri:
                  Fn::Sub: arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${ManageBlogPostFunction.Arn}/invocations
                responses:
                  default:
                    statusCode: "200"
                httpMethod: "PUT"
                type: aws_proxy
      Cors:
        AllowMethods: "'POST,OPTIONS,GET,DELETE'"
        AllowHeaders: "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token'"
        AllowOrigin: "'http://myserverlesblog01.ajayproject.com'"

  ApplicationResourceGroup:
    Type: AWS::ResourceGroups::Group
    Properties:
      Name:
        Fn::Sub: ApplicationInsights-SAM-${AWS::StackName}
      ResourceQuery:
        Type: CLOUDFORMATION_STACK_1_0

Outputs:
  LoginApiUrl:
    Description: "URL for the Login API"
    Value:
      Fn::Sub: "https://${ServerlessBlogAPIGateway}.execute-api.${AWS::Region}.amazonaws.com/v1/"
```

### **<mark>For added security, we have configured the Lambda function to match the origin, ensuring that only requests from our specified domain can access the API Gateway.</mark>**

### **  
Conclusion 🚀:**

In conclusion, this project harmonizes AWS SAM with CI/CD practices, offering a streamlined and efficient path to serverless development. Bridging the gap between architecture complexity and deployment simplicity, it empowers developers to navigate the serverless landscape with ease. With rapid iterations, secure deployments, and optimal performance, this project is a testament to the future of serverless architecture. Embrace the journey towards efficient and reliable serverless development! 🌐💻✨
