AI-Powered Product Description Generator
Build a serverless REST API that generates product descriptions with Amazon Bedrock.
Introduction
30 Second Summary
Every product you buy online has a carefully crafted description designed to make you click "Add to Cart." Writing hundreds of those descriptions by hand is slow and expensive, but AI can generate them in seconds.
In this project, you will build a serverless microservice on AWS that generates AI-powered product descriptions. You'll wire up a REST API, invoke Amazon's Nova Lite model through Amazon Bedrock, store results in DynamoDB, and automate deployments with GitHub Actions.
What You'll Build
A cloud-native serverless API that accepts a product name and category, generates a marketing description using Amazon Bedrock, and persists the result in DynamoDB.
By the end of this project, you'll have:
- A Python Lambda function that orchestrates AI calls to Amazon Bedrock and stores results in DynamoDB.
- An AWS SAM template defining your full serverless stack as infrastructure as code.
- A CI/CD pipeline using GitHub Actions that auto-deploys your service on every push to main.
- Secret Mission: A retrieval endpoint and structured CloudWatch logging for production-grade observability.
Are there any prerequisites?
This project assumes you have an AWS account with credits, a GitHub account, Python 3.10, Docker, and VS Code already installed. You'll install the AWS SAM CLI in Step 1.
You should also have the AWS CLI configured with credentials that have access to create resources in your account.
Before We Start
Before you write any application code, you need the right tools and permissions in place. This project uses AWS SAM to define and deploy your serverless stack, Amazon Bedrock to generate AI-powered descriptions, and GitHub to host your code with automated deployments.
In this step, you will install the AWS SAM CLI, enable access to the AI model your Lambda function will call, and create the GitHub repository that holds everything together.
In this step, get ready to:
- Install the AWS SAM CLI and verify your AWS credentials.
- Enable Amazon Bedrock model access for Amazon Nova Lite.
- Create and clone your GitHub repository.
Install AWS SAM CLI
The AWS SAM CLI is a command-line tool that lets you build, test, and deploy serverless applications defined in a SAM template. It packages your Lambda code and provisions your cloud infrastructure in a single command.
- Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
- Type Terminal (macOS) or PowerShell (Windows) and press Enter to open it.
Download and install the SAM CLI package for your operating system.
macOS
- Download the SAM CLI installer by clicking the link for your chip type:
- Double-click the downloaded .pkg file and follow the installer prompts.
- Accept the default installation location when prompted.
Not sure which chip you have?
Click the Apple menu in the top-left corner, then About This Mac. If the Chip field says "Apple M1" (or M2, M3, M4), use the Apple Silicon link. If it says "Intel", use the Intel link.
Windows
- Download the SAM CLI MSI installer: AWS_SAM_CLI_64_PY3.msi
- Double-click the downloaded .msi file and follow the installer prompts.
- Accept the default settings and complete the installation.
- Close and reopen your PowerShell window after installation so the new PATH takes effect.
- Verify the installation by running this command:
sam --version
✔️ I see a version number
The SAM CLI is installed and ready. Move on to verifying your AWS CLI credentials below.
ⓧ Command not found
- On macOS, make sure you completed the .pkg installer without errors. Try opening a new terminal window and running the command again.
- On Windows, close PowerShell completely, reopen it, and try again. The MSI installer adds sam to your PATH, but existing windows do not pick up the change.
If it still does not work, help me troubleshoot the SAM CLI installation.
Now confirm your AWS CLI is configured and can reach your AWS account.
- Verify your AWS credentials by running this command:
aws sts get-caller-identity
What does this command do?
This calls the AWS Security Token Service to confirm your CLI credentials are valid. It returns your AWS account ID, user ARN, and user ID.
If this works, your CLI can deploy resources to AWS.
✔️ I see my account details
You should see JSON output containing your Account, Arn, and UserId. Your AWS CLI is configured correctly.
ⓧ Unable to locate credentials
Your AWS CLI is not configured yet. You need to set up your access key and secret key.
- Run the following command to start the configuration wizard:
aws configure
- Enter your AWS Access Key ID when prompted.
- Enter your AWS Secret Access Key when prompted.
- Enter us-east-1 for the default region.
- Enter json for the output format.
- Run the verification command again to confirm it works:
aws sts get-caller-identity
If you still have trouble, help me configure the AWS CLI.
Enable Amazon Bedrock model access
Your Lambda function will call Amazon Bedrock to generate product descriptions using a foundation model called Amazon Nova Lite. Before you can invoke the model via the API, you need to enable access to it in the Bedrock console.
- Open the Amazon Bedrock console at https://console.aws.amazon.com/bedrock/.
- Make sure you are in the US East (N. Virginia) region. Check the region selector in the top-right corner of the console. If it shows a different region, click it and select US East (N. Virginia) us-east-1.
- In the left navigation pane, under Bedrock configurations, click Model access.
- Click Enable specific models.
- In the Base models list, find and select Nova Lite.
- Click Next.
- On the Review and submit page, click Submit.
- Refresh the Base models table. You should see Amazon Nova Lite with an Access granted status.
What is Amazon Nova Lite?
Amazon Nova Lite is a fast, low-cost multimodal model from Amazon that processes text, image, and video inputs. It is well-suited for tasks like generating marketing copy, answering questions, and powering chat interfaces.
Because it is an Amazon-owned model, access is typically granted immediately. Third-party models (like Anthropic Claude) may require additional approval steps.
Don't see Nova Lite in the list?
Make sure your region is set to us-east-1. Nova Lite is only available in specific regions. If you are in a different region, the model will not appear in the Base models list.
If you still have trouble, help me enable Bedrock model access.
Initialize the GitHub repository
Your project code will live in a GitHub repository. Later, you will add a CI/CD workflow that auto-deploys your serverless stack whenever you push to the main branch.
- Open GitHub in your browser at https://github.com.
- Click the + icon in the top-right corner, then click New repository.
- Enter ai-product-description-generator as the repository name.
- Leave the repository set to Public.
- Check the box for Add a README file.
- Click Create repository.
Now clone the repository to your local machine so you can add files to it.
- On your new repository page, click the green Code button.
- Copy the HTTPS URL.
- Move to your Desktop by running this command:
cd ~/Desktop
- Clone the repository by running this command:
git clone [[REPO_URL="https://github.com/YOUR_USERNAME/ai-product-description-generator.git"]]
- Move into the project folder by running:
cd ai-product-description-generator
- Confirm the repository contents by running:
ls
You should see README.md listed in the output.
Clone command failing?
If you see an authentication error, make sure you are signed into git on your machine. GitHub may prompt you to authenticate via browser or ask for a personal access token.
If you see "repository not found", double-check that you copied the correct URL from your repository page.
Still stuck? Help me clone my GitHub repository.
Your tools are ready and your repository is waiting for code. Next up, you will define the serverless infrastructure that powers your AI microservice.
Define Your Serverless Infrastructure
In Step 1, you installed the SAM CLI, confirmed your AWS credentials, and enabled Bedrock model access. Now it's time to define what you're actually deploying.
AWS SAM uses a single template.yaml file to describe your entire serverless stack as code. In this step, you'll define a DynamoDB table, a Lambda function, and an API Gateway endpoint in one place.
In this step, get ready to:
- Create an AWS SAM template defining your serverless resources.
- Set up the project file structure with source code and deployment configuration.
Create the SAM template
The template.yaml file is your Infrastructure as Code definition. It tells CloudFormation exactly which AWS resources to provision and how they connect.
- Create a file called template.yaml in the root of your ai-product-description-generator folder and add the following code:
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: AI-Powered Product Description Generator
Globals:
Function:
Timeout: 30
MemorySize: 256
Runtime: python3.10
Architectures:
- x86_64
Resources:
DescriptionsTable:
Type: AWS::DynamoDB::Table
Properties:
TableName: product-descriptions
AttributeDefinitions:
- AttributeName: id
AttributeType: S
KeySchema:
- AttributeName: id
KeyType: HASH
BillingMode: PAY_PER_REQUEST
What does this code do?
- Transform: AWS::Serverless-2016-10-31 tells CloudFormation this is a SAM template with serverless-specific resource types.
- The Globals section sets shared defaults (timeout, memory, runtime) for all Lambda functions in the template.
- DescriptionsTable creates a DynamoDB table with a single partition key (id) and PAY_PER_REQUEST billing, so you only pay for actual reads and writes.
- Save template.yaml.
- You won't see a visible change yet. The next chunk adds the Lambda function that ties everything together.
- Still in template.yaml, add the following below the DescriptionsTable resource (inside the Resources section):
GenerateDescriptionFunction:
Type: AWS::Serverless::Function
Properties:
CodeUri: src/
Handler: app.lambda_handler
Environment:
Variables:
TABLE_NAME: !Ref DescriptionsTable
MODEL_ID: amazon.nova-lite-v1:0
Policies:
- DynamoDBCrudPolicy:
TableName: !Ref DescriptionsTable
- Statement:
- Effect: Allow
Action:
- bedrock:InvokeModel
Resource: "arn:aws:bedrock:us-east-1::foundation-model/amazon.nova-lite-v1:0"
Events:
GenerateDescription:
Type: Api
Properties:
Path: /descriptions
Method: post
Outputs:
ApiEndpoint:
Description: API Gateway endpoint URL
Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/descriptions"
What does this code do?
- CodeUri: src/ tells SAM where your Lambda function code lives on disk.
- Handler: app.lambda_handler maps to the lambda_handler function inside app.py.
- The Policies section grants the function permission to read/write DynamoDB and invoke the Bedrock model.
- The Events section creates an API Gateway POST endpoint at /descriptions that triggers this function.
- The Outputs section prints the live API URL after deployment so you know where to send requests.
Why PAY_PER_REQUEST billing?
DynamoDB offers two billing modes: provisioned capacity and on-demand. On-demand (PAY_PER_REQUEST) means you pay only for the reads and writes you actually perform.
For a project with low, unpredictable traffic, this keeps costs at zero when idle. DynamoDB's Always Free tier covers 25 GB of storage and up to 200 million requests per month.
- Save template.yaml.
- Validate the template by running this command in your terminal:
sam validate
- You should see a message confirming that template.yaml is a valid SAM Template.
Seeing a validation error?
- YAML is whitespace-sensitive. Make sure you're using spaces (not tabs) and that each indentation level is exactly 2 spaces.
- Check that GenerateDescriptionFunction is indented at the same level as DescriptionsTable (both are children of Resources).
- Confirm the Outputs section is at the root level (same indentation as Resources and Globals).
Help me debug my SAM template validation error
✔️ Awesome, I've got everything!
Great. Double check you've saved template.yaml and that validation passed.
ⓧ I'd like to double check the full code
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: AI-Powered Product Description Generator
Globals:
Function:
Timeout: 30
MemorySize: 256
Runtime: python3.10
Architectures:
- x86_64
Resources:
DescriptionsTable:
Type: AWS::DynamoDB::Table
Properties:
TableName: product-descriptions
AttributeDefinitions:
- AttributeName: id
AttributeType: S
KeySchema:
- AttributeName: id
KeyType: HASH
BillingMode: PAY_PER_REQUEST
GenerateDescriptionFunction:
Type: AWS::Serverless::Function
Properties:
CodeUri: src/
Handler: app.lambda_handler
Environment:
Variables:
TABLE_NAME: !Ref DescriptionsTable
MODEL_ID: amazon.nova-lite-v1:0
Policies:
- DynamoDBCrudPolicy:
TableName: !Ref DescriptionsTable
- Statement:
- Effect: Allow
Action:
- bedrock:InvokeModel
Resource: "arn:aws:bedrock:us-east-1::foundation-model/amazon.nova-lite-v1:0"
Events:
GenerateDescription:
Type: Api
Properties:
Path: /descriptions
Method: post
Outputs:
ApiEndpoint:
Description: API Gateway endpoint URL
Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/descriptions"
Create the project file structure
SAM expects your Lambda function code to live in the directory specified by CodeUri. You pointed that to src/ in your template, so you need to create that folder and its files.
- Create the src/ directory and its placeholder files by running these commands:
mkdir src
touch src/__init__.py
touch src/app.py
- Confirm the files were created by running:
ls src/
- You should see __init__.py and app.py listed in the output.
Why an empty __init__.py?
The __init__.py file marks the src/ directory as a Python package. SAM needs this to properly resolve imports when it packages your function for deployment.
- Add the project dependency to src/requirements.txt by running this command:
echo 'boto3>=1.35.0' > src/requirements.txt
Why include boto3 explicitly?
The Lambda runtime includes boto3 by default, but the bundled version may not support the latest Bedrock APIs. Pinning boto3>=1.35.0 in your requirements ensures SAM bundles a version with full Converse API compatibility.
- Create the deployment configuration file samconfig.toml in the root of your project and add the following:
version = 0.1
[default.deploy.parameters]
stack_name = "ai-product-description-generator"
resolve_s3 = true
s3_prefix = "ai-product-description-generator"
region = "us-east-1"
capabilities = "CAPABILITY_IAM"
confirm_changeset = false
What does samconfig.toml do?
- This file stores default parameters for sam deploy so you don't have to type them every time.
- resolve_s3 = true tells SAM to automatically create an S3 bucket for your deployment artifacts.
- capabilities = "CAPABILITY_IAM" authorizes CloudFormation to create IAM roles on your behalf.
- Save samconfig.toml.
- Verify your full project structure by running:
ls
- You should see README.md, samconfig.toml, src, and template.yaml listed.
Missing files or wrong directory?
- Make sure your terminal is inside the ai-product-description-generator directory. Run pwd to check your current location.
- If you don't see src in the output, the mkdir command may have run in the wrong folder. Navigate to your project root and try again.
Help me fix my project directory structure
✔️ Awesome, I've got everything!
Great. Double check that all files are saved and your ls output matches.
ⓧ I'd like to double check the full code
version = 0.1
[default.deploy.parameters]
stack_name = "ai-product-description-generator"
resolve_s3 = true
s3_prefix = "ai-product-description-generator"
region = "us-east-1"
capabilities = "CAPABILITY_IAM"
confirm_changeset = false
boto3>=1.35.0
Your infrastructure is fully defined as code. Next up, you'll write the Lambda function logic that generates AI-powered descriptions using Bedrock.
Build the Lambda Function
Your infrastructure is defined, but the Lambda function itself is still an empty file. The real logic lives in src/app.py: receiving an API request, calling Amazon Bedrock to generate a product description, and persisting the result in DynamoDB.
In this step, you will write the complete Lambda function across three layers: the AI integration, the data persistence, and the request handler that ties everything together.
In this step, get ready to:
- Write the Bedrock integration that generates product descriptions using the Converse API.
- Add DynamoDB persistence to store generated descriptions.
- Wire up the Lambda handler to orchestrate the full request flow.
Write the Bedrock integration
The Bedrock Converse API is a unified interface for invoking foundation models. It standardizes the request format across all models, so you can swap models later without rewriting your integration code.
- In VS Code, open src/app.py (it should be empty).
- Add the module imports and client initialization by pasting this code:
import json
import os
import uuid
from datetime import datetime, timezone
import boto3
# Create the Bedrock Runtime client for AI model invocations
bedrock_client = boto3.client("bedrock-runtime", region_name="us-east-1")
# Create the DynamoDB resource and reference the table from environment variables
dynamodb = boto3.resource("dynamodb")
table = dynamodb.Table(os.environ["TABLE_NAME"])
# Load the model ID from environment variables (set in template.yaml)
MODEL_ID = os.environ["MODEL_ID"]
What does this code do?
This module-level code runs once when Lambda initializes your function (a "cold start"). By creating clients here instead of inside the handler, they persist across invocations and reduce latency.
- bedrock_client connects to the Bedrock Runtime service for AI inference calls.
- table references the DynamoDB table whose name is injected via the TABLE_NAME environment variable from your SAM template.
- MODEL_ID holds amazon.nova-lite-v1:0, also injected from the template.
- Below the module-level setup, add the generate_description function:
def generate_description(product_name, category):
# Define the system prompt that shapes the AI's writing style
system_prompt = (
"You are a professional copywriter. Generate a compelling, concise "
"product description (2-3 sentences) for marketing purposes. "
"Focus on benefits and appeal to customers."
)
# Format the user's product details into the message
user_message = f"Product: {product_name}\nCategory: {category}"
# Call the Bedrock Converse API with the model, message, and config
response = bedrock_client.converse(
modelId=MODEL_ID,
messages=[
{
"role": "user",
"content": [{"text": user_message}],
}
],
system=[{"text": system_prompt}],
inferenceConfig={"maxTokens": 256, "temperature": 0.7},
)
# Extract the generated text from the response structure
return response["output"]["message"]["content"][0]["text"]
What does this code do?
The generate_description function calls Amazon Nova Lite via the Converse API. The Converse API uses a standardized message format with roles (user, assistant) just like a chat conversation.
- system sets the AI's persona and instructions separately from the user message.
- inferenceConfig controls output length (maxTokens) and creativity (temperature at 0.7 adds variety while staying coherent).
- The response path response["output"]["message"]["content"][0]["text"] navigates the Converse API's nested response structure to get the generated text.
Why the Converse API instead of InvokeModel?
AWS recommends using Converse over InvokeModel because it unifies the inference request across Amazon Bedrock models and simplifies the management of multi-turn conversations. If you swap to a different model later, your code stays the same.
- Save src/app.py.
You will not see any output yet. The next function adds the database layer, and you will test the full flow after deployment in the next step.
Seeing a red underline on the import?
Your local editor may show a warning on import boto3 if boto3 is not installed in your local Python environment. This is safe to ignore. SAM builds and installs dependencies from requirements.txt during sam build, so the Lambda runtime will have boto3 available.
If you want the warning gone locally, run pip install boto3 in your terminal, but it is not required for deployment.
Help me fix my boto3 import warning
Add DynamoDB persistence
Every generated description needs to be stored so it can be retrieved later. The save_description function creates a unique record in DynamoDB with a UUID, timestamp, and the generated content.
- In src/app.py, add the save_description function below generate_description:
def save_description(product_name, category, description):
# Generate a unique ID and timestamp for this record
item_id = str(uuid.uuid4())
timestamp = datetime.now(timezone.utc).isoformat()
# Build the item with all fields we want to store
item = {
"id": item_id,
"product_name": product_name,
"category": category,
"description": description,
"created_at": timestamp,
}
# Write the item to DynamoDB
table.put_item(Item=item)
return item
What does this code do?
- uuid.uuid4() generates a random unique identifier so each description has its own primary key.
- datetime.now(timezone.utc).isoformat() creates an ISO 8601 timestamp in UTC for consistent time tracking.
- table.put_item(Item=item) writes the complete record to DynamoDB. The table uses PAY_PER_REQUEST billing, so you only pay for actual writes.
- The function returns the full item (including the generated id and created_at) so the API can include them in its response.
- Save src/app.py.
You will not see output yet. The handler in the next substep ties these two functions together into a working API endpoint.
Wire up the Lambda handler
The lambda_handler function is the entry point that API Gateway calls when a request arrives. It parses the incoming JSON body, validates the required fields, orchestrates the generation and storage, and returns a structured HTTP response.
- In src/app.py, add the lambda_handler function below save_description:
def lambda_handler(event, context):
# Parse the JSON body from the API Gateway event
try:
body = json.loads(event.get("body", "{}"))
except json.JSONDecodeError:
return {
"statusCode": 400,
"body": json.dumps({"error": "Invalid JSON in request body"}),
}
# Extract required fields from the request
product_name = body.get("product_name")
category = body.get("category")
# Validate that both fields are present
if not product_name or not category:
return {
"statusCode": 400,
"body": json.dumps(
{"error": "Both 'product_name' and 'category' are required"}
),
}
# Generate the AI description and save it to DynamoDB
description = generate_description(product_name, category)
item = save_description(product_name, category, description)
# Return the complete item as a JSON response
return {
"statusCode": 200,
"headers": {"Content-Type": "application/json"},
"body": json.dumps(item),
}
What does this code do?
The handler follows a common Lambda pattern: parse, validate, execute, respond.
- event.get("body", "{}") safely extracts the request body. API Gateway sends the body as a JSON string inside the event object.
- The try/except block catches malformed JSON and returns a 400 error immediately.
- Field validation ensures both product_name and category are present before calling Bedrock (avoiding wasted API calls on bad input).
- The 200 response includes the Content-Type header so API consumers know they are receiving JSON.
- Save src/app.py.
Your Lambda function is now complete. You will deploy and test it in the next step.
Worried about indentation or syntax errors?
Python is whitespace-sensitive. Make sure each function starts at column 0 (no leading spaces before def). The code inside each function should be indented with 4 spaces.
If you see a red squiggly on a line, check for mismatched parentheses or quotes. The response path in generate_description has multiple nested brackets. Make sure each opening bracket has a matching close.
Help me find and fix my syntax error
✔️ Awesome, I've got everything!
Great. Double check you have saved src/app.py and move on to the next step where you will deploy this function live.
ⓧ I'd like to double check the full code
Here is the complete src/app.py file. Compare yours against it:
import json
import os
import uuid
from datetime import datetime, timezone
import boto3
bedrock_client = boto3.client("bedrock-runtime", region_name="us-east-1")
dynamodb = boto3.resource("dynamodb")
table = dynamodb.Table(os.environ["TABLE_NAME"])
MODEL_ID = os.environ["MODEL_ID"]
def generate_description(product_name, category):
system_prompt = (
"You are a professional copywriter. Generate a compelling, concise "
"product description (2-3 sentences) for marketing purposes. "
"Focus on benefits and appeal to customers."
)
user_message = f"Product: {product_name}\nCategory: {category}"
response = bedrock_client.converse(
modelId=MODEL_ID,
messages=[
{
"role": "user",
"content": [{"text": user_message}],
}
],
system=[{"text": system_prompt}],
inferenceConfig={"maxTokens": 256, "temperature": 0.7},
)
return response["output"]["message"]["content"][0]["text"]
def save_description(product_name, category, description):
item_id = str(uuid.uuid4())
timestamp = datetime.now(timezone.utc).isoformat()
item = {
"id": item_id,
"product_name": product_name,
"category": category,
"description": description,
"created_at": timestamp,
}
table.put_item(Item=item)
return item
def lambda_handler(event, context):
try:
body = json.loads(event.get("body", "{}"))
except json.JSONDecodeError:
return {
"statusCode": 400,
"body": json.dumps({"error": "Invalid JSON in request body"}),
}
product_name = body.get("product_name")
category = body.get("category")
if not product_name or not category:
return {
"statusCode": 400,
"body": json.dumps(
{"error": "Both 'product_name' and 'category' are required"}
),
}
description = generate_description(product_name, category)
item = save_description(product_name, category, description)
return {
"statusCode": 200,
"headers": {"Content-Type": "application/json"},
"body": json.dumps(item),
}
Your Lambda function is ready to go live. Next up, you will deploy the entire stack to AWS and test your API with a real request.
Deploy and Test Your API
Your Lambda function code is written and your infrastructure is defined. Now it is time to deploy everything to AWS and see your AI-powered API running live.
In this step, you will use AWS SAM to package and deploy your entire serverless stack in one command. Then you will call your live endpoint and watch Amazon Bedrock generate a real product description on demand.
In this step, get ready to:
- Deploy the serverless stack to AWS with SAM.
- Test the live API endpoint with curl.
- Verify data persistence in DynamoDB.
Build and deploy with SAM
SAM packages your Lambda code, uploads it to S3, and then creates a CloudFormation stack containing all the resources defined in your template.
- Make sure Docker Desktop is running on your machine. SAM uses Docker to build Lambda packages in a Linux-compatible environment.
- In your terminal, make sure you are inside your project's root folder (the one containing template.yaml).
- Build the Lambda deployment package by running this command:
sam build
What does this command do?
The sam build command reads your template.yaml, installs the Python dependencies from requirements.txt, and creates a deployment-ready artifact in the .aws-sam/ directory.
- You should see a Build Succeeded message in your terminal output.
Build failed?
Make sure Docker Desktop is running. SAM needs Docker to simulate the Lambda build environment.
Confirm you are running the command from the directory that contains template.yaml. If you see a "template not found" error, you are in the wrong folder.
help me fix my sam build error
- Deploy the stack to AWS by running this command:
sam deploy --guided
What happens during guided deployment?
SAM will ask you a series of interactive prompts. Since your samconfig.toml already has the configuration values, the defaults shown in brackets will match your settings. Press Enter to accept each default.
When you see a prompt about your function not having authorization defined, type y and press Enter. This is expected because your API endpoint is publicly accessible without authentication.
- Wait for the deployment to complete. This takes 1-2 minutes while CloudFormation provisions your Lambda function, API Gateway, DynamoDB table, and IAM roles.
- When deployment finishes, look for the Outputs section at the bottom of the terminal output. Copy the ApiEndpoint URL. This is your live API address.
Save your endpoint URL
Paste the endpoint URL somewhere accessible (a text file or sticky note). You will use it in the next substep and again in the CI/CD step.
Seeing Security Constraints Not Satisfied?
This error means you typed N (or pressed Enter without typing y) at the authorization prompt. Run sam deploy --guided again and this time type y when asked about authorization.
help me fix my SAM deployment error
Test the live API
Your API is deployed and ready to receive requests. Time to call it and see Amazon Bedrock generate a product description in real time.
- Send a POST request to your API by running this command:
curl -X POST [[API_ENDPOINT="your API Gateway endpoint URL from the Outputs section"]] \
-H "Content-Type: application/json" \
-d '{"product_name": "CloudGlow Smart Lamp", "category": "Smart Home"}'
What does this command do?
This sends a JSON payload with a product name and category to your API. The Lambda function receives the request, calls Bedrock's Converse API to generate a marketing description, saves the result to DynamoDB, and returns everything as JSON.
- You should see a JSON response containing an id, product_name, category, description (the AI-generated text), and created_at timestamp.
Getting a timeout or 500 error?
A 500 error often means the Lambda function cannot reach Bedrock. Verify that you enabled model access for Amazon Nova Lite in the us-east-1 region (Step 1). The access status must show as granted.
If you see a "Connection refused" or timeout, double check that you copied the full endpoint URL including /Prod/descriptions at the end.
help me debug my API error response
- Now verify the item was saved to DynamoDB. Open the AWS Console and navigate to the DynamoDB service.
- In the left navigation pane, click Tables.
- Click the product-descriptions table.
- Click Explore table items.
- In the Items returned section, you should see your item with the product name, category, AI-generated description, and timestamp.
Why check DynamoDB separately?
The curl response already shows the saved item, but checking DynamoDB directly confirms that persistence is working independently of the API response. In production, you would monitor DynamoDB to ensure data durability.
Your serverless API is live and generating AI-powered descriptions on demand. Next up, you will automate deployments so that every push to your GitHub repository triggers a fresh deploy.
Set Up CI/CD with GitHub Actions
Your API is live and working, but right now the only way to update it is by running sam build and sam deploy manually from your terminal.
In a professional environment, deployments happen automatically when code is pushed to the main branch. This is called CI/CD (Continuous Integration / Continuous Deployment). In this step, you will set up GitHub Actions to build and deploy your SAM application every time you push to main.
In this step, get ready to:
- Configure GitHub repository secrets for AWS deployment.
- Create a GitHub Actions workflow that auto-deploys on push.
- Verify end-to-end CI/CD by pushing a code change.
Configure AWS credentials for GitHub Actions
GitHub Actions runs your deployment on GitHub's servers, not on your local machine. For the runner to call sam deploy on your behalf, it needs the same AWS credentials you used to configure the AWS CLI locally.
You will store these credentials as encrypted repository secrets that GitHub Actions can access at runtime.
- Open your repository on GitHub in your browser.
- Under your repository name, click Settings.
- In the Security section of the sidebar, select Secrets and variables, then click Actions.
- Click the Secrets tab.
- Click New repository secret.
- In the Name field, type AWS_ACCESS_KEY_ID.
- In the Secret field, paste your AWS Access Key ID (the same one you used when configuring the AWS CLI).
- Click Add secret.
- Repeat the process to add a second secret with the name AWS_SECRET_ACCESS_KEY and paste your AWS Secret Access Key as the value.
- Repeat once more to add a third secret with the name AWS_REGION and enter us-east-1 as the value.
Why three separate secrets?
The GitHub Actions workflow references each secret individually using the ${{ secrets.SECRET_NAME }} syntax. Splitting credentials into separate secrets follows the principle of least privilege and makes it easier to rotate one credential without touching the others.
Can't find the Settings tab?
You need admin or write access to the repository. If you created the repo yourself, you already have this. If the Settings tab is hidden, click the overflow dropdown menu (three dots) next to the tab bar and select Settings from there.
Help me find my repository settings on GitHub.
Create the GitHub Actions workflow
GitHub Actions uses YAML workflow files stored in a special .github/workflows/ directory. When you push to main, GitHub detects the workflow file and runs it automatically.
- Create the workflow directory by running this command in your project folder:
mkdir -p .github/workflows
- Create a new file at .github/workflows/deploy.yml in VS Code and add the following workflow definition:
name: Deploy SAM Application
on:
push:
branches:
- main
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.10"
- uses: aws-actions/setup-sam@v2
- uses: aws-actions/configure-aws-credentials@v4
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: ${{ secrets.AWS_REGION }}
- run: sam build
- run: sam deploy --no-confirm-changeset --no-fail-on-empty-changeset
What does this workflow do?
- on: push: branches: [main] triggers the workflow every time code is pushed to the main branch.
- actions/checkout@v4 clones your repository onto the runner.
- actions/setup-python@v5 installs Python 3.10 to match your Lambda runtime.
- aws-actions/setup-sam@v2 installs the AWS SAM CLI on the runner.
- aws-actions/configure-aws-credentials@v4 injects your AWS secrets as environment variables so SAM can deploy.
- sam build packages the Lambda function, and sam deploy --no-confirm-changeset --no-fail-on-empty-changeset deploys without waiting for manual approval.
- Save deploy.yml.
✔️ Awesome, I've got everything!
Great. Double check you have saved .github/workflows/deploy.yml and the file is in the correct directory.
ⓧ I'd like to double check the full code
Here is the complete deploy.yml file:
name: Deploy SAM Application
on:
push:
branches:
- main
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.10"
- uses: aws-actions/setup-sam@v2
- uses: aws-actions/configure-aws-credentials@v4
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: ${{ secrets.AWS_REGION }}
- run: sam build
- run: sam deploy --no-confirm-changeset --no-fail-on-empty-changeset
Now push all your project files to GitHub. This first push to main will trigger the pipeline.
- Stage, commit, and push your code by running these commands:
git add .
git commit -m "Add SAM project and CI/CD workflow"
git push origin main
- Open your repository on GitHub in your browser.
- Under your repository name, click the Actions tab.
- Click the Deploy SAM Application workflow in the left sidebar.
- Click the most recent workflow run to see the summary.
You should see the workflow running with a yellow spinner, or completed with a green checkmark if it has already finished.
Pipeline failed?
Click the failed job to expand the logs. Common causes:
- Secrets misspelled. Double check that the secret names are exactly AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, and AWS_REGION.
- Invalid credentials. Make sure the Access Key ID and Secret Access Key you pasted are correct and active in your AWS account.
- Region mismatch. The region secret must be us-east-1 to match your deployed stack.
My GitHub Actions pipeline is failing during SAM deploy. Help me debug it.
Verify end-to-end CI/CD
The pipeline should have deployed your stack successfully. Now you will confirm that future pushes also trigger automatic deployments by making a small code change.
- In src/app.py, find the system_prompt string inside the generate_description function.
- Add Keep it under 50 words. to the end of the prompt text (before the closing parenthesis).
- Save src/app.py.
- Push the change by running these commands:
git add .
git commit -m "Update system prompt for shorter descriptions"
git push origin main
- Switch back to the Actions tab in your browser and watch the new workflow run start automatically.
- Wait for the run to complete with a green checkmark.
Once the pipeline finishes, test that your updated code is live.
- Call your API endpoint again by running this command (replace the URL with your API Gateway endpoint from Step 4):
curl -X POST [[API_ENDPOINT="your API Gateway endpoint URL"]] \
-H "Content-Type: application/json" \
-d '{"product_name": "CloudBrew Coffee Maker", "category": "Kitchen Appliances"}'
You should receive a JSON response with a shorter, updated product description. This confirms the pipeline deployed your code change automatically.
Why is this powerful?
You now have a fully automated deployment pipeline. Every push to main triggers a build and deploy without any manual intervention. This is the same pattern used by production engineering teams at companies like Amazon.
- Revert the system prompt change so your code stays in its original state. Remove the Keep it under 50 words. text you added, save the file, and push again:
git add .
git commit -m "Revert system prompt to original"
git push origin main
Not seeing the updated response?
Make sure the second pipeline run completed with a green checkmark before testing. If the response still seems the same, remember that AI-generated text varies on each call. The key confirmation is that the pipeline ran successfully.
My CI/CD pipeline ran but the API response does not seem to reflect my code change.
Your serverless microservice now deploys itself. Every push to main builds, packages, and ships your Lambda function to AWS without you lifting a finger. Next up, take on the Secret Mission to add a retrieval endpoint and structured logging for production-grade observability.
Secret mission
Add Retrieval Endpoint and Structured Logging
Your API can generate descriptions, but can it retrieve them later? And when something goes wrong in production, how would you debug it? Add a GET endpoint for retrieval and structured CloudWatch logging for operational visibility.
Clean Up Your Resources
Clean Up Your Resources
Decide whether to keep your resources running, pause them to come back later, or delete them entirely. Your serverless stack costs nothing at rest. You only pay for Bedrock calls when you invoke the API.
Cost warning
Lambda and DynamoDB are within the AWS Always Free tier. API Gateway is free for 12 months (1 million calls per month).
Amazon Bedrock charges per token each time you invoke the API. Your test calls cost fractions of a cent, but leaving an automated process calling the endpoint could accumulate charges.
Resources you used:
- CloudFormation stack (ai-product-description-generator) containing your Lambda function, API Gateway, DynamoDB table, and IAM roles.
- SAM deployment S3 bucket (auto-created by SAM CLI for storing deployment artifacts).
- GitHub repository with configured secrets (AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_REGION).
- Local project files on your Desktop.
Keep everything running
No action needed. Choose this if you want to keep calling your API or plan to extend the project further.
- Your Lambda function, DynamoDB table, and API Gateway endpoint remain deployed but cost nothing at rest.
- You only pay when you invoke the endpoint (Bedrock per-token charges).
- Your GitHub Actions pipeline continues to auto-deploy on push to main.
Pause - I'll come back to this later
No action needed. Serverless resources have zero idle cost, so pausing and keeping are effectively the same.
- Your stack remains deployed but generates no charges unless you invoke the API.
- Your GitHub Actions workflow only runs when you push to main, so it costs nothing while idle.
Delete - I don't want to use this again
Remove all project resources and start fresh. Follow each step below to fully clean up.
- Delete the CloudFormation stack (this removes your Lambda function, API Gateway, DynamoDB table, and IAM roles) by running:
aws cloudformation delete-stack --stack-name ai-product-description-generator
- Find the SAM deployment S3 bucket by listing your buckets:
aws s3 ls
- Look for a bucket name containing aws-sam-cli-managed-default in the output. Delete it by running:
aws s3 rb s3://[[SAM_BUCKET="your-sam-bucket-name-from-the-output-above"]] --force
- Delete the GitHub repository. Go to your repository on GitHub.
- Click Settings.
- Scroll to the bottom of the General page and find the Danger Zone section.
- Click Delete this repository and follow the confirmation prompts.
- Delete the local project files by running:
macOS
cd ~/Desktop
rm -rf ai-product-description-generator
Windows
cd ~\Desktop
rmdir /s /q ai-product-description-generator
That's a Wrap!
That's a Wrap!
Nice work! You just built and deployed a production-grade serverless microservice on AWS that generates AI-powered product descriptions on demand.
You've learned how to:
- Build a serverless REST API using AWS Lambda, API Gateway, and DynamoDB to handle requests, generate content, and persist data.
- Integrate Amazon Bedrock (Nova Lite) to generate AI-powered product descriptions using the Converse API.
- Define all infrastructure as code with AWS SAM and automate deployments with a GitHub Actions CI/CD pipeline that deploys on every push to main.
- Secret Mission: Added a GET retrieval endpoint and structured CloudWatch logging for operational excellence.
Ready to quiz yourself?