Requirements
Functional Requirements:
- Create a short URL for a given long URL.
- Return the long URL associated with a given short URL.
- (Optional) Support custom domain.
- (Optional) Auth if we want to have the user manage their shortened URLs.
Non-Functional Requirements:
- Low latency: the user needs to get to the target page quickly.
- High throughput: we don't want to limit the throughput of the downstream service.
- Reliability: the URL shortener may be a key component in user's critical infrastructure.
- Data retention: URLs not used for a long time should be freed up for re-use.
- Security: we don't control how the URL creator uses it further. It is not our task to handle the access to the shortened URL.
- Idempotency: creating a shortened URL for the existing long URL should return the same entry.
API Design
Two interaction paths:
- Write: user creates a shortened URL
- Read: user navigates/calls the shortened URL and gets redirected to the full URL.
Write POST /create
Accepts JSON body containing "originUrl" property. We won't use query parameters as the original URL may exceed the query param limit.
Returns a JSON containing "shortUrl" property with the shortened URL.
Read GET /<short_url_id>
Returns a 308 response (permanent redirect) and Location header pointing to the full URL.
High-Level Design
Main components:
- Stateless service handling REST requests.
- DB maintaining the mapping between long and short URLs.
Detailed Component Design
Server:
Simple application (e.g., Spring Boot server app or a NodeJS express server). Using an async framework (e.g., Reactor) may be beneficial as the application is IO-bound.
POST /create
- Extract and sanitize the input (should be a valid URL).
- Hash it into the available space (e.g., fixed-length alphabetical string). Let's assume a string of English letters with a length of 12. Would support 26^12 short URLs. With custom domain logic, we can scope IDs per domain, but that's a follow-up.
- Insert the mapping of short ID to a long URL into the DB.
- Return 200 and a new URL if insert succeeded.
- Return 201 and the existing URL if the entry already exists.
GET /
- Extract and sanitize teh input (should be a valid URL ID).
- Look up the full URL in the DB.
- Return 308 with the full URL in the Location header.
- Return 404 if the mapping is not present.