Capstone: Reviewing Against Well-Architected, Cleanup, and Extensions
You've reached the end
The previous twenty articles built piece by piece: from a first Lambda function to a product with an API, data, authentication, events, realtime, orchestration, observability, security, CI/CD, and measured under load. This last article puts the picture back together, reviews it with a standard framework, then talks about cleanup and the next steps.
The whole thing, built
┌──────────── Cognito (JWT) ────────────┐
Browser ──HTTPS──▶ API Gateway HTTP API (throttle) │ authentication
│ │ │ │
│ POST /links ───┼──▶ create-link ──▶ DynamoDB single-table (+GSI1, TTL)
│ GET /links ────┼──▶ list-links ──▶ ▲
│ DELETE /links ─┼──▶ delete-link ──────┘
│ GET /{code} ───┼──▶ resolve ──┬─▶ 301
│ │ │ └─▶ EventBridge ─▶ SQS(+DLQ) ─▶ aggregator
│ │ │ │
│ │ │ Step Functions (moderation) ▼
│ │ │ count + push
└─WebSocket──────────────────────────────────────────◀── realtime dashboard
Observability: Powertools (log/trace/metric) · X-Ray · CloudWatch alarm + dashboard
Operations: SAM · CI (GitHub Actions) · CD canary + rollback (CodeDeploy)
None of the boxes is a server you have to keep alive. When no one is using it, the bill goes near zero.
Reviewing against Well-Architected
The AWS Well-Architected Framework evaluates an architecture through five pillars. Going through each shows how far the series reached, and what's left to do.
Operational Excellence. The entire infrastructure is code in one SAM template, versioned with the repo, stood up and torn down with one command. CI builds and validates every push, CD uses canary with an observation window and a way back. The system is observable through structured logs, X-Ray traces, metrics, and alarms. Still missing: runbooks for incidents and automatic deploy via OIDC instead of from the machine.
Security. Authentication with Cognito JWT right at the gateway; data partitioned per user and IDOR protection at the write layer; IAM tightened to least-privilege per function; throttle against flooding; secrets belong to Parameter Store. Still missing: WAF (needs CloudFront in front for HTTP API), and CORS should be narrowed from * to the actual dashboard domain.
Reliability. Events through SQS with retry and DLQ so none are lost; idempotent counting so it's not wrong on duplicate delivery; an atomic counter safe under concurrency; and the load test showed the system degrades gracefully, shedding excess load instead of collapsing. DynamoDB and Lambda inherently run across multiple Availability Zones (multi-AZ) within a Region, managed by AWS already. Still missing: raising the concurrency quota so it doesn't shed load as early as it does now.
Performance Efficiency. arm64 for cheap, fast compute; memory tunable to cut cold start; single-table with sparse GSI for single-query reads instead of joins; on-demand contracts with load. Still missing: with stricter cold-start requirements, consider provisioned concurrency (after raising the quota) or writing hot functions in a runtime that supports SnapStart.
Cost Optimization. Pay per use, idle goes to zero, and the real bill for the whole series is near zero thanks to the free tier plus the HTTP API, arm64, on-demand choices. Still missing: cost tags and a budget alarm when moving into real production.
This framework isn't for scoring but for surfacing what's still empty, and each "still missing" above is a concrete task for the next iteration.
Cleanup: one command
Because everything lives in one CloudFormation stack, wiping the entire product clean is just:
sam delete --stack-name url-shortener --no-prompts --region ap-southeast-1
This command deletes the Lambda functions, the HTTP API and WebSocket API, the DynamoDB table, the user pool, the event bus, the queue, the state machine, the alarms, the dashboard, and the associated IAM roles. Afterward, check that no resources remain that incur charges. An empty on-demand DynamoDB table or a few uncalled Lambda functions cost almost nothing, but the habit of deleting the stack when you don't need it is the surest way to avoid a surprise bill. If you want to keep it around to explore further, just leave it; the idle cost is near zero.
Extensions
The product already runs for real, but there are a few clear steps to take it further.
Custom domain and WAF. Put a CloudFront distribution in front of the HTTP API, attach the sho.rt domain via an ACM certificate, and attach a WAF web ACL to CloudFront. This both gives nicer short links and fills the exact WAF gap left in the security pillar.
A real dashboard. The series built the backend of realtime; the front end needs a small web page that opens the WebSocket on load and shows the list of links with click counts ticking in real time. This is where the WebSocket part (Article 11) joins a UI.
Real safety scanning. The moderation state machine (Article 12) currently uses a blocklist of words; replacing it with a call to a real URL-scanning service (like Google Safe Browsing) is a tidy upgrade, and the place to apply the secret in Parameter Store.
Multi-region. With higher availability requirements, DynamoDB Global Tables replicate data to another region, and Route 53 routes by latency to the API in each region.
Each direction is one iteration, built on the foundation already there, not a redo.
Series wrap-up
We went from "how Lambda runs code" to a complete serverless product, operated like production. Along the way, each article solved a real problem and measured it with real numbers: cold start, single-table design, idempotency, realtime, orchestration, observability, security, safe deploy, cost, and behavior under load. The throughline is the serverless way of thinking: composing small AWS-managed components with events, so what you write contracts with demand and costs almost nothing while idle.
Thanks for following the whole series. The full code is at github.com/nghiadaulau/serverless-url-shortener-aws, to use as a foundation to build on.
You've reached the end