Table of Contents – Intro: Why use SAM to deploy an API? – Prerequisites: Install SAM CLI – Step 1: Initialize SAM – Step 2: Define the API Endpoint – Step 3: Build and Test Locally – Step 4: Deploy the API – Step 5: CI / CD With Github Actions – Conclusion Intro: Why Use SAM to Deploy an API? Deploying an API using the AWS Serverless Application Model (SAM) is an efficient and scalable approach for cloud-based applications. It simplifies infrastructure management, provides built-in SSL/TLS support, and seamlessly integrates with AWS services such as CloudFormation, Lambda, S3, Route 53, and CloudWatch. By leveraging a serverless model, developers can focus on writing application logic while AWS handles scaling, security, and maintenance, making it an ideal solution for modern, high-availability APIs. This tutorial will guide you step-by-step on how to create a simple API, deploy it using AWS CloudFormation with the SAM CLI, and then automate deployments using GitHub Actions. Here’s a more detailed diagram of this workflow: Prerequisites Prerequisites AWS CLI and SAM CLI installed. Node.js installed Docker for local testing. GitHub account. AWS CLI and SAM CLI installed. Node.js installed Node.js installed Docker for local testing. Docker GitHub account. GitHub Prerequisites: Install the SAM CLI Before diving into the tutorial, ensure the AWS SAM CLI is installed on your machine. Below are the installation steps for macOS and Windows: For macOS Install Homebrew (if not already installed): Install Homebrew /bin/bash -c “$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" /bin/bash -c “$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" Add the AWS tap and install the SAM CLI: Add the AWS tap and install the SAM CLI brew tap aws/tapbrew install aws-sam-cli brew tap aws/tapbrew install aws-sam-cli Verify the installation with sam --version sam --version For Windows Download the SAM CLI installer: Download the SAM CLI installer Go to the official AWS SAM CLI page and download the latest Windows installer. Go to the official AWS SAM CLI page and download the latest Windows installer. official AWS SAM CLI page Run the installer: Run the installer Double-click the downloaded .exe file and follow the installation wizard. Double-click the downloaded .exe file and follow the installation wizard. .exe Add SAM CLI to the PATH (if not automatically added): Add SAM CLI to the PATH Open the Start menu and search for “Environment Variables.” Click on “Edit the system environment variables.” Under “System Properties,” click the “Environment Variables” button. In “System Variables,” find Path and click "Edit." Add the directory where the SAM CLI was installed (e.g., C:\Program Files\Amazon\AWS SAM CLI\bin). Open the Start menu and search for “Environment Variables.” Click on “Edit the system environment variables.” Under “System Properties,” click the “Environment Variables” button. In “System Variables,” find Path and click "Edit." Path Add the directory where the SAM CLI was installed (e.g., C:\Program Files\Amazon\AWS SAM CLI\bin). C:\Program Files\Amazon\AWS SAM CLI\bin Verify the installation: Open a command prompt and run sam --version Verify the installation sam --version Step 1: Initialize the SAM CLI Create a new repository on your machine: mkdir aws-serverless-api Create a new repository on your machine: mkdir aws-serverless-api Navigate to the directory: cd aws-serverless-api Navigate to the directory: cd aws-serverless-api Initialize a new SAM application: sam initthen you’ll be prompted with a few options. Here is my setup: Initialize a new SAM application: sam init Choose “1 — AWS Quick Start Templates.” Choose “7 — Serverless API” to deploy a Lambda-backed serverless API on API Gateway.” Choose “Node.js 20.x” Choose “N” or No for X-Ray tracing since we want to reduce our costs, and this is just a simple Hello World example. If you are making a complex API pulling in different services, you might want to enable that to help in debugging. I’m also choosing “N” for cloudwatch monitoring since we don’t need analytics on performance for this Hello World example. Also “N” for the json format in the lambda log since this is just a Hello World example. Choose “1 — AWS Quick Start Templates.” “1 — AWS Quick Start Templates.” “1 — AWS Quick Start Templates.” Choose “7 — Serverless API” to deploy a Lambda-backed serverless API on API Gateway.” “7 — Serverless API” “7 — Serverless API” Choose “Node.js 20.x” “Node.js 20.x” “Node.js 20.x” Choose “N” or No for X-Ray tracing since we want to reduce our costs, and this is just a simple Hello World example. If you are making a complex API pulling in different services, you might want to enable that to help in debugging. “N” “N” I’m also choosing “N” for cloudwatch monitoring since we don’t need analytics on performance for this Hello World example. “N” “N” Also “N” for the json format in the lambda log since this is just a Hello World example. “N” “N” Now, let’s cd into the directory that the sam init generated; I chose to name my project sam-hello-world so let’s go: sam init sam-hello-world cd sam-hello-world cd sam-hello-world Then open the project in VSCode code . — when you open the project in VSCode or your favorite IDE, you should already see a lot of generated files. code . Understanding the Generated Files Here's what each of the files generated from sam init does: sam init **template.yaml** – Defines your API Gateway, Lambda, and other AWS resources. **src/handlers/** – Contains sample Lambda function handlers. **events/** – Sample test events for local testing. **package.json** – Manages dependencies for Node.js Lambda functions. **.gitignore**, **README.md** – Standard project setup files. **template.yaml** – Defines your API Gateway, Lambda, and other AWS resources. **template.yaml** **src/handlers/** – Contains sample Lambda function handlers. **src/handlers/** **events/** – Sample test events for local testing. **events/** **package.json** – Manages dependencies for Node.js Lambda functions. **package.json** **.gitignore**, **README.md** – Standard project setup files. **.gitignore** , **README.md** We will modify and remove some of these files to fit our very simple “hello world” API. modify and remove some of these files to fit our very simple “hello world” API Step 2: Define the API Endpoint Now, open template.yaml and replace the existing Lambda function definitions with the following: template.yaml AWSTemplateFormatVersion: "2010-09-09" Transform: AWS::Serverless-2016-10-31 Description: AWS SAM template for deploying Node.js/Express API to Lambda Resources: HelloWorldFunction: Type: AWS::Serverless::Function Properties: Handler: src/app.lambdaHandler Runtime: nodejs20.x Events: HelloWorld: Type: Api Properties: Path: / Method: GET AWSTemplateFormatVersion: "2010-09-09" Transform: AWS::Serverless-2016-10-31 Description: AWS SAM template for deploying Node.js/Express API to Lambda Resources: HelloWorldFunction: Type: AWS::Serverless::Function Properties: Handler: src/app.lambdaHandler Runtime: nodejs20.x Events: HelloWorld: Type: Api Properties: Path: / Method: GET This defines one route at the root of our API that is a GET request Update the Lambda Function In the /src directory, let’s create a file called app.js /src app.js In src/app.js, let’s write a function to output hello world from our route: src/app.js exports.lambdaHandler = async (event) => { return { statusCode: 200, body: JSON.stringify({ message: "Hello, World!" }) }; }; exports.lambdaHandler = async (event) => { return { statusCode: 200, body: JSON.stringify({ message: "Hello, World!" }) }; }; Remove Unnecessary Files Now, let’s remove the additional handlers and events that the sam init generated in step 1. sam init Open a terminal in the project directory and execute these commands: execute these commands rm -rf src/handlers/get-all-items.mjs src/handlers/get-by-id.mjs src/handlers/put-item.mjs rm -rf src/handlers/get-all-items.mjs src/handlers/get-by-id.mjs src/handlers/put-item.mjs rm -rf events/ rm -rf events/ Step 3: Build and Test Locally Open a terminal in the project directory and follow the steps below: Build the application: Build the application: Build the application sam build you should see an output like this: sam build you should see an output like this: sam build Start a local API server: Start a local API server: Start a local API server sam local start-api — Note you’ll need to have docker installed to run this locally. When successful, you should see a container created for our API in the docker desktop. Open a browser or use curl in the terminal to test the endpoint: curl [http://127.0.0.1:3000/](http://127.0.0.1:3000/) sam local start-api — Note you’ll need to have docker installed to run this locally. When successful, you should see a container created for our API in the docker desktop. Open a browser or use curl in the terminal to test the endpoint: sam local start-api — Note you’ll need to have docker installed to run this locally. When successful, you should see a container created for our API in the docker desktop. sam local start-api docker Open a browser or use curl in the terminal to test the endpoint: Open a browser or use curl in the terminal to test the endpoint: curl curl [http://127.0.0.1:3000/](http://127.0.0.1:3000/) curl [http://127.0.0.1:3000/](http://127.0.0.1:3000/) curl [http://127.0.0.1:3000/](http://127.0.0.1:3000/) If you see the JSON returned from our endpoint, then you’ve successfully started the API locally! 🚀 Step 4: Deploy the API Package and deploy the API using SAM: Now that we’ve confirmed our API is working locally, let’s deploy it to AWS using Package and deploy the API using SAM sam deploy --guided sam deploy --guided This guided deployment will allow you to: This guided deployment will allow you to: 1. Choose your stack name; mine is sam-hello-world sam-hello-world 2. Choose AWS Region; mine is us-east-1 us-east-1 3. Confirm changes before deployment; I chose Y Y 4. Allow SAM-CLI Role Creation, Y Y 5. Disable Rollback, choose N we want the tool to clean up any failed resources N 6. HelloWorldFunction has no Authentication; is this okay? For the purposes of this tutorial Y Y 7. Save arguments to configuration file Y Y 8. Configuration file samconfig.toml samconfig.toml 9. SAM configuration environment — leave this as default If your deployment was successful, you should see the resources successfully created like this: When the deployment is successful, log in to your AWS account and search for the API Gateway service. Go to API settings, copy your domain paste it in the URL input on your browser window, and add `/Prod` to the end of your URL to reach your deployed API endpoint. log in to your AWS account My URL looks like this: You can also search for Lambda in the AWS console to view your deployed serverless Lambda function, where the code we wrote for this endpoint lives. You should also see an S3 bucket created, which holds the bundle of our code. Now that we’ve successfully deployed our API, let’s automate this process with GitHub so that anytime we want to develop on our API, the deployment can automatically trigger by just committing to the MAIN or MASTER branch. Step 5: CI/CD With Github Actions Initialize Your Git Repo and Push to Your Master Branch First, we’ll initialize a git repository in our project’s directory with git initThen, let’s add these files to our .gitignoreas we do not want to commit them. git init .gitignore echo "node_modules/\n.aws-sam/\nsamconfig.toml" >> .gitignore echo "node_modules/\n.aws-sam/\nsamconfig.toml" >> .gitignore Now, we’re safe to stage all of the files in the project: git add . and then make our first commit: git add . git commit -m "Initial commit - AWS SAM API setup" git commit -m "Initial commit - AWS SAM API setup" This next step assumes you have the GitHub CLI installed — This step is so we can create a GitHub repo from our current directory: gh repo create aws-sam-cli-tutorial --public --source=. --remote=origin gh repo create aws-sam-cli-tutorial --public --source=. --remote=origin If you don’t have the GitHub CLI, you can create the repo manually on GitHub and then point the directory to your repo with this command: git remote add origin https://github.com/YOUR_USERNAME/YOUR_REPO_NAME.git git remote add origin https://github.com/YOUR_USERNAME/YOUR_REPO_NAME.git Finally, let’s push our code to GitHub git push -u origin master git push -u origin master Set Up GitHub Actions for CI/CD Let’s create the GitHub actions workflow file: mkdir -p .github/workflows mkdir -p .github/workflows touch .github/workflows/deploy.yml touch .github/workflows/deploy.yml Open that deploy.yml file in vscode and paste the following yaml configuration: deploy.yml name: Deploy API to AWS CloudFormation on: push: branches: - master jobs: deploy: runs-on: ubuntu-latest steps: - name: Checkout Code uses: actions/checkout@v3 - name: Install AWS SAM CLI uses: aws-actions/setup-sam@v2 - name: Configure AWS CLI uses: aws-actions/configure-aws-credentials@v2 with: aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} aws-region: us-east-1 - name: Build and Deploy API run: | sam build sam deploy \ --stack-name sam-hello-world \ --s3-bucket ${{ secrets.AWS_S3_BUCKET }} \ --capabilities CAPABILITY_IAM \ --region ${{ secrets.AWS_REGION }} \ --no-confirm-changeset \ --no-fail-on-empty-changeset name: Deploy API to AWS CloudFormation on: push: branches: - master jobs: deploy: runs-on: ubuntu-latest steps: - name: Checkout Code uses: actions/checkout@v3 - name: Install AWS SAM CLI uses: aws-actions/setup-sam@v2 - name: Configure AWS CLI uses: aws-actions/configure-aws-credentials@v2 with: aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} aws-region: us-east-1 - name: Build and Deploy API run: | sam build sam deploy \ --stack-name sam-hello-world \ --s3-bucket ${{ secrets.AWS_S3_BUCKET }} \ --capabilities CAPABILITY_IAM \ --region ${{ secrets.AWS_REGION }} \ --no-confirm-changeset \ --no-fail-on-empty-changeset In this .yaml file for automated deployment, we rely on the AWS-Actions repositories from GitHub: https://github.com/orgs/aws-actions/repositories to install the SAM CLI and configure our credentials — these AWS repos may change in a few years, so update as needed. https://github.com/orgs/aws-actions/repositories Now, let’s go to github.com — go to your repositories, click the repository we made for this project, go to settings, actions, and you’ll see this screen to add environment and repository secrets. We’ll want to add Repository secrets for: We’ll want to add Repository secrets for: AWS_ACCESS_KEY_ID AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY AWS_SECRET_ACCESS_KEY AWS_REGION AWS_REGION AWS_S3_BUCKET AWS_S3_BUCKET To get these values, go to your AWS console. For AWS_REGION, you’ll want to use the same region that you deployed from your local machine. If you are unsure of the region, search for cloudformation and find the stack that you created, click on it, and you should see the region in the URL. AWS_REGION, For the AWS_S3_BUCKET, search for S3 in the AWS console and select the bucket that was created when we deployed from our machine in Step 4. Use the ID of this bucket for your value; mine is shown in the screenshot below. AWS_S3_BUCKET, For the AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY, go to the IAM service in the AWS console: AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY, From here, create an IAM user if you don’t already have one with the access you want to use, and add at least these permission scopes to the user: AWSLambdaFullAccess IAMFullAccess AmazonS3FullAccess CloudFormationFullAccess AmazonAPIGatewayAdministrator AWSLambdaFullAccess IAMFullAccess AmazonS3FullAccess CloudFormationFullAccess AmazonAPIGatewayAdministrator You can also just add AdministratorAccess if you’re comfortable with that. After that, we’ll want to create an Access key and Secret for this user. Choose the CLI as the use case for the access key. Make sure to copy your secret because you won’t be able to see it again. AdministratorAccess Now, go back to GitHub and add your repository secrets: Okay, the final step is to test that everything works! Update your app.js to say something else; I changed mine to “What up, World!” exports.lambdaHandler = async (event) => { return { statusCode: 200, body: JSON.stringify({ message: "What up, World!" }), }; }; exports.lambdaHandler = async (event) => { return { statusCode: 200, body: JSON.stringify({ message: "What up, World!" }), }; }; Then, push your changes to your master branch. git add . git commit -m 'updated message' git push — make sure you're on the master branch when you push. git add . git commit -m 'updated message' git push In your GitHub repo, you should see the action run and successfully deploy: Finally, go back to your web browser and paste the URL from the API Gateway we set up. Mine is: https://tt40c6vgm3.execute-api.us-east-1.amazonaws.com/Prod — and confirm the message was changed. https://tt40c6vgm3.execute-api.us-east-1.amazonaws.com/Prod Conclusion Congratulations! You just created a CI/CD deployment pipeline for a serverless AWS Stack; now, you can get busy building out your API with useful services. Let me know if this article was useful or if you have any questions or suggestions for improvements!