Skip to content

Jaocodigos/shortpy

Repository files navigation

shortpy

URL shortener using architecture best practices, enabling good structure and performance.

Technologies

  • Flask 3.1.2 - Web framework
  • Redis 7.1.0 - NoSQL database for URL storage
  • Marshmallow - Data validation and serialization
  • Hiredis - High-performance Redis client

Features

  • URL shortening with unique codes generated in base62
  • Expiration time definition for links (minutes, hours or days)
  • Automatic redirection to original URLs
  • Link expiration validation
  • Simple web interface
  • REST API for integration

Functional Requirements

  • Create Short URLs

    • The system must allow users to create a shortened version of a valid URL.
  • Unique Code Generation

    • Each shortened URL must be associated with a unique, non-guessable code.
  • Redirect to Original URL

    • When accessing a shortened URL, the system must redirect the user to the original URL.
  • Expiration Support

    • The system must allow defining an expiration time for shortened URLs using human-readable formats (minutes, hours, or days).
  • REST API Access

    • The system must expose a RESTful API to allow integration with external systems.
  • Web Interface Access

    • The system must provide a simple web interface for manual URL shortening.

Non Functional Requirements

  • Constraints

    • The generated short code must have a maximum length of 6 characters.

    • The short code must follow an alphanumeric pattern.

    • Stored URLs must have a maximum expiration time of 30 days.

  • Performance

    • The application must be able to handle up to 1.000.000 requests per day.

    • The system must be optimized for a read-heavy workload, with an expected ratio of 10 read operations for every write operation.

  • Availability

    • The application must ensure high availability, operating 24/7 without planned downtime.

Why Redis?

Redis was chosen as the primary data store for this project due to its alignment with the system’s functional and non-functional requirements.

Native key expiration (TTL) support is a core feature of Redis and directly addresses the requirement for automatic URL expiration. This eliminates the need for background cleanup jobs or additional application-level logic, simplifying the overall system design.

The data model of the application is simple and predictable: a short alphanumeric code maps directly to a URL and its associated metadata. Redis’ key-value model naturally fits this structure without introducing unnecessary complexity such as relational schemas or indexing strategies.

From a scalability perspective, Redis can comfortably handle the expected workload of up to one million requests per day on a single instance. When higher availability is required, Redis replication and sentinel-based setups allow the system to achieve high availability without changes to the application code.

Now, in case you make the gold question: what about the redis memory usage(RAM)?. Ok, lets go.

Memory Usage Estimation

Although Redis stores data in memory, the expected memory footprint of this application remains controlled and predictable due to the small size and limited lifetime of stored data.

Each shortened URL entry consists of:

  • A short alphanumeric key (up to 6 characters)

  • The original URL string

  • Minimal metadata (timestamps and expiration information)

  • Redis internal object overhead

A conservative estimation per entry is outlined below:

Component Estimated Size
Short code key ~50 bytes
Original URL (average length ~100 chars) ~100 bytes
Metadata (expiration, timestamps) ~50–100 bytes
Redis internal overhead ~100–150 bytes
Total per entry ~300–400 bytes

Under a high-load scenario with 1,000,000 active URLs stored simultaneously:

400 bytes * 1,000,000 entries ≈ 400 MB of RAM

Considering a read-heavy workload with approximately 10 read requests for every write request, the system is expected to handle around 90,000 write operations per day.

90,000 writes/day × 400 bytes ≈ 36 MB of new data per day

After 30 days(max of exp time):

36 MB/day × 30 days ≈ 1.08 GB

This shows us that a dedicated 2-4gb redis instance is a viable scenario.

Requirements

  • Python 3.x
  • Redis Server running locally or remotely

Environment Variables

Create a .env file in the project root with the following variables:

SECRET_KEY=your_secret_key_here
DATABASE_URI=localhost
DATABASE_PORT=6379
SERVER_ADDRESS=http://127.0.0.1
SERVER_PORT=5000
ADMIN_USERNAME=admin
ADMIN_PASSWORD=admin

Required:

  • SECRET_KEY - Secret key for Flask (required)

Optional:

  • DATABASE_URI - Redis address (default: localhost)
  • DATABASE_PORT - Redis port (default: 6379)
  • DATABASE_PASSWORD - Redis password
  • SERVER_ADDRESS - Server address (default: http://127.0.0.1)
  • SERVER_PORT - Server port (default: 5000)
  • ADMIN_USERNAME - Admin username (default: admin)
  • ADMIN_PASSWORD - Admin password (default: admin)

Run Locally

  1. Clone the repository:
    git clone <repository-url>
    cd shortpy
  1. Install dependencies:
  pip install -r requirements.txt
  1. Configure environment variables (see section above)

  2. Make sure Redis is running:

  redis-server
  1. Run the application:
  python -m app.main

The application will be available at http://127.0.0.1:5000

Run with Docker

  1. First things first, lets build the image. On terminal(project root path):
    docker build -t shortpy:dev .
  1. Create a .env file and customize as you want(see the .env_template to get what you need).

  2. Time to run the compose file:

    docker-compose up 

and done!

API Usage

Create shortened URL

Endpoint: POST /api/shorten

Body:

{
  "url": "https://example.com/very-long-url",
  "expires_in": "30 minutes"
}

Accepted formats for expires_in:

  • X minutes - Expires in X minutes
  • X hours - Expires in X hours
  • X days - Expires in X days

Response:

{
  "response": "http://127.0.0.1:5000/api/shorten?code=3D7nja"
}

Access shortened URL

Endpoint: GET /api/shorten?code=<code>

Automatically redirects to the original URL if the link is still valid.

Possible errors:

  • 404 - URL not found
  • 403 - Link expired

Project Structure

shortpy/
├── app/
│   ├── resources/
│   │   ├── api.py          # API endpoints
│   │   └── interface.py    # Web interface routes
│   ├── templates/
│   │   └── index.html      # Home page
│   ├── config.py           # Application settings
│   ├── database.py         # Redis connection
│   ├── helpers.py          # Helper functions
│   ├── main.py             # Application entry point
│   └── schema.py           # Validation schemas
├── tests/                  # Tests
├── requirements.txt        # Dependencies
└── README.md

Architecture

The project uses:

  • Base62 encoding to generate short and unique codes
  • Redis for fast and efficient storage
  • Global counter to ensure code uniqueness
  • Time validation for automatic link expiration
  • Blueprint pattern for modular route organization

About

Shortpy is a simple URL shortener built with Flask and Redis, focusing on clean architecture, good code organization, and performance.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors