Weather Dashboard
A production-grade serverless weather application that fetches real-time conditions and a 5-day forecast for any city in the world. The API key never touches the browser — Lambda retrieves it from SSM Parameter Store at runtime. Results are cached in DynamoDB for 15 minutes to reduce API calls and cost. The entire stack is defined in CloudFormation, deployed through CodePipeline, and served globally via CloudFront.
Stack: AWS Lambda (Python 3.11) · API Gateway HTTP API · DynamoDB · SSM Parameter Store · CloudFront · S3 · Route 53 · ACM · CodePipeline · CodeBuild · CodeCommit · CloudFormation | Type: Serverless web app — 100% AWS-native, zero third-party CI/CD
How it works
Cache-first request flow — When a user searches for a city, the browser calls API Gateway, which proxies to Lambda. Lambda checks DynamoDB first. On a cache hit (within 15 minutes), it returns the stored result immediately — no outbound API call, sub-30 ms response. On a miss, it fetches the API key from SSM, calls OpenWeatherMap, writes the result to DynamoDB with a TTL, and returns the response (~150–250 ms).
Secure secret handling — The OpenWeatherMap API key is stored as a SecureString in SSM Parameter Store. Lambda fetches it on cold start and caches it in the execution context. The key never appears in environment variables, source code, or any browser-side resource.
Static frontend — HTML, CSS, and JavaScript are deployed to a private S3 bucket. CloudFront serves them globally using Origin Access Control (OAC) — the bucket has no public access policy and is unreachable directly. A CloudFront Function rewrites directory index paths so /about/ resolves to about/index.html.
Architecture

