Full Stack App
React + Lambda + DynamoDB URL shortener
Architecture
- Visitor / SPA — Anyone clicking a short link (hot path), plus the React admin SPA for creating and managing links.
- CloudFront — One distribution, two behaviors: static SPA from S3, everything else (short codes + /api/*) to API Gateway. Custom short domains attach here as aliases with ACM certs.
- SPA Assets — Private bucket with the built React admin app, served via Origin Access Control.
- API Gateway (HTTP API) — GET /{code} routes to the redirect function with no authorizer (redirects must be anonymous). /links and /domains routes use the Cognito JWT authorizer.
- Cognito User Pool — Accounts for people who create and manage links. Following a link never requires auth.
- Redirect Fn (hot path) — One GetItem, one 301. Fires a link.clicked message to SQS without awaiting it — no synchronous writes on the hot path. 128 MB, provisioned concurrency in prod.
- Links API Fn — CRUD for links and custom domains: validates URLs, generates or reserves codes (conditional Put), lists a user's links via the owner GSI.
- links table — On-demand. PK = short code for O(1) redirect lookups; expires_at is the DynamoDB TTL attribute so dead links vanish for free. GSI1 (owner_id, created_at) lists "my links". Daily click rollups live alongside.
- click-events queue — Buffers link.clicked events so the redirect path never waits on analytics. DLQ after 3 receives.
- Analytics Fn — Consumes clicks in batches of up to 100, aggregates per code per day, then a handful of UpdateItem ADDs: total click_count on the link plus a clicks_daily rollup row.
Event flow
- Redirect Fn — Fires one link.clicked per successful 301; send is fire-and-forget so the hot path never waits.
- click-events — Standard SQS queue; at-least-once, so the consumer aggregates idempotently. DLQ after 3 receives.
- Analytics Fn — Batch size 100 / 30s window. Aggregates per (code, UTC day) then UpdateItem ADDs the rollups in the links table.
- link.clicked — Emitted by the Redirect Fn on every successful redirect, without awaiting the send. No PII: IP is salted-hashed for rough uniqueness only.
About this design
- ## What this is A serverless URL shortener with the shape of a real product: a hot-path redirect Lambda in front of DynamoDB, TTL-based link expiry, asynchronous click analytics over SQS, a Cognito-protected management API, and optional custom short domains. A React admin SPA (served from S3 via CloudFront) is where links get created; following a link needs no account and touches almost nothing. The design optimises exactly one thing: **GET /{code} must be one read and one 301**. Everything else — counting, rollups, ownership, expiry — is arranged so it never adds latency to that path. ## How it works - **Redirect hot path** — CloudFront forwards `/{code}` to API Gateway (no authorizer), the Redirect Fn does a single `GetItem` on the links table (PK = code), sends a `link.clicked` message to SQS *without awaiting the result*, and returns `301 Location: long_url`. Missing codes return 404; TTL-expired ones 410. - **Expiry** — `expires_at` is the DynamoDB TTL attribute. Expired links are deleted by DynamoDB itself; the Redirect Fn also checks the timestamp so links that expired seconds ago (TTL deletion lags) still return 410. - **Analytics** — the Analytics Fn consumes SQS batches (up to 100 clicks), aggregates per code per UTC day, and issues a few `UpdateItem ADD`s: total `click_count` on the link plus a `clicks_daily` rollup row with referrer/country maps. Stats queries never scan raw events. A DLQ catches poison messages. - **Management** — `/links` and `/domains` sit behind a Cognito JWT authorizer. Creating a link is a conditional Put (`attribute_not_exists(code)`), so custom-code collisions surface as 409 instead of silent overwrites. GSI1 `(owner_id, created_at)` lists "my links" newest first. - **Custom domains** — a `domains` row tracks DNS verification and the ACM cert; verified domains attach to CloudFront as aliases, and codes carry a `domain` attribute. The sequence diagram traces the redirect hot path end to end, including the async aggregation leg; the event flow shows the `link.clicked` contract. ## How to extend - **Shave the hot path further**: move redirects to CloudFront Functions/Lambda@Edge with DynamoDB global tables, or add short-TTL CloudFront caching per code. - **Richer analytics**: tee `link.clicked` to Kinesis Firehose → S3 and query raw events with Athena; add bot filtering before counting. - **Product features**: QR codes per link, password-protected links, per-owner rate limits (WAF), link preview/unfurl metadata. - **Abuse controls**: check `long_url` against Safe Browsing on create, and add a `disabled` flag the Redirect Fn honours.