Skip to content
← All patterns

general

URL Shortener

Where does this short link go?

A hash-only table keyed by short code, with an index for the reverse lookup.

The simplest single-table design: a partition key and no sort key. Each item maps a short code to a long URL, with a click count that goes up by one in a single atomic update. The table is only ever read by GetItem on the code; an index on longUrl shows whether a URL already has a code, so the app can reuse it rather than create another.

The model

ShortLink

A short code mapping to a long URL.

pk
LINK#<code>

Attributes: code (S), longUrl (S), clicks (N), createdBy (S), createdAt (S), lastClickedAt (S), isDisabled (BOOL)

Access patterns

  • GetItemResolve a short code

    Fetch the long URL for a short code.

  • Query · GSI1Find short code for URL

    Check whether a given long URL already has a short code.

  • UpdateItemIncrement click count

    Atomic ADD on clicks when a short link is resolved.

Design notes

Why hash-onlywhy

Every read of the table is a GetItem by the full key, so there is no sort key to carry. Adding one would cost storage and buy nothing for these access patterns.

A static prefix on the codewhy

Every code is stored behind LINK#, so the key template starts with fixed text. A tool that finds an entity's rows by key prefix has something to match on: with a bare code as the key there is no prefix, and a begins_with on an empty string selects the whole table.

A GSI for the reverse lookupwhy

Without GSI1, finding an existing short code for a given long URL would require a full-table scan. With KEYS_ONLY projection the GSI is cheap and returns just the composite keys - enough to resolve via a follow-up GetItem on the base table if you need the full record.

Click counter uses ADDwhy

DynamoDB's ADD expression is atomic server-side. Multiple concurrent redirects never lose a click even without conditional writes or app-side locking.

isDisabled over deletetrade-off

Keeping disabled links with isDisabled set to true preserves analytics (click history) and avoids the footgun of a collision if someone later re-registers the same short code. Delete when you're sure.

Taught in the course

url-shortenerDynamoDB workbench
Ready to run
Explore an access patternSelect to load & run

Fetch the long URL for a short code.

Request
Execute against the local Dynoxide engine
ReturnedFiltered outChanged
code(pk)
clicks
longUrl
createdAt
createdBy
isDisabled
lastClickedAt
LINK#abc123
42N
https://example.com/pricingS
—
—
—
—
LINK#k3n9qz
1284N
https://example.com/blog/single-table-designS
—
—
—
—
LINK#r7wm2p
917N
https://example.com/docs/getting-startedS
—
—
—
—
LINK#b4xh8t
63N
https://example.com/careers/engineeringS
—
—
—
—
LINK#z2fq5v
3401N
https://example.com/blog/why-we-moved-off-sqlS
—
—
—
—
LINK#m8ct6j
205N
https://example.com/changelog/2026-06S
—
—
—
—
LINK#p5vd1w
88N
https://example.com/events/summitS
—
—
—
—
LINK#t9gk4r
1502N
https://example.com/docs/api/queryS
—
—
—
—
LINK#w6bz3n
17N
https://example.com/support/contactS
—
—
—
—
LINK#h1ju7s
2260N
https://example.com/blog/hot-partitionsS
—
—
—
—
LINK#n4rp9d
9N
https://example.com/legal/termsS
—
—
—
—
LINK#c7ys2k
744N
https://example.com/downloads/cliS
—
—
—
—
LINK#nubo-home
12483N
https://nubo.app/S
2026-07-10T00:00:00ZS
user_aliceS
falseBOOL
2026-10-13T23:32:11ZS
LINK#aws-summit
842N
https://sinovi.dev/talks/aws-summit-londonS
2026-09-26T22:44:00ZS
user_martinS
falseBOOL
2026-10-13T22:12:00ZS
LINK#ddb-guide
3914N
https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/S
2026-08-01T06:15:00ZS
user_bobS
falseBOOL
2026-10-13T20:40:00ZS
LINK#std-blog
522N
https://nubo.app/blog/single-table-designS
2026-09-17T08:30:00ZS
user_carolS
falseBOOL
2026-10-13T11:42:00ZS
LINK#dead-link
17N
https://example.com/campaign-endedS
2026-05-02T00:30:00ZS
user_danielS
trueBOOL
2026-05-29T23:30:00ZS
LINK#dynamodb-book
2108N
https://www.dynamodbguide.com/S
2026-07-28T01:50:00ZS
user_elenaS
falseBOOL
2026-10-13T23:15:00ZS
Awaiting request
Your next query starts here.

Choose an access pattern above, or build your own request. See what comes back and what it costs.

Write transactions, streams, tags and TTL are among the operations this browser build leaves out. dynoxide's native build has them.