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 lookup object 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 generateShortCode function 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 generateShortCodeWithRetry function 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 the lookup. Given the vast number of possible codes and an initially empty store, collisions are statistically improbable.
  • Code Blocklists: An integrated validator function 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, a CodeRestrictedError is 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 the shortener.shorten() method, and returns the generated short code and URL. Custom errors from the Shortener class 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 accesses flipr.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:

  1. Clone and Install:
    bash
    git clone https://github.com/edeckers/flipr-distributed-url-shortener.git
    cd flipr
    git checkout part-1-ephemeral
    npm install
  2. Start the Server:
    bash
    npm start

    The server will launch on `http://localhost:8000`, with Winston logs visible in your terminal.
  3. 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 the short_code, short_url, and original_url will be returned.
  4. Test Redirection:
    Visit the short_url (e.g., http://localhost:8000/aB3xY9`) in your browser or usecurl -L http://localhost:8000/aB3xY9`.
  5. 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!

Leave a Reply

Your email address will not be published. Required fields are marked *

Fill out this field
Fill out this field
Please enter a valid email address.
You need to agree with the terms to proceed