Skip to content

0xrayn/shortlink-api

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

6 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation


πŸ“¦ About

ShortLink is a backend REST API that shortens long URLs and tracks click analytics. The core focus of this project is caching and performance using Redis:

  • Redirects use a cache-aside pattern: the short code to URL mapping is checked in Redis first, falling back to PostgreSQL only on a cache miss, then populating the cache for subsequent requests.
  • Rate limiting is implemented with Redis using a fixed-window counter, protecting both the redirect endpoint and link creation from abuse.
  • Click tracking is recorded asynchronously so it never slows down the redirect response.

This project is built as a backend portfolio piece to demonstrate:

  • βœ… Cache-aside pattern with Redis (cache hit/miss handling)
  • βœ… Redis-based rate limiting (fixed-window counter with TTL)
  • βœ… JWT authentication
  • βœ… Click analytics with async recording
  • βœ… Dockerized setup (app + PostgreSQL + Redis in one command)

πŸ—οΈ Architecture

flowchart LR
    Client[Client / Postman] -->|GET /:code| API[Gin REST API]
    API -->|1. Check cache| Redis[(Redis)]
    Redis -->|Cache hit| API
    API -.->|Cache miss| DB[(PostgreSQL)]
    DB -.->|2. Fetch URL| API
    API -.->|3. Populate cache| Redis
    API -->|302 Redirect| Client
    API -->|async| Visits[Record visit + increment counter]
    Visits --> DB
Loading

πŸš€ Tech Stack

Component Technology
Language Go 1.22
Web Framework Gin
Database PostgreSQL
Cache / Rate Limiter Redis
Auth JWT (golang-jwt)
Password Hashing bcrypt
Containerization Docker & Docker Compose

πŸ“ Project Structure

shortlink/
β”œβ”€β”€ main.go                    # entry point
β”œβ”€β”€ config/database.go         # PostgreSQL + Redis connection, auto-migration
β”œβ”€β”€ models/models.go            # structs & request payloads
β”œβ”€β”€ middleware/auth.go          # JWT auth & Redis-based rate limiter
β”œβ”€β”€ handlers/
β”‚   β”œβ”€β”€ auth_handler.go         # register & login
β”‚   └── link_handler.go         # create/delete link, redirect (cache-aside), stats, QR code, my-links
β”œβ”€β”€ validators/validators.go     # custom validators (URL scheme, link code format)
β”œβ”€β”€ routes/routes.go            # route definitions
β”œβ”€β”€ postman/                     # Postman collection for testing
β”œβ”€β”€ Dockerfile
β”œβ”€β”€ docker-compose.yml
└── README.md

Note on database schema: tables are created automatically at startup via RunMigrations() in config/database.go (using CREATE TABLE IF NOT EXISTS), so no manual setup step is needed.


βš™οΈ Getting Started

Option 1 - Run with Docker (recommended)

This spins up the API, PostgreSQL, and Redis with a single command.

git clone https://github.com/0xrayn/shortlink.git
cd shortlink
docker-compose up --build

The API will be available at http://localhost:8080.

Option 2 - Run locally

Requires Go 1.22+, a running PostgreSQL instance, and a running Redis instance.

git clone https://github.com/0xrayn/shortlink.git
cd shortlink

cp .env.example .env
# Edit .env to match your local PostgreSQL and Redis configuration

go mod tidy
go run main.go

πŸ“– API Documentation

Authentication

Method Endpoint Description Auth
POST /auth/register Register a new user -
POST /auth/login Login and get JWT token -

Register

POST /auth/register
{
  "email": "user@example.com",
  "password": "secret123"
}

Login

POST /auth/login
{
  "email": "user@example.com",
  "password": "secret123"
}

Links

Method Endpoint Description Auth Rate Limit
POST /shorten Create a short link Required 10 req/min per IP
GET /:code Redirect to the original URL - 60 req/min per IP
GET /:code/stats Get click stats for a link - -
GET /:code/qr Get a QR code (PNG) pointing to the short link - -
DELETE /:code Delete a link (owner only) Required -
GET /my-links List links created by the logged-in user Required -

Create Short Link

POST /shorten
Authorization: Bearer <token>

{
  "url": "https://github.com/0xrayn/shortlink-api"
}

Optionally provide a custom alias:

{
  "url": "https://github.com/0xrayn/shortlink-api",
  "code": "my-repo"
}

Response:

{
  "id": 1,
  "code": "aB3xY9",
  "short_url": "http://localhost:8080/aB3xY9",
  "original_url": "https://github.com/0xrayn/shortlink-api",
  "created_at": "2026-06-14T10:00:00Z"
}

