Welcome to my world!

Welcome to my world!

I'm a software engineer who cares deeply about clarity, usability, and getting the details right.

I'm a software engineer who cares deeply about clarity, usability, and getting the details right.

Brand Logo
Icon
1

Shortify - A URL Shortener and QR Code Generator

<!--

Work info

-->

Company:

Independent Project

Role:

Full-Stack Software Engineer

Year:

2025

Work Image

Project Overview

Shortify is a personal, full-stack project focused on designing and shipping a simple but production-minded URL shortener with QR code generation. The project was intentionally scoped to balance usability, correctness, and deployability without over-engineering.

The goal was not to build the most feature-rich link management platform, but to design a small system end-to-end: backend API design, persistence, frontend UI, and cloud deployment. I owned the entire lifecycle from initial architecture decisions to refactoring, redesign, and deployment.

Background & Motivation

I wanted a project that exercised real system boundaries rather than isolated coding exercises. URL shorteners are deceptively simple: while the core idea is straightforward, the surrounding concerns—persistence, collisions, deployment, UI clarity, and future scalability—introduce meaningful design decisions.

Rather than optimizing prematurely, I approached Shortify as a deliberately staged system: start with simple, correct foundations and leave room for growth.

Problem Statement

The core problem was to design a small but complete system that:

  • reliably maps short codes to long URLs

  • supports QR code generation as a first-class feature

  • persists data across restarts and deployments

  • presents a clean, usable interface

  • can be deployed and iterated on like a real product

At the same time, the project needed to avoid unnecessary complexity that would obscure learning or slow iteration.

Approach & Constraints

Several constraints guided the implementation:

  • The backend needed to be simple, readable, and easy to extend

  • Database persistence was required, but early development favored speed over scale

  • The frontend needed a full redesign once initial assumptions proved limiting

  • Deployment needed to be real, not local-only

  • The system should have a clear path from “toy” to “production-ready”

Given these constraints, I prioritized clarity and separation of concerns over premature optimization.

Architecture & Technology Stack
Backend Architecture

The backend was implemented using Python and Flask, chosen for its clarity and minimal abstraction overhead. The API was responsible for:

  • generating unique short identifiers (including support for custom short codes)

  • persisting mappings between short codes and destination URLs

  • handling redirect requests and incrementing per-link click counts

  • generating QR codes associated with each shortened link

  • logging requests for basic analytics

Persistence was handled using SQLite during initial development to enable fast iteration and zero-infrastructure setup. The schema and access patterns were intentionally designed to allow a future migration to PostgreSQL without rewriting core logic.

The backend was deployed on Render, providing a simple but real production environment for API hosting and persistence.

Frontend Architecture

The frontend was built as a single-page application using React and Tailwind CSS. It was responsible for:

  • collecting user input for link creation

  • displaying generated short links and QR codes

  • presenting basic per-link analytics (e.g., click counts)

  • providing a responsive experience across desktop and mobile

After an initial implementation, the frontend was deleted and redesigned from scratch. The second iteration focused on clearer user flow, reduced visual noise, and more obvious primary actions, reinforcing the idea that iteration sometimes requires discarding working but suboptimal designs.

The frontend was deployed independently using AWS Amplify, allowing UI changes to be made and deployed without coupling them to backend changes.

System Boundaries & Data Flow

At a high level:

  • the frontend communicates with the backend via a small, explicit API surface

  • the backend owns all persistence, redirect handling, and analytics

  • deployment is intentionally split to mirror real-world service boundaries

This architecture kept responsibilities clear and made the system easier to reason about, debug, and extend.

Key Decisions
Start with SQLite, plan for PostgreSQL

Shortify initially used SQLite to enable fast iteration and minimal setup. Rather than treating this as a dead end, I designed the data access layer with a clear migration path to PostgreSQL in mind.

This allowed me to:

  • validate schema and access patterns early

  • avoid infrastructure overhead during early development

  • plan for production persistence without rewriting core logic

The eventual PostgreSQL migration was treated as an architectural step, not an afterthought.

Treat QR codes as a core feature, not an add-on

QR code generation was designed as a first-class capability. This influenced:

  • API shape

  • data models

  • frontend affordances

By designing QR support early, the system avoided awkward retrofits later.

Redesign the frontend from scratch

After building an initial frontend, I intentionally deleted it and started over. The first version functioned, but it didn’t communicate intent clearly and didn’t scale well as features were added.

The redesign focused on:

  • simplifying user flow

  • making primary actions obvious

  • reducing visual and cognitive clutter

This reset improved the overall coherence of the product and reinforced the value of iteration over attachment.

Separate deployment concerns by responsibility

I split deployment intentionally:

  • Backend deployed on Render

  • Frontend deployed on AWS Amplify

This separation mirrored real-world deployment patterns and allowed each side of the system to evolve independently.

Tradeoffs & Risks

Several tradeoffs were accepted intentionally:

  • SQLite limits scalability but improves iteration speed

  • A small feature set limits scope but improves quality

  • Manual deployment configuration trades automation for understanding

These choices were appropriate for the project’s goals and left room for future expansion.

Results & Impact

Shortify resulted in a fully deployed, end-to-end system that behaved like a real product rather than a local demo.

Over the course of development and live usage:

  • The platform was used to create 150+ short links, including both generated and custom short codes

  • The redirect endpoint processed 1,000+ redirect requests, with per-link click counts tracked and persisted

  • QR code generation was integrated directly into the core flow, allowing each short link to be shared visually as well as via URL

  • A responsive, single-page interface enabled link creation and analytics viewing across desktop and mobile devices

  • The backend and frontend were deployed independently, validating the system’s ability to evolve without redeployment coupling

Beyond raw usage numbers, the project demonstrated the ability to:

  • design a complete system across frontend, backend, persistence, and deployment

  • make architectural decisions that supported iteration rather than locking in early assumptions

  • treat deployment and observability as part of development instead of as an afterthought

Most importantly, Shortify validated my ability to design, ship, and iterate on a production-minded system independently, from first commit to live usage.

Reflections & Takeaways

Shortify reinforced several principles:

  • small systems still deserve good architecture

  • iteration often requires throwing work away

  • deployment is part of development

  • designing for migration early reduces future friction

What I Intentionally Did Not Do

I avoided adding features like analytics dashboards, authentication, or link expiration. While useful, they would have obscured the project’s primary learning goals and introduced unnecessary complexity.

Keeping the scope tight made the system easier to reason about and improve.

Results

0

Redirect endpoints processed

0

Short links generated

0

Independently deployed services

Building and evolving production systems under real-world constraints

Social Icon
Social Icon

Building and evolving production systems under real-world constraints

Social Icon
Social Icon

Building and evolving production systems under real-world constraints

Social Icon
Social Icon