π Languages: English | EspaΓ±ol
A REST API built with Go and Gin Framework, designed to run both as an AWS Lambda function (using Docker containers) and as a traditional HTTP service for local development.
AWS Lambda is a serverless computing service that executes code in response to events, without the need to provision or manage servers. When we talk about "running Lambda as API", we refer to using Lambda functions as the backend of a REST API, where each HTTP request becomes a Lambda function invocation.
- Client β sends HTTP request to a URL
- API Gateway β receives the request and transforms it into a Lambda event
- Lambda Function β processes the event and returns a response
- API Gateway β converts the Lambda response to HTTP format
- Client β receives the standard HTTP response
graph LR
A[Client] --> B[API Gateway]
B --> C[Lambda Function]
C --> D[Process Request]
D --> C
C --> B
B --> A
- Pay per use: Only pay for actual execution time (billed by milliseconds)
- No fixed costs: No servers running 24/7 consuming resources
- Scale to zero: When there's no traffic, the cost is $0
- Comparison: An API with 1M requests/month can cost ~$20 vs ~$50-200 on EC2
- Auto-scaling: Handles from 1 to thousands of concurrent requests automatically
- No configuration: No need to configure load balancers or auto-scaling groups
- Instant response: Adapts to traffic spikes without manual intervention
- Concurrency: Up to 1,000 concurrent executions by default (scalable)
β
REST/GraphQL APIs with variable traffic
β
Microservices and event-driven architectures
β
Applications with unpredictable usage patterns
β
Startups and MVPs with limited budget
β
APIs requiring high availability without management
- Cold starts: First invocation can take 100-1000ms (mitigable with SnapStart)
- Time limit: Maximum 15 minutes execution per invocation
- Memory limit: Maximum 10GB RAM per function
- Payload limit: 6MB for synchronous requests
This application is a REST API that provides basic endpoints such as health check, echo, and server time. It's optimized for deployment on AWS Lambda using Docker containers and features an automated CI/CD pipeline with GitHub Actions.
The application uses a hybrid pattern that allows:
- Production: Execution as AWS Lambda function through API Gateway
- Development: Traditional HTTP server for local testing
aws-lambda-go/
βββ .github/workflows/ # CI/CD Pipeline with GitHub Actions
β βββ main.yml # Build and deploy workflow
βββ cmd/ # Application entry point
β βββ main.go # Hybrid startup logic (Lambda/HTTP)
βββ docker_images/ # Docker configurations
β βββ deploy/ # Dockerfile for AWS Lambda
β β βββ Dockerfile.deploy
β βββ local/ # Dockerfile for local development
β βββ Dockerfile.local
βββ internal/ # Internal application code
β βββ middleware/ # Custom middlewares
β β βββ logger.go # Logging and observability middleware
β βββ router/ # Route configuration
β βββ router.go # Endpoint and handler definitions
βββ compose.yml # Docker Compose for development
βββ go.mod # Project dependencies
βββ go.sum # Dependencies lock file
The application automatically detects if it's running in AWS Lambda using the AWS_LAMBDA_FUNCTION_NAME
environment variable:
if _, inLambda := os.LookupEnv("AWS_LAMBDA_FUNCTION_NAME"); inLambda {
lambda.Start(ginadapter.NewV2(r).ProxyWithContext)
return
}
Features:
- Uses
aws-lambda-go-api-proxy
to adapt Gin to Lambda - Deployed as Docker image in ECR
- Invocation through API Gateway
- Auto-scaling and pay-per-use billing
When Lambda environment is not detected, the application starts a traditional HTTP server:
port := os.Getenv("PORT")
if port == "" {
port = "8080"
}
r.Run(":" + port)
Features:
- Direct HTTP server on configurable port (default: 8080)
- Ideal for development and local testing
- Detailed logs with custom middleware
# Multi-stage build for AWS Lambda
FROM amazonlinux:2 as builder
# ... static compilation
FROM public.ecr.aws/lambda/go:1
COPY --from=builder /app/app ${LAMBDA_TASK_ROOT}
CMD ["app"]
Optimizations:
- Static build (
CGO_ENABLED=0
) - Official AWS Lambda base image for Go
- Reduced size with flags
-ldflags="-s -w"
# Multi-stage build for local development
FROM golang:1.24-alpine AS builder
# ... compilation
FROM alpine:latest
# ... minimal final image
EXPOSE 8080
CMD ["./server"]
Features:
- Minimal Alpine image for development
- Port 8080 exposed by default
- CA certificates included for HTTPS
The compose.yml
file defines two services for different use cases:
services:
app-lambda:
build:
dockerfile: docker_images/deploy/Dockerfile.deploy
ports:
- "9000:8080"
Usage:
# Start local Lambda emulation
docker-compose up app-lambda
# Invoke function via Lambda Runtime API
curl -XPOST "http://localhost:9000/2015-03-31/functions/function/invocations" \
-d '{"httpMethod":"GET", "path":"/health"}'
services:
app-api:
build:
dockerfile: docker_images/local/Dockerfile.local
ports:
- "9000:8080"
Usage:
# Start traditional HTTP API
docker-compose up app-api
# Consume directly via HTTP
curl http://localhost:9000/health
The API exposes the following endpoints defined in internal/router/router.go
:
Endpoint | Method | Description |
---|---|---|
/ |
GET | Welcome page with API information |
/health |
GET | Health check - returns {"status": "ok"} |
/echo |
POST | Echo service - returns the sent JSON |
/time |
GET | Current server time in RFC3339 format |
# Health check
curl https://your-api-gateway-url/health
# Echo service
curl -X POST https://your-api-gateway-url/echo \
-H "Content-Type: application/json" \
-d '{"message": "Hello World"}'
# Server time
curl https://your-api-gateway-url/time
The project includes a custom middleware in internal/middleware/logger.go
:
func RequestLogger() gin.HandlerFunc {
return func(c *gin.Context) {
start := time.Now()
c.Next()
log.Printf("[REQ] %3d | %-7s | %s | %v",
c.Writer.Status(),
c.Request.Method,
c.Request.URL.Path,
time.Since(start),
)
}
}
Features:
- Logging of all requests with duration
- Consistent format:
[REQ] STATUS | METHOD | PATH | DURATION
- Automatic integration with CloudWatch Logs in Lambda
The GitHub Actions workflow (.github/workflows/main.yml
) automates the complete deployment process:
-
Trigger: Push to
main
ordevelopment
branches -
Build Stage:
- AWS credentials configuration
- ECR (Elastic Container Registry) login
- Branch to environment mapping (
main
βprod
,development
βdev
) - Docker image build using
Dockerfile.deploy
- Tag with timestamp:
{environment}-{YYYYMMDDTHHMMSSZ}
- Push to ECR with versioned and latest tags
-
Deploy Stage:
- Lambda function update with new image
- Wait for successful update confirmation
- Publish new Lambda version
BRANCH_ENV_MAP: '{"main": "prod", "development": "dev", "staging": "stg", "testing": "tst"}'
Secret | Description |
---|---|
AWS_ACCESS_KEY_ID |
AWS access key |
AWS_SECRET_ACCESS_KEY |
AWS secret key |
AWS_DEFAULT_REGION |
AWS region (e.g., us-east-1) |
ECR_REGISTRY |
ECR registry URI |
ECR_REPO_NAME |
ECR repository name |
AWS_LAMBDA_BASE_NAME |
Lambda function base name |
- ECR Images:
{ECR_REGISTRY}/{ECR_REPO_NAME}:{environment}-{version}
- Lambda Function:
{AWS_LAMBDA_BASE_NAME}-{environment}
Example:
- Image:
123456789.dkr.ecr.us-east-1.amazonaws.com/my-app:prod-20240115T143022Z
- Lambda:
my-lambda-function-prod
- Go 1.24+
- Docker and Docker Compose
# Clone repository
git clone <repository-url>
cd aws-lambda-go
# Install dependencies
go mod download
# Run locally (HTTP mode)
go run cmd/main.go
Variable | Description | Default |
---|---|---|
PORT |
Port for local HTTP mode | 8080 |
ROOT_PATH |
API base path | / |
AWS_LAMBDA_FUNCTION_NAME |
Detects Lambda mode (auto) | - |
# Traditional HTTP API
docker-compose up app-api
curl http://localhost:9000/health
# Local Lambda emulation
docker-compose up app-lambda
curl -XPOST "http://localhost:9000/2015-03-31/functions/function/invocations" \
-d '{"httpMethod":"GET", "path":"/health"}'