Serverless in Practice on AWS: URL Shortener + Realtime Analytics
Build a complete serverless product on AWS from scratch: a URL shortening service with realtime analytics. The series does not teach each service in isolation; it builds one production-ready backend end to end — Lambda, API Gateway, DynamoDB single-table, Cognito, EventBridge, Step Functions, WebSocket API — then operates it for real: idempotency, DLQ, X-Ray tracing, cold start, IAM least-privilege, CI/CD canary, cost analysis and load testing. All infrastructure is built with AWS SAM, code in Node.js + TypeScript, every command run for real on AWS, code at github.com/nghiadaulau/serverless-url-shortener-aws. Grounded in the official AWS documentation.
What Serverless Is and When to Use It
Series opener: what serverless actually means (not 'no servers'), what it trades away and what it gains, when NOT to use it, and the product we build throughout — a URL shortening service with realtime analytics. Includes the overall architecture diagram and a per-part roadmap.
Setting Up the Environment: AWS SAM and Your First Lambda Function
Install the AWS SAM CLI, build a project skeleton for the URL shortener (TypeScript + esbuild), then deploy a real Lambda function to AWS via a Function URL, call it with curl, run it locally in Docker, and clean up with one command. Plus a lesson on the SAM CLI needing to be new enough to support the nodejs22.x runtime.
How Lambda Runs Your Code Internally
Dissect Lambda's execution environment lifecycle: the three phases Init, Invoke, Shutdown, why cold start exists, and how static code runs once. Measure cold start for real (Init Duration), then measure the memory–CPU relationship with the same CPU work at 128 MB and 1769 MB. Understand this so later performance and cost decisions have a basis.
API Gateway: HTTP API or REST API, and Building the First Routes
API Gateway has two API types with two feature sets and two price tiers. Compare HTTP API and REST API to pick the right one for the URL shortener, then build two real routes: POST /links to create a link and GET /{code} for a 301 redirect. Handle validation, path parameters, and CORS preflight so the dashboard article later doesn't break.
DynamoDB Single-Table Design: Start From the Questions, Not the Table
DynamoDB design runs opposite to a relational database: you start from the query questions, not from tables. This article lists the URL shortener's access patterns, explains what partition key and sort key actually do, then builds a single-table with an item collection — a link and its click stats live in the same partition and come back in one query. Create a real table, put and query real items to see the mechanics.
Global Secondary Index and Sparse Index: Opening a New Query Path
The single-table from the previous article answers 'open a link by code' fast, but is helpless at 'list a user's links'. This article adds a global secondary index to invert the key and open exactly that query path, then uses a sparse index so the index holds only links and naturally drops the stat records. Create a real GSI, run a real query to see the mechanics.
Wiring DynamoDB Into Code: Safe Writes and Atomic Counter
Wire the two handlers into the real DynamoDB: create-link writes an item with a conditional write so it never overwrites a duplicate code, resolve-link does a lookup then counts clicks with an atomic counter. Fire many opens in parallel to see the atomic counter count exactly, and hit a real account limit — Lambda concurrency — when 40 of 50 requests come back 503.
Cognito and JWT Authorizer: Only Logged-In Users Can Create Links
Add real users with Amazon Cognito. Stand up a user pool that issues JWTs, attach the HTTP API's JWT authorizer to protect the create-link route while the open-link route stays public, and have the handler read the user identity from a claim in the token instead of hard-coding it. Create a real user, get a real token, call the API with and without a token to see the boundary.
Multi-Tenant: Each User Their Own Data Slice, and Blocking IDOR
Turn the URL shortener into a true multi-tenant system. Add a list-links route scoped to the identity in the token, and a delete-link route that checks ownership inside the write operation so one user can't delete another's link even if they guess the code right. Tested with two real users to see the boundary hold.
EventBridge: Decoupling Click Recording from the Redirect Path
Opening the event-driven part. Instead of counting clicks right inside the redirect handler, every link open publishes an event onto a custom EventBridge event bus, and a separate consumer handles it. Build the bus, have resolve publish the event, attach a consumer via an event pattern, then open a real link to watch the event flow through the bus to the consumer.
Counting Clicks Safely: Idempotency, DLQ, and Partial Batch Failure
Turn the logging consumer from the previous article into a real aggregator. Insert SQS between EventBridge and Lambda for batching, retry, and a dead-letter queue, count clicks into DynamoDB with a transaction that both increments counters and prevents double-counting in one atomic operation, and report failures per message so only the broken one is retried. Real tests: a duplicate event counts once, a failing event lands in the DLQ.
WebSocket API: Pushing Click Counts to a Realtime Dashboard
Build the realtime part of the dashboard. Stand up an API Gateway WebSocket API with $connect and $disconnect routes, store each connection in DynamoDB tied to the link it watches, then have the aggregator push the new click count down to exactly the open connections each time it finishes counting. Open a real connection, click the link, and watch the number jump to the browser with no reload.