Serverless REST API built with AWS Lambda, API Gateway, DynamoDB, S3 and SQS, using Node.js + TypeScript and AWS SAM.
The entire application is developed and tested locally using Docker, SAM Local, DynamoDB Local, MinIO and ElasticMQ.
No AWS deployment is required.
The project follows a simplified Clean Architecture approach:
API Gateway → Lambda Handler → Service → Repository → DynamoDB
CSV import pipeline:
CSV → MinIO (S3) → S3 Event Bridge → Lambda → ElasticMQ (SQS) → SQS Consumer → Lambda → DynamoDB
The current S3 flow uses a small local bridge because MinIO cannot directly invoke SAM Local Lambda endpoints.
The local SQS flow uses a small consumer because ElasticMQ cannot directly trigger SAM Local Lambda endpoints.
| Method | Endpoint | Description | Status |
|---|---|---|---|
| POST | /loyalty-cards |
Create a loyalty card | ✅ |
| GET | /loyalty-cards/{id} |
Get a loyalty card by ID | ✅ |
| GET | /loyalty-cards |
Get all loyalty cards | ✅ |
- POST
/loyalty-cards - LoyaltyCard model
- LoyaltyCardService
- Input validation
- UUID generation
- Initial points
- Creation timestamp
- DynamoDB persistence
- DynamoDB Local
- Loyalty cards table
- LoyaltyCardRepository
- Create / Get / GetAll operations
- Service → Repository → DynamoDB
- Environment configuration
- Lambda permissions
- DynamoDB item mapping
- Separate handlers, services and repositories
- Application/domain errors
- HTTP error mapping
- Centralized dependency creation
- Centralized HTTP error handling if needed
- Jest
- Handler tests
- Service unit tests
- FakeLoyaltyCardRepository
- Repository integration tests
- Additional validation/error tests
- Integration tests
- MinIO configured as local S3
- CSV import Lambda
- CSV parsing
- S3 ObjectCreated events
- S3 Event Bridge
- MinIO → Bridge → SAM Local
- SQS
- SQS-triggered Lambda
- Persist imported cards through LoyaltyCardService
- Retries and failed messages
- Dead Letter Queue
- End-to-end pipeline test
- API Gateway / SAM Local
- DynamoDB Local
- MinIO
- Docker network
- Persistent DynamoDB storage
- Persistent MinIO storage
- Automatic DynamoDB table creation
- Automatic S3 bucket creation
- SAM Lambda network configuration
- Local S3 event bridge
- SQS
- SQS → Lambda consumer
- DLQ
- Complete infrastructure in
template.yaml - One-command local setup
lambda-api/
├── src/
│ ├── handlers/ # Lambda handlers
│ ├── services/ # Business logic
│ ├── repositories/ # Data access
│ ├── models/ # Domain models
│ ├── errors/ # Application/domain errors
│ ├── infrastructure/ # AWS clients & dependencies
│ ├── local/ # Local development utilities
│ └── tests/ # Unit tests & test doubles
│
├── docker/
│ ├── dynamodb/ # DynamoDB initialization
│ ├── s3/ # S3/MinIO initialization
│ └── sqs/ # Local SQS configuration
│
├── events/ # Local test events & CSV files
├── template.yaml # AWS SAM infrastructure
├── docker-compose.yml # Local AWS infrastructure
├── jest.config.ts # Jest configuration
└── package.json
- Docker
- Docker Compose
- Node.js
- npm
- AWS CLI
- AWS SAM CLI
No AWS account is required.
npm installdocker compose up -dThis starts:
- DynamoDB Local
- MinIO
- ElasticMQ (local SQS)
- Database, S3 and SQS initialization containers
Services are connected through the Docker network:
lambda-api
DynamoDB:
http://localhost:8000
MinIO S3 API:
http://localhost:9000
MinIO Console:
http://localhost:9001
ElasticMQ:
http://localhost:9324
Local AWS credentials:
Access Key: dummy
Secret Key: dummy123
Region: us-east-1
The loyalty-cards bucket, DynamoDB table and SQS queue are created automatically.
sam buildsam local start-api --docker-network lambda-apiAPI:
http://localhost:3000
Example:
curl -X POST http://localhost:3000/loyalty-cards \
-H "Content-Type: application/json" \
-d '{"customerName":"Axel"}'curl http://localhost:3000/loyalty-cards/{id}curl http://localhost:3000/loyalty-cardsThe local S3 bridge and SQS consumer invoke Lambda functions through SAM Local:
sam local start-lambda --docker-network lambda-apiSAM Lambda endpoint:
http://localhost:3001
This exposes the Lambda functions defined in template.yaml, including:
ImportLoyaltyCardsFunction
ProcessLoyaltyCardFunction
npm run local:s3-bridgeThe bridge listens on:
http://localhost:4000
It receives MinIO S3 events and invokes the corresponding Lambda through SAM Local.
The bridge is only required for the local environment. In AWS, S3 can invoke Lambda directly.
npm run local:sqs-consumerThe consumer polls the local ElasticMQ queue and invokes ProcessLoyaltyCardFunction through SAM Local.
Queue:
http://localhost:9324/000000000000/loyalty-cards
The local consumer provides the SQS → Lambda integration used during local development.
In AWS, this integration would normally be handled by an SQS event source mapping instead of the local consumer.
Upload a CSV to MinIO:
AWS_ACCESS_KEY_ID=dummy \
AWS_SECRET_ACCESS_KEY=dummy123 \
aws s3 cp events/cards.csv s3://loyalty-cards/cards.csv \
--endpoint-url http://localhost:9000 \
--region us-east-1This triggers the local S3 event flow and invokes ImportLoyaltyCardsFunction.
The Lambda reads the CSV from MinIO, parses its contents and sends each loyalty card to SQS.
Example:
Parsed CSV:
[
{ customerName: 'Axel' },
{ customerName: 'Juan' },
{ customerName: 'Maria' }
]
The SQS consumer then processes each message through ProcessLoyaltyCardFunction, which reuses LoyaltyCardService to persist the cards in DynamoDB.
npm testJest discovers tests under:
tests/**/*.test.ts
Unit tests use FakeLoyaltyCardRepository to test service logic without requiring DynamoDB.
The goal is to reproduce a serverless AWS architecture entirely locally and understand how the individual AWS components interact.
Local equivalents:
- DynamoDB Local → DynamoDB
- MinIO → S3
- ElasticMQ → SQS
- SAM Local → Lambda / API Gateway
- Docker Compose → Local infrastructure
- S3 Event Bridge → Local S3 → Lambda integration
- SQS Consumer → Local SQS → Lambda integration