Skip to Content
Course content

187: Practice Exercise: Building a URL Shortener with Analytics

Click on the "Edit" button in the top corner of the screen to edit your slide content.

Whenever I see a junior dev tackle a URL shortener, they almost always start with the same approach: they write a function that generates a random 6-character string, checks the database to see if it exists, and if it does, loops back to generate another one. It seems intuitive, right? You just keep rolling the dice until you hit a unique value.

The Myth: "Random Strings are the Safest Way to Avoid Collisions"

Here is why that approach is a trap. Imagine your database has 10 million entries. The probability of a collision increases as your table grows. Eventually, your code might have to hit the database five or six times just to find one available string for a single URL. You've essentially turned your "shortener" into a performance bottleneck that relies on luck.

// The "Wrong" Way: Collision-based loops
do {
    $shortCode = substr(str_shuffle("0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"), 0, 6);
    $exists = $db->query("SELECT id FROM urls WHERE code = '$shortCode'");
} while ($exists->num_rows > 0);

This is "hope-based programming." We can do much better by leveraging the database's own strengths.

The Reality: Mapping Sequential IDs to Base62

Instead of guessing a string, use the auto-incrementing primary key (the ID) of your database record. Every ID is guaranteed to be unique. To make that ID look like a "code," you convert that integer from Base10 (0-9) to Base62 (0-9, a-z, A-Z).

For example, if the ID is 125, you don't want the user to see your.url/125. You convert 125 into Base62, and it becomes your.url/2B. It's deterministic, lightning-fast, and you never have to run a SELECT query just to check if a code is taken.

function base62Encode($num) {
    $chars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
    $res = '';
    while ($num > 0) {
        $res = $chars[$num % 62] . $res;
        $num = intdiv($num, 62);
    }
    return $res;
}

Capturing Metadata Before the Jump

Now, the "analytics" part of this project. The biggest mistake here is trying to record the click after the redirect. Once you send a header("Location: ..."), the PHP script effectively ends for the user. You have to capture the data in the split second between the request arriving and the redirect firing.

I usually recommend creating a separate clicks table. You don't just want the timestamp; you want the context. Use the $_SERVER superglobal to grab the HTTP_USER_AGENT (to know if they're on mobile) and REMOTE_ADDR (for the IP). I'll be honest: IP-based location tracking is a bit of a rabbit hole involving external APIs, but for this exercise, just storing the raw IP is a great start.

The flow should look like this:

  • User hits /r/abc123
  • You look up the destination URL based on the code abc123
  • You insert a row into the clicks table with the current timestamp and user agent
  • You call header("Location: $destination")

One last tip: use a 302 (Found/Temporary) redirect rather than a 301 (Permanent). If you use a 301, some browsers will cache the redirect locally, and your analytics will stop recording hits for returning users because the browser won't even hit your server the second time.




📋 Practical Task

Build a High-Performance URL Redirector with Click-Logging

Your task is to implement the logic for the redirection engine of a URL shortener. You are not required to build the URL creation form, but you must build the handler that processes the short codes.

Requirements:

  • The Database: Create two tables. A urls table (id, original_url, short_code) and a clicks table (id, url_id, clicked_at, user_agent, ip_address).
  • The Logic: Write a PHP script that accepts a short code via a GET parameter (e.g., redirect.php?c=2B).
  • The Analytics: The script must log the user's IP address and Browser User Agent into the clicks table before redirecting the user.
  • The Redirect: Use a 302 redirect to send the user to the original_url associated with that code.
  • Error Handling: If the code doesn't exist in the database, redirect the user to a 404 page or display a friendly "Link Expired" message.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.