URL validation: the url field must be a valid http:// or https:// URL (other schemes such as javascript:, ftp:, or file: are rejected). The optional code field, if provided, must be 3-30 characters and contain only letters, numbers, hyphens, and underscores.

Redirect

GET /aB3xY9

Returns 302 Found with Location header set to the original URL. The first request is a cache miss (reads from PostgreSQL and populates Redis); subsequent requests are served from Redis until the cache entry expires (1 hour TTL).

Get Link Stats

GET /aB3xY9/stats
{
  "code": "aB3xY9",
  "original_url": "https://github.com/0xrayn/shortlink-api",
  "click_count": 5,
  "created_at": "2026-06-14T10:00:00Z",
  "recent_visits": [
    { "visited_at": "2026-06-14T10:05:00Z", "ip_address": "127.0.0.1", "user_agent": "PostmanRuntime/7.36.0" }
  ]
}

Get QR Code

GET /aB3xY9/qr

Returns a 256x256 PNG image encoding the short URL (http://localhost:8080/aB3xY9). Open this URL directly in a browser to see the QR code, or scan it with a phone camera to be redirected to the original link.

Delete Link

DELETE /aB3xY9
Authorization: Bearer <token>

Deletes the link and its click history. Only the user who created the link can delete it; attempting to delete someone else's link (or a non-existent code) returns 404.

Get My Links

GET /my-links?page=1&limit=10
Authorization: Bearer <token>
{
  "data": [ ... ],
  "pagination": { "page": 1, "limit": 10, "total": 3 }
}

Health

Method Endpoint Description
GET /health Health check

πŸ”’ Caching Strategy - The Core of This Project

The most important part of this codebase is RedirectLink in handlers/link_handler.go. Every redirect follows the cache-aside pattern:

  1. Check Redis for link:<code>.
  2. Cache hit -> parse the composite value (stored as link_id|original_url). Use the original_url for the redirection and the link_id to record the visit. This completely eliminates database queries during cache hits.
  3. Cache miss -> query PostgreSQL for both id and original_url, then store it in Redis as link_id|original_url with a TTL (1 hour) for future requests.
  4. Record the visit (insert into link_visits, increment click_count) asynchronously in a goroutine, so the redirect response is never delayed by write operations.

Note on Backwards Compatibility: The cache parser is fully backwards-compatible. If it encounters a legacy cache entry containing only the URL, it automatically queries the database once to retrieve the link ID and updates the cache to the new composite format (link_id|original_url) on the fly.

Rate Limiting

middleware.RateLimit(limit, window) implements a fixed-window counter using Redis:

  • Each (route, IP) pair gets a Redis key with an incrementing counter and a TTL equal to the window duration.
  • If the counter exceeds the limit before the TTL expires, the request is rejected with 429 Too Many Requests and a retry_after field (seconds until the window resets).
  • /shorten is limited to 10 requests/minute per IP (prevent spam link creation).
  • /:code (redirect) is limited to 60 requests/minute per IP (prevent abuse while allowing normal traffic).

πŸ§ͺ Testing with Postman

A ready-to-use Postman collection is included at postman/ShortLink_API.postman_collection.json, covering:

  • Register & login (with automatic token extraction)
  • Create short link (random code and custom alias)
  • Redirect (cache miss -> cache hit flow)
  • Click stats verification
  • Pagination on /my-links
  • 404 for non-existent codes
  • Rate limit behavior (429 after exceeding the limit)

How to run:

  1. Open Postman -> Import -> select postman/ShortLink_API.postman_collection.json
  2. Make sure the API is running (docker-compose up or go run main.go)
  3. Click the collection -> Run -> Run ShortLink API

All requests include pm.test() assertions, so the runner gives a pass/fail summary automatically - no manual checking needed.


πŸ“Έ Demo

Screenshots of the API tested via Postman:

Create short link

Create short link

Redirect with cache

Redirect

Link stats

Link stats

Rate limit response

Rate limit

QR code (open directly in browser)

QR code


πŸ—ΊοΈ Roadmap

  • JWT authentication
  • Cache-aside pattern with Redis
  • Redis-based rate limiting
  • Click analytics with async recording
  • Pagination
  • Dockerized setup
  • Postman collection with automated tests
  • Deploy to Railway/Render

About

URL shortener REST API with Redis caching, rate limiting, and click analytics. Built with Go, PostgreSQL, and Docker.

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors