September 29, 2026

Expiring URL service — designing a small API around explicit failure modes

Built an Express URL shortener with eight-character Nano ID aliases, ephemeral Map storage, redirects, JSON lookup, timer-based expiry, and documented scale-up design.

Personal prototype
Role
Software engineer
Published
September 2026
Focus
Personal prototype
Engineer
Saaim Abdullah
Expiring URL service — designing a small API around explicit failure modes system overview

Small products still require real systems thinking

A URL shortener looks like a one-table CRUD exercise until you ask what happens after an alias expires, whether a generated ID collides, how multiple servers find the same link, what a redirect is allowed to target, and whether a reboot should invalidate existing URLs. I built a small Express/Node.js temporary-link service and used its constrained design to make those questions explicit. The implementation is intentionally simple: a JavaScript Map stores aliases within one process; Nano ID generates short keys; and a timer removes each record at expiry. Its value is a complete request lifecycle with transparent trade-offs—not an invented claim of millions of requests per second.

Numbers that are actually grounded

MetricImplementationWhy it matters
Alias length8 charactersShort links with a defined identifier size
Primary URL actions3 — create, redirect, fetch originalEnd-to-end link lifecycle and inspection
Additional operational route1 health endpointBasic process liveness check
Persistence stores1 in-process MapConstant-time-style lookup without network database overhead
Expiration scheduling1 timer per created linkSimple lifecycle behavior, bounded by process lifetime
Process durability0 after restartLinks do not survive a fresh process
That final figure is important: zero persistence across a process restart is a documented trade-off of the prototype, not an uptime measure.

Request lifecycle, step by step

ActionClient requestServer behaviorResponse / outcome
CreatePOST original destination to /api/urlGenerate 8-character ID and store mappingReturn short URL based on request host
RedirectGET the short-ID routeFind destination in MapRedirect if present; otherwise 404
InspectGET fetch endpoint for IDLook up original destinationJSON result or 404
ExpireScheduled per-link timerDelete mapping from MapFuture lookup returns 404
HealthGET health routeCheck server process is activeLiveness response

Why a Map was the correct first implementation

For a self-contained prototype, in-memory storage avoids provisioning a database and keeps the path from request to lookup easy to inspect. JavaScript Map is a natural fit for key-to-value lookup in a single event-loop process. That makes it straightforward to reason about functionality and isolate API behavior from infrastructure. But it creates clear limits: the process is the database. A restart drops all links. Two Node.js replicas would have different contents. A successful creation response does not mean the alias is durable. Describing that boundary precisely is more persuasive than treating a tiny demo as a distributed platform.

Expiry is a correctness contract, not merely a timer

The prototype schedules deletion when the link is created. This works while its process remains alive, but process timers are not durable queues and do not replace an absolute expiry timestamp. If this were a shared service, I would store expires_at and enforce expiry during every lookup. A background cleanup process could then reclaim old rows without becoming part of the correctness condition.
RequirementCurrent mechanismProduction-oriented design
Alias persistenceProcess-local MapPostgreSQL or Redis with an explicit durability policy
ExpirysetTimeout per linkStored absolute expiration + lookup-time check
Multiple replicasSeparate incompatible MapsShared store accessed by all replicas
ID collisionNano ID generated aliasUnique index + retry on duplicate key
Redirect safetyURL supplied by callerValidate HTTP(S) scheme and destination policy
Abuse protectionSimple HTTP interfaceRate limits, logging, bot/abuse response
Idempotent creationNew alias for each requestOptional client idempotency key if needed

Is an eight-character alias enough?

The 8-character length is a design fact. It does not automatically make the service collision-free or secure. Collision probability depends on the actual character alphabet and number of stored IDs, and a public short URL should be treated as discoverable unless access control is explicitly added. The correct implementation response is a uniqueness constraint and collision retry, not confidence in random generation alone. Similarly, a temporary short link is not an access-control token: deleting its mapping stops that redirect, but a recipient who already has the original URL can still visit the destination. Temporary links and revocable content authorization are different product requirements.

Failure and security review

FailureUser impactDesign response
Server process restartsExisting aliases disappearMove mappings to shared persistence
Two replicas handle different requestsAlias created on A is missing on BCentralize state
Timer fires late under heavy event-loop workExpired alias may remain temporarily resolvableCheck expiry on reads
Unsafe redirect destinationUsers can be sent to unwanted schemes/sitesNormalize and validate URL destinations
High create rateMemory and timer count grow without boundRate limiting, storage TTL, quotas
A generated ID already existsExisting destination can be overwrittenAtomic uniqueness check and retry

What the implementation proves

The service implements the complete temporary-alias lifecycle: create, return, resolve, inspect, and expire. It is a good compact demonstration of REST contracts, ID generation, routing, process-local state, and lifecycle behavior. Its limitations also demonstrate that I distinguish functional correctness in a prototype from durability, horizontal scaling, and security in production. Next validation plan: create reproducible tests for expiry boundaries, collision injection, malformed URLs, restart behavior, and two-instance consistency; then benchmark memory use versus active link count. Only after shared persistence is in place would I publish throughput and availability figures for a deployment.

Source

Expiring URL service — designing a small API around explicit failure modes architecture diagram 1

More to explore

Jul 15, 2026
Jul 15, 2026

Fitter Health

Sole engineer across a 106-table healthcare platform, 135 passing tests, AWS delivery, and durable notification recovery.

View project
Client deliveryFull stack
System overview for Fitter Health

Let’s talk

I like working through complex problems with people who care about the details. Have a product to build, an engineering role, or an interesting challenge? Let’s start a conversation.

A little note

SaaimOpen to full-time roles, contract work, and conversations about things worth building.

ϟ 1
Contact