Welcome to the inaugural post in a series dedicated to demystifying the development of distributed systems on Kubernetes. Our chosen subject, a practical “Todo application” for systems design, is a URL shortener we’ve named Flipr 🐬.
The primary objective of this series is to construct a fully functional and deployable system, engineered for scalability—from a singular instance to a robust, distributed cluster operating on Kubernetes.
Series Focus and Scope
This series will meticulously detail the creation of a scalable, distributed URL shortener and the underlying architectural decisions. To maintain a sharp focus on architectural evolution, certain elements critical for a production environment will be intentionally omitted in these discussions, including:
- Comprehensive Unit Testing: The emphasis is on architectural progression, not a fully production-ready codebase.
- Extensive Input Validation: Only basic checks will be implemented, deferring production-grade security hardening.
- Abuse Prevention Mechanisms: While rate limiting will be considered, advanced anti-spam measures are outside this series’ scope.
- User Account Management: Flipr will serve as a straightforward shortener, showcasing distributed systems concepts without user accounts.
Therefore, if your goal is a complete, off-the-shelf URL shortener for immediate production use, resources on GitHub might be more suitable. However, if you’re eager to grasp the fundamentals of distributed system construction and benchmark architectural enhancements, you’re in the ideal place.
Defining Requirements and Scale Targets
Before diving into any code, it’s crucial to establish the foundational requirements for Flipr. Performance targets will remain intentionally flexible, as actual throughput and latency are heavily dependent on the deployment hardware. Nonetheless, each iteration will be benchmarked to objectively demonstrate how architectural changes contribute to improved performance and scalability, rather than just increasing complexity.
Functional Requirements:
- Short Code Specifications:
- Length: 6-7 alphanumeric characters.
- Character Set: a-z, A-Z, 0-9 (62 unique characters).
- Namespace: Approximately 3.5 trillion potential codes (62^7).
- URL Constraints:
- Maximum original URL length: 2,048 characters (browser-safe standard).
- Short code lifespan: Indefinite (no automatic expiration).
Design Goals:
- Horizontal Scalability: The system should easily accommodate increased load by adding more instances, rather than relying on larger, more powerful servers.
- Read-Heavy Workload: Anticipate that redirect requests will significantly outnumber URL shortening requests (a typical ratio of 10:1 or higher).
- Storage Growth: The design must support millions of short codes with ample capacity for future expansion.
- Low Latency: Both shortening and redirection operations should aim for rapid responses (ideally sub-100ms).
The Journey Begins: Starting with Simplicity
The most effective starting point is the simplest possible implementation that achieves the core functionality: shortening URLs and redirecting users. This foundational version then allows for iterative enhancements—adding features, optimizations, and infrastructure components—each driven by a tangible need arising from the limitations of the preceding version.
This “emergent architecture” philosophy advocates for building the simplest working solution first. Storage methods, scaling strategies, and database schemas naturally evolve from real-world problems encountered, rather than being speculative upfront designs. This approach ensures that the application dictates its storage and scaling needs, preventing early lock-in to potentially unsuitable architectural choices. By observing a working system quickly, developers maintain momentum and avoid getting bogged down in premature optimization.
Flipr 0: The Ephemeral Prototype
True to our “simplest thing first” promise, this initial version of Flipr is deliberately minimal, eschewing all external dependencies like databases or caching layers. It comprises pure business logic and a basic HTTP server. All data resides solely in memory, meaning it vanishes upon server restart—a perfectly acceptable trade-off for this introductory phase.
Chosen Technology Stack:
- TypeScript: For enhanced code maintainability and type safety.
- Node.js: As the robust runtime environment.
- Express: To manage HTTP server functionalities.
- Winston: For structured and efficient logging.
- Zod: To facilitate robust request validation.
Core Business Logic
The heart of Flipr resides within the Shortener class, which encapsulates all URL shortening logic. This design ensures complete independence from HTTP protocols, databases, or any infrastructure specifics, resulting in clean, testable code that could be repurposed for diverse contexts (e.g., a CLI tool).
- In-Memory Storage: The
lookupobject serves as our entire “database,” a straightforward key-value store mapping short codes to their original URLs. Its ephemeral nature is temporary and deliberate. - Code Generation: The
generateShortCodefunction produces random strings from a 62-character alphabet (a-z, A-Z, 0-9). With a default length of 7 characters, this yields approximately 3.5 trillion possible unique codes, offering substantial headroom. - Collision Resolution: The
generateShortCodeWithRetryfunction attempts to generate a valid, unique code up to 10 times. A “valid” code must not violate blocklist restrictions and must not already exist in thelookup. Given the vast number of possible codes and an initially empty store, collisions are statistically improbable. - Code Blocklists: An integrated
validatorfunction enforces checks against three distinct blocklists:- Reserved: Codes like “api” or “admin” that could imply authority.
- Offensive: Self-explanatory codes to prevent misuse.
- Protected: Codes reserved for future special purposes or to prevent confusion.
- Custom Short Codes: Users have the option to provide their own short code (e.g.,
flipr.io/mysite). These custom codes undergo the same validation process as generated codes; if restricted or already taken, aCodeRestrictedErroris thrown. - Distinct Error Types: Custom error classes (
CodeRestrictedError,ShortCodeNotFoundError,FailedUrlRetrievalError) are employed to allow the HTTP layer to differentiate between various failure modes, maintaining a clean separation between business logic and transport concerns.
Integrating with Express
The Express server setup is minimalist, handling two primary endpoints:
- URL Shortening (
/api/shorten– POST): This endpoint receives a URL and an optional custom code. It validates the request body using Zod, invokes theshortener.shorten()method, and returns the generated short code and URL. Custom errors from theShortenerclass are mapped to appropriate HTTP status codes (e.g., 422 for restricted codes, 500 for internal errors). - Short Code Resolution (
/:shortCode– GET): This is the redirection endpoint. When a user accessesflipr.io/abc123, the system looks up the corresponding original URL and issues a 302 redirect. If the short code is not found, a 404 response is returned.
The remaining Express configuration includes standard boilerplate such as CORS headers, static file serving for a basic web UI, a health check endpoint, and Winston-powered structured logging. The full server code is available in the associated GitHub repository for reference.
Running Flipr Locally
To experiment with Flipr 0:
- Clone and Install:
bash
git clone https://github.com/edeckers/flipr-distributed-url-shortener.git
cd flipr
git checkout part-1-ephemeral
npm install - Start the Server:
bash
npm start
The server will launch on `http://localhost:8000`, with Winston logs visible in your terminal. - Shorten a URL (using cURL):
bash
curl -X POST http://localhost:8000/api/shorten \
-H "Content-Type: application/json" \
-d '{"url": "https://example.com/some/very/long/url"}'
A JSON response containing theshort_code,short_url, andoriginal_urlwill be returned. - Test Redirection:
Visit theshort_url(e.g.,http://localhost:8000/aB3xY9`) in your browser or usecurl -L http://localhost:8000/aB3xY9`. - Try a Custom Code:
bash
curl -X POST http://localhost:8000/api/shorten \
-H "Content-Type: application/json" \
-d '{"url": "https://example.com", "custom_code": "mysite"}'
If a web UI is enabled, you can also access `http://localhost:3000` for a simple form interface.
What Comes Next
In the subsequent post, we will proceed to containerize Flipr and establish basic Kubernetes configurations utilizing Kustomize. This will involve packaging the application for deployment and laying the groundwork for its operation within a distributed environment.
The codebase evolves across dedicated branches on GitHub, allowing you to track progress commit by commit, deploy the system independently, or jump directly to any stage of interest. This series is crafted as a learning resource for understanding distributed systems architecture, released under the MPL-2.0 license. Contributions, suggestions, and issue reports are highly encouraged via the repository.
Conclusion
We began by asking: what constitutes the minimum viable URL shortener? The answer, for Flipr, is approximately 150 lines of TypeScript code. While this ephemeral version lacks persistence and single-instance scalability, it successfully proves the core concept and provides a robust foundation. Our next step is to Dockerize the application and implement a basic Kubernetes configuration.
How do you typically initiate new projects? Do you prefer starting with the database schema, or do you let the application logic guide your architectural decisions? Share your insights in the comments below!