~11 min
Once the requirements are scoped, the API is the contract the rest of the design has to satisfy, and the fastest way to make it look considered is to model resources, not actions. A URL shortener's API is not "createLink" and "getLink" — it is a links resource: POST /links creates one, GET /links/{code} reads one, DELETE /links/{code} removes one. HTTP already carries the verb, so the path only has to name the noun. This matters beyond style: HTTP defines shared behavior per method that clients, proxies and browsers all rely on. GET is defined as a safe method — a client does not expect it to change server state — which is why it can be retried, cached, and prefetched without asking permission. POST, PUT and DELETE are not safe; a client should only issue them because the user asked for that specific change.
HTTP also defines idempotent methods: a method is idempotent if sending the same request twice has the same effect on the server as sending it once. PUT, DELETE, and every safe method are idempotent by this definition; POST is not. That gap matters on a flaky network: a client that times out waiting for a POST /links response genuinely cannot tell whether the link was created or the response was merely lost, and retrying blindly risks creating a duplicate. The common fix is an idempotency key — the client generates a unique token once and sends it with every retry of the same logical request; the server stores which keys it has already processed and returns the original result for a repeat, instead of creating a second resource. PUT sidesteps the problem structurally: PUT /links/{code} with a client-chosen code can be retried safely without any extra key, because setting a resource to the same state twice is by definition idempotent.
Fielding's dissertation, which coined REST, ties two more constraints to this same idea. A stateless server keeps no session state between requests — every request carries everything needed to understand it (an auth token, not a server-side session ID pinned to one machine) — which is what lets you add a second server behind a load balancer without sticky sessions. And a response that is cacheable must say so explicitly, because a client, proxy or CDN is only allowed to reuse a stored response when the server has labelled it safe to reuse. A redirect service benefits enormously from both: a stateless GET /r/{code} can be served from any node, and a redirect that rarely changes can carry a cache header that keeps most traffic from ever reaching the database at all.
Recognize the key as already processed and return the original link's result — it must not create a second link.