URL shortener using architecture best practices, enabling good structure and performance.
- 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
- 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
-
- The system must allow users to create a shortened version of a valid URL.
-
- Each shortened URL must be associated with a unique, non-guessable code.
-
- When accessing a shortened URL, the system must redirect the user to the original URL.
-
- The system must allow defining an expiration time for shortened URLs using human-readable formats (minutes, hours, or days).
-
- The system must expose a RESTful API to allow integration with external systems.
-
- The system must provide a simple web interface for manual URL shortening.
-
-
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.
-
-
-
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.
-
-
- The application must ensure high availability, operating 24/7 without planned downtime.
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.
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.
- Python 3.x
- Redis Server running locally or remotely
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=adminRequired:
SECRET_KEY- Secret key for Flask (required)
Optional:
DATABASE_URI- Redis address (default: localhost)DATABASE_PORT- Redis port (default: 6379)DATABASE_PASSWORD- Redis passwordSERVER_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)
- Clone the repository:
git clone <repository-url>
cd shortpy- Install dependencies:
pip install -r requirements.txt-
Configure environment variables (see section above)
-
Make sure Redis is running:
redis-server- Run the application:
python -m app.mainThe application will be available at http://127.0.0.1:5000
- First things first, lets build the image. On terminal(project root path):
docker build -t shortpy:dev .-
Create a .env file and customize as you want(see the .env_template to get what you need).
-
Time to run the compose file:
docker-compose up and done!
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 minutesX hours- Expires in X hoursX days- Expires in X days
Response:
{
"response": "http://127.0.0.1:5000/api/shorten?code=3D7nja"
}Endpoint: GET /api/shorten?code=<code>
Automatically redirects to the original URL if the link is still valid.
Possible errors:
404- URL not found403- Link expired
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
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