Building Cloud Applications with Go and AWS Lambda
As a associate system administrator I worked on Redhat Linux servers, including user management, permissions, services, and performance monitoring Automated routine administrative tasks using Bash scripting and cron jobs, reducing manual effort by ~30% I am aws certified sysops administrator and Google Certified Cloud Engineer. Determined to transition my career into cloud architect /Cloud Support role
Introduction:
Cloud computing has become the foundation of modern applications, providing scalability, reliability, and operational efficiency. For Go developers, AWS offers a powerful set of services and tools to build high-performance, cloud-native solutions.
This article introduces how to work with AWS in Go using the AWS SDK v2, focusing on three essential services: Amazon S3, DynamoDB, and AWS Lambda.
1. Getting Started with AWS and Go
The AWS SDK for Go v2 provides an idiomatic interface for working with AWS services. To begin, configure the SDK and create a client:
cfg, err := config.LoadDefaultConfig(context.TODO(),
config.WithRegion("us-west-2"))
if err != nil {
log.Fatalf("config error: %v", err)
}
s3Client := s3.NewFromConfig(cfg)
Best practice: Use IAM roles or environment variables for authentication instead of hardcoding access keys.
2. Amazon S3 for Object Storage
Amazon S3 offers secure, durable, and highly scalable object storage. Common use cases include storing files, backups, and media assets.
Uploading an object:
_, err := client.PutObject(context.TODO(), &s3.PutObjectInput{
Bucket: aws.String(bucketName),
Key: aws.String("example.txt"),
Body: bytes.NewReader(content),
})
Generating a presigned URL:
url, err := presignClient.PresignGetObject(
context.TODO(),
&s3.GetObjectInput{Bucket: &bucket, Key: &key},
)
This allows secure, temporary access to private objects without exposing credentials.
3. DynamoDB and AWS Lambda
DynamoDB
DynamoDB is a fully managed NoSQL database optimized for performance and scale. Querying an item is straightforward:
result, err := client.GetItem(context.TODO(), &dynamodb.GetItemInput{
TableName: aws.String("users"),
Key: map[string]types.AttributeValue{
"user_id": &types.AttributeValueMemberS{Value: userID},
},
})
AWS Lambda
AWS Lambda allows you to run code without managing servers. Go functions benefit from low cold-start latency, making them suitable for event-driven applications.
Minimal Lambda function:
func handle(ctx context.Context, req events.APIGatewayProxyRequest) (Response, error) {
return Response{StatusCode: 200, Body: "Hello from Go Lambda"}, nil
}
func main() {
lambda.Start(handle)
}
Conclusion
Go and AWS form a powerful combination for building cloud-native applications. With S3 for storage, DynamoDB for fast data access, and Lambda for serverless execution, developers can build applications that are scalable, performant, and resilient.
As a practical exercise, try creating an application that:
Uploads a file to S3
Stores metadata in DynamoDB
Invokes a Lambda function to process the file
This simple workflow highlights how effectively Go integrates with AWS to deliver production-ready cloud solutions.
Let’s implement above lambda by creating a demo application using only AWS CLI commands. This will walk through a simple workflow:
Upload a file to Amazon S3
Store related metadata in DynamoDB
Invoke a Lambda function to process the file
Demo: File Processing Workflow with AWS CLI
Prerequisites
AWS CLI installed and configured (
aws configure)An AWS account with permissions for S3, DynamoDB, and Lambda
A local test file (e.g.,
example.txt)
Step 1: Create an S3 Bucket and Upload a File
# Create an S3 bucket (bucket names must be globally unique)
aws s3 mb s3://my-demo-bucket-12345
# Upload a file to the bucket
aws s3 cp example.txt s3://my-demo-bucket-12345/
Step 2: Create a DynamoDB Table and Store Metadata
# Create a DynamoDB table for file metadata
aws dynamodb create-table \
--table-name FileMetadata \
--attribute-definitions AttributeName=FileName,AttributeType=S \
--key-schema AttributeName=FileName,KeyType=HASH \
--billing-mode PAY_PER_REQUEST
# Insert metadata for the uploaded file
aws dynamodb put-item \
--table-name FileMetadata \
--item '{
"FileName": {"S": "example.txt"},
"Bucket": {"S": "my-demo-bucket-12345"},
"UploadTimestamp": {"S": "'$(date -u +%Y-%m-%dT%H:%M:%SZ)'"}
}'
Step 3: Create and Invoke a Lambda Function
3.1. Create a Lambda execution role
aws iam create-role \
--role-name LambdaS3DynamoRole \
--assume-role-policy-document '{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": {"Service": "lambda.amazonaws.com"},
"Action": "sts:AssumeRole"
}]
}'
# Attach basic permissions
aws iam attach-role-policy \
--role-name LambdaS3DynamoRole \
--policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
3.2. Create the Lambda function
Create a file lambda_function.py:
import json
def lambda_handler(event, context):
print("Event received:", event)
return {
"statusCode": 200,
"body": json.dumps("File processed successfully!")
}
Zip and deploy:
zip function.zip lambda_function.py
aws lambda create-function \
--function-name ProcessFileLambda \
--runtime python3.9 \
--role arn:aws:iam::<YOUR_ACCOUNT_ID>:role/LambdaS3DynamoRole \
--handler lambda_function.lambda_handler \
--zip-file fileb://function.zip
3.3. Invoke the Lambda function
Pass file details to the function:
aws lambda invoke \
--function-name ProcessFileLambda \
--payload '{"bucket":"my-demo-bucket-12345","key":"example.txt"}' \
response.json
cat response.json
Summary
With just a few CLI commands, we:
Uploaded a file to Amazon S3
Saved metadata into DynamoDB
Invoked a Lambda function to process the file
This forms the foundation of a scalable serverless workflow.
Step 3: Create and Invoke a Lambda Function
3.1. Create a Lambda execution role
aws iam create-role \
--role-name LambdaS3DynamoRole \
--assume-role-policy-document '{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": {"Service": "lambda.amazonaws.com"},
"Action": "sts:AssumeRole"
}]
}'
aws iam attach-role-policy \
--role-name LambdaS3DynamoRole \
--policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
3.2. Write the Go Lambda function
Create a file main.go:
package main
import (
"context"
"encoding/json"
"fmt"
"github.com/aws/aws-lambda-go/events"
"github.com/aws/aws-lambda-go/lambda"
)
type Response struct {
StatusCode int `json:"statusCode"`
Body string `json:"body"`
}
func handler(ctx context.Context, event map[string]interface{}) (Response, error) {
bucket := event["bucket"]
key := event["key"]
msg := fmt.Sprintf("Processing file %v from bucket %v", key, bucket)
return Response{
StatusCode: 200,
Body: msg,
}, nil
}
func main() {
lambda.Start(handler)
}
3.3. Build and package the Go binary
Make sure you build for Linux (Lambda runtime):
GOOS=linux GOARCH=amd64 go build -o bootstrap main.go
zip function.zip bootstrap
3.4. Deploy the Lambda function
aws lambda create-function \
--function-name ProcessFileLambda \
--runtime provided.al2 \
--role arn:aws:iam::<YOUR_ACCOUNT_ID>:role/LambdaS3DynamoRole \
--handler bootstrap \
--zip-file fileb://function.zip
3.5. Invoke the Lambda function
aws lambda invoke \
--function-name ProcessFileLambda \
--payload '{"bucket":"my-demo-bucket-12345","key":"example.txt"}' \
response.json
cat response.json
✅ Now you have a Go-powered Lambda function that accepts file details, processes them, and returns a response.
let’s modify the Lambda so it doesn’t just echo the S3 file info but also queries the DynamoDB FileMetadata table we created earlier.
This way, when you invoke the Lambda with a bucket and key, it will:
Read the metadata from DynamoDB for that file.
Return the metadata in the response.
Updated Lambda (main.go)
package main
import (
"context"
"encoding/json"
"fmt"
"github.com/aws/aws-lambda-go/lambda"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue"
"github.com/aws/aws-sdk-go-v2/service/dynamodb"
"github.com/aws/aws-sdk-go-v2/service/dynamodb/types"
)
type Event struct {
Bucket string `json:"bucket"`
Key string `json:"key"`
}
type FileMetadata struct {
FileName string `dynamodbav:"FileName" json:"file_name"`
Bucket string `dynamodbav:"Bucket" json:"bucket"`
UploadTimestamp string `dynamodbav:"UploadTimestamp" json:"uploaded_at"`
}
type Response struct {
StatusCode int `json:"statusCode"`
Body interface{} `json:"body"`
}
func handler(ctx context.Context, event Event) (Response, error) {
// Load AWS config
cfg, err := config.LoadDefaultConfig(ctx)
if err != nil {
return Response{StatusCode: 500, Body: "failed to load AWS config"}, err
}
client := dynamodb.NewFromConfig(cfg)
// Query DynamoDB for file metadata
result, err := client.GetItem(ctx, &dynamodb.GetItemInput{
TableName: aws.String("FileMetadata"),
Key: map[string]types.AttributeValue{
"FileName": &types.AttributeValueMemberS{Value: event.Key},
},
})
if err != nil {
return Response{StatusCode: 500, Body: "failed to query DynamoDB"}, err
}
if result.Item == nil {
return Response{StatusCode: 404, Body: fmt.Sprintf("No metadata found for %s", event.Key)}, nil
}
var metadata FileMetadata
err = attributevalue.UnmarshalMap(result.Item, &metadata)
if err != nil {
return Response{StatusCode: 500, Body: "failed to unmarshal DynamoDB item"}, err
}
return Response{
StatusCode: 200,
Body: metadata,
}, nil
}
func main() {
lambda.Start(handler)
}
Build and Deploy
# Build for Lambda runtime
GOOS=linux GOARCH=amd64 go build -o bootstrap main.go
zip function.zip bootstrap
# Update Lambda code
aws lambda update-function-code \
--function-name ProcessFileLambda \
--zip-file fileb://function.zip
Invoke with CLI
aws lambda invoke \
--function-name ProcessFileLambda \
--payload '{"bucket":"my-demo-bucket-12345","key":"example.txt"}' \
response.json
cat response.json
Output:
{
"statusCode": 200,
"body": {
"file_name": "example.txt",
"bucket": "my-demo-bucket-12345",
"uploaded_at": "2025-09-21T12:34:56Z"
}
}
Conclusion
Amazon S3 provides a reliable, scalable storage layer for uploading and managing files.
DynamoDB allows you to store and retrieve file metadata with low latency and automatic scaling.
AWS Lambda (Go runtime) enables serverless processing of files, integrating directly with S3 and DynamoDB.
The demo workflow showed how to:
Upload a file to S3
Store metadata in DynamoDB
Invoke a Go Lambda function to query and return metadata
Using the AWS CLI, you can set up and test this workflow quickly without additional infrastructure.
This pattern is a foundation for building scalable, event-driven applications such as file processors, data pipelines, or content management systems.
