URL Shortener as a Backend Learning Project

src/redirects/config.go holds all core routing and data validation rules for your URL shortener backend project.

Redirect mapping table structure

This table is the foundational data layer for the entire project, eliminating orphaned redirects and ensuring all routing decisions are tied to a single, auditable source of truth. You define the table schema to prioritize fast lookups, with a primary key on the short code column to speed up query times even as your code base scales to hundreds of thousands of entries. No in-memory cache sits in front of this table for core routing, so you never serve stale redirects after a user edits or deletes a link they created.

URL Shortener as a Backend Learning Project desk detail
Desk detail for this page — not a measured lab photo.
Short code Long URL Created timestamp (Unix) Expiry timestamp (Unix) Owner ID Access count
x8j2p9 https://example.com/2024/frontend-performance-checklist 1714521600 1715817600 usr_6f2d8a 124
m7q3r1 https://github.com/your-username/portfolio-v4 1714608000 0 usr_6f2d8a 47
k9s5v2 https://conference-talks.io/2024/edge-runtime-deep-dive 1714694400 1717286400 usr_9x1c3v 302

The 0 value in the expiry timestamp column indicates a permanent redirect that will never be auto-deleted, for use cases like linking to personal projects or evergreen content. The access count column increments on every valid redirect, so you can add usage limits or analytics features later without modifying the core schema. Illustrative example: The 1,296,000 second difference between the created and expiry timestamps for the x8j2p9 entry translates to a 15 day temporary redirect window, aligned with common use cases for sharing event links, limited-time discount codes, or draft content for collaborators.

Collision handling rule options

Short code collisions occur when your random 6-character base62 generation function produces a code that already exists in the redirect table, a rare but unavoidable edge case for any URL shortener. You have two distinct implementation choices to resolve these collisions without breaking user workflows. The first option is to retry code generation up to 5 times, returning a 500 service error if all retries produce duplicate codes. This approach is simple to code and keeps all short codes exactly 6 characters long, but creates avoidable user-facing errors when code space runs low for high-traffic instances. The second option is to append a single incremental base62 character to the duplicate code, creating a 7-character code that avoids collisions without retries. This approach eliminates generation errors entirely, and only produces longer codes for less than 0.02% of requests for a standard 6-character base62 code space.

You select the auto-suffix rule for this project, since it prioritizes user experience over rigid short code length, and the statistical risk of a double collision (requiring a second suffix character) is negligible for all small to medium project use cases. You write the rule directly into config.go: when a write request for a new short code returns a duplicate entry error from your database, pull the next character from the pre-defined base62 set, append it to the generated code, and re-run the write request. You only throw an error if the suffixed code also produces a duplicate, a scenario that will almost never occur for projects with fewer than 10 million total short codes.

Illustrative card for URL Shortener as a Backend Learning Project
Illustrative worksheet for this topic. Treat numbers as examples.

Expiry logic implementation

You also have two core choices for how to handle expired short code entries to keep your database size lean and avoid serving stale redirects. The first option is to run a nightly cron job that scans the entire redirect table and deletes all entries with an expiry timestamp earlier than the current Unix time. This approach reduces per-request database load, but can leave expired entries accessible for up to 24 hours after they expire, and creates resource spikes during the nightly scan for large tables. The second option is to run an expiry check on every incoming redirect request, deleting the entry immediately if it is expired before returning an error to the user, paired with a weekly bulk cleanup job to delete expired entries that are never requested. This approach ensures expired links are never served to users, and spreads database load evenly across all requests instead of concentrating it in a single nightly job.

You select the on-request expiry check for this project, as it aligns with expected user behavior for shared links, where most expired links will be requested at least once after their expiry date. You write the logic into config.go: when a request comes in for a given short code, first pull the matching entry from the redirect table. If the entry’s expiry timestamp is non-zero and less than the current Unix timestamp, delete the entry immediately and return a 410 GONE status code instead of a generic 404, to let users know the link was previously valid but is no longer accessible. You add a 10 second TTL to negative cache entries for expired codes to avoid repeated database hits for the same dead link requested repeatedly. This pattern aligns with the backend best practices highlighted in Build Next Stack project guides, balancing performance and data freshness without overprovisioning scheduled job resources.

Next time you open src/redirects/config.go

You leave all the core logic drafted directly in the file, so you can pick up implementation without rewriting foundational rules. You leave the full redirect table schema definition formatted for both PostgreSQL and SQLite, so you can use whichever database you are most comfortable with for the project. You leave the collision resolution function stub complete with the base62 character set constant defined at the top of the file, so you don’t have to look up the standard URL-safe base62 ordering to finish the implementation. You leave the expiry check logic fully written, with comments highlighting where to plug in your database client and where to adjust the negative cache TTL if you want to tweak performance.

You also leave three pre-written test case stubs: one for a collision scenario that triggers the auto-suffix rule, one for an expired link that returns a 410 status, and one for a permanent redirect that returns a 301 status with no expiry check. You add a single TODO comment linking to the separate redirect test file, and a configurable variable for default expiry time set to 15 days, which you can adjust to any value or set to 0 to make all new short codes permanent by default. You also leave comments highlighting optional extensions you can add later, including rate limiting for code generation per user, custom short code support, and access logging for link owners, all of which work with the existing schema and rules you have defined. No extra dependencies are required for the core logic, so you can run the file locally as soon as you connect your database client.

(Word count: 1372)