Compute & API
| Service | Role |
|---|---|
| AWS Lambda | Python 3.11 handler — input validation, cache check, OpenWeatherMap call, response mapping |
| API Gateway HTTP API v2 | Public HTTPS endpoint with CORS restricted to https://weather.craftingnewtech.com |
| Lambda Reserved Concurrency | Capped at 10 concurrent executions — prevents runaway API costs |
Frontend hosting
| Service | Role |
|---|---|
| Amazon S3 | Private static hosting — HTML, CSS, JS, and 18 custom SVG weather icons |
| Amazon CloudFront | Global CDN — HTTPS termination, security headers policy, directory index function |
| CloudFront Function | Rewrites /path/ to /path/index.html at the edge |
DNS & TLS
| Service | Role |
|---|---|
| Route 53 | ALIAS records — weather.craftingnewtech.com and apex craftingnewtech.com pointing to CloudFront |
| AWS Certificate Manager | TLS certificate provisioned in us-east-1 (required for CloudFront) |
Data & secrets
| Service | Role |
|---|---|
| Amazon DynamoDB | WeatherCache table — On-Demand billing, TTL attribute for automatic 15-min expiry |
| SSM Parameter Store | OpenWeatherMap API key as SecureString — fetched by Lambda, never exposed to the browser |
Observability
| Service | Role |
|---|---|
| Amazon CloudWatch | Structured Lambda logs, 5-widget dashboard, 4 alarms (errors, duration, API 4xx, API 5xx) |
| Amazon SNS | Alarm topic — CloudWatch breaches publish to SNS, triggering email alerts |
CI/CD pipeline
| Service | Role |
|---|---|
| AWS CodeCommit | Git repository — main branch triggers pipeline on push |
| AWS CodePipeline | Two-stage pipeline: Source (CodeCommit) → Build (CodeBuild) |
| AWS CodeBuild | Runs cfn-lint, checkov, pip-audit, pytest gates before deploying |
| EventBridge | Push-triggered pipeline — no polling, instant invocation on git push aws main |
Security design
No secrets in the browser — The OpenWeatherMap key lives exclusively in SSM Parameter Store. The frontend JavaScript only knows the API Gateway URL. There is no way for a user to extract the key from the browser.
Least-privilege IAM — Five separate IAM roles cover Lambda, CodeBuild, CodePipeline, CloudFormation, and EventBridge. Each role has only the permissions required for its function. No shared credentials, no wildcard resource ARNs.
S3 + OAC — The frontend bucket blocks all public access. CloudFront uses Origin Access Control (OAC) to sign requests to S3 with SigV4. Direct S3 object URLs return 403. The bucket policy permits only the CloudFront distribution’s service principal.
CORS enforcement — API Gateway allows only https://weather.craftingnewtech.com. Cross-origin calls from other domains or localhost are rejected at the API layer.
Input validation — The city name parameter is validated against a whitelist regex (^[a-zA-Z0-9\s,\-\.]{1,100}$) before any downstream call is made. SQL injection and path traversal patterns are rejected immediately.
Content Security Policy — All pages serve a strict CSP (default-src 'self', no 'unsafe-inline'). All styles are in an external stylesheet — no inline <style> blocks or style= attributes.
Pipeline security gates — cfn-lint and checkov validate CloudFormation templates. pip-audit scans Lambda dependencies for CVEs. pytest enforces ≥ 80% test coverage. Any gate failure aborts the build with on-failure: ABORT.
CI/CD pipeline
git push aws main
└─▶ CodeCommit ──▶ EventBridge ──▶ CodePipeline
│
┌──────▼──────┐
│ CodeBuild │
│ pre_build │
│ cfn-lint │
│ checkov │
│ pip-audit │
│ pytest │
└──────┬───────┘
│ (all gates pass)
┌────────────▼────────────────┐
│ build phase │
│ cfn package (Lambda zip) │
│ cfn deploy (nested stacks) │
│ s3 sync (frontend) │
│ cloudfront invalidate /* │
└─────────────────────────────┘
The CloudFront invalidation uses --invalidation-batch with CallerReference=${CODEBUILD_BUILD_ID}, making it idempotent — re-running the same build never creates duplicate invalidations.
Key design decisions
CloudFormation over Terraform — The entire project uses AWS-native IaC only. No third-party state backend, no Terraform Cloud, no external lock table. CloudFormation nested stacks organized by concern (IAM, storage, CDN, database, backend, API, pipeline, monitoring) keep each template focused and independently deployable.
DynamoDB On-Demand — No minimum capacity cost. At portfolio scale, the cache table costs effectively nothing. On-demand scales automatically if traffic spikes without capacity planning.
Lambda reserved concurrency — Capped at 10 concurrent executions. This prevents a sudden traffic spike or misconfigured client from sending thousands of calls to OpenWeatherMap and blowing through the API quota.
Vanilla JS frontend — No React, no build step for the frontend. The HTML, CSS, and JS are deployed directly to S3 as static files. This eliminates a node_modules dependency tree, reduces the attack surface for supply-chain CVEs, and makes the frontend trivially cacheable.
18 custom SVG weather icons — OpenWeatherMap’s raster PNG icons depend on an external CDN call, which the strict CSP blocks. Inline SVG icons built from OpenWeatherMap’s condition codes serve from the same origin with no external dependencies.
DynamoDB TTL for cache expiry — No cron job, no Lambda sweeper. DynamoDB TTL automatically deletes expired records. The 15-minute window balances freshness against API call volume — weather data does not change meaningfully in 15 minutes for most use cases.
Infrastructure layout
infrastructure/cloudformation/
├── 01-iam.yml # 5 least-privilege roles
├── 02-storage.yml # WebsiteBucket + ArtifactsBucket
├── 03-cdn.yml # CloudFront + OAC + CloudFront Function
├── 04-database.yml # DynamoDB WeatherCache table
├── 05-backend.yml # Lambda function + log group
├── 06-api.yml # API Gateway HTTP API + CORS
├── 07-ssm.yml # SSM parameter placeholder
├── 08-pipeline.yml # CodeCommit + CodeBuild + CodePipeline + EventBridge
├── 09-monitoring.yml # CloudWatch alarms + dashboard + SNS topic
└── master.yml # Root nested stack — deploys all 9 in dependency order
Live application

The app is deployed at weather.craftingnewtech.com.
Try searching for any city — London, Tokyo, São Paulo, or New York. Use the °C / °F toggle to switch units. The About page documents how the app works and the privacy model.