Skip to content

SandstromPL/url-shortner

Repository files navigation

ScaleURL

ScaleURL is a backend-only URL shortener built with Go, PostgreSQL, and Redis. It started as a portfolio MVP and has now been hardened into a more deployable, testable, and operationally safer service.

There is currently no frontend in this repository. The product surface is an HTTP API plus the public redirect endpoint.

Version Timeline

Version 1: MVP Baseline

Version 1 established the core product flow:

  • JWT-based auth for register/login
  • authenticated short-link creation
  • public redirect endpoint
  • PostgreSQL persistence for users, links, and clicks
  • Redis cache-aside redirect flow
  • Redis-backed rate limiting
  • expiry support
  • basic analytics
  • Docker-based local setup

This was already a good backend prototype, but it still leaned heavily on manual testing and had only light operational hardening.

Version 2: Hardened Backend

Version 2 keeps the same feature set and improves the service around reliability and deployability:

  • stricter JSON parsing with unknown-field rejection
  • stronger request validation for email, password, URL, and expiry input
  • /health and /ready endpoints
  • graceful shutdown with connection cleanup
  • HTTP server timeouts
  • request ID and real-IP middleware
  • automated unit tests for core helpers and handlers
  • GitHub Actions CI for format, vet, test, and Docker build checks

Architecture

  • cmd/server/main.go: app bootstrap, middleware stack, routes, server timeouts, graceful shutdown
  • internal/handlers: HTTP handlers plus validation and system endpoints
  • internal/auth: bcrypt password hashing and JWT creation/validation
  • internal/middleware: auth middleware and Redis-backed rate limiting
  • internal/db: shared PostgreSQL pool and startup migration runner
  • internal/cache: shared Redis client, cached link helpers, rate-limit helpers
  • internal/models: API/data structs
  • migrations: raw SQL schema files

Request Flow

Create Link

  1. Client sends POST /api/shorten with a JWT.
  2. Request body is size-limited and validated.
  3. A Base62 code is generated.
  4. The link is inserted into PostgreSQL.
  5. Redis is pre-warmed with link metadata.

Redirect

  1. Client opens GET /:code.
  2. Redis is checked first.
  3. Active/expiry rules are enforced on cached and DB-loaded links.
  4. The browser receives 302 Found.
  5. Click logging runs asynchronously and updates analytics counters.

Analytics

  1. Owner calls GET /api/analytics/:id.
  2. Link ownership is verified.
  3. PostgreSQL aggregates totals, last-24h usage, device split, and top referrers.

Tech Stack

  • Go + Chi
  • PostgreSQL + pgx
  • Redis + go-redis
  • JWT auth
  • Docker / Docker Compose
  • GitHub Actions

Local Setup

1. Start dependencies

docker compose up -d

If you do not have Redis running locally, uncomment the Redis service in docker-compose.yml and start Compose again.

2. Configure env

The project uses .env:

PORT=8080
BASE_URL=http://localhost:8080
DB_URL=postgres://scaleurl:secret@localhost:5432/scaleurl?sslmode=disable
REDIS_URL=redis://localhost:6379
JWT_SECRET=change-this-to-a-long-random-secret-in-production
JWT_EXPIRY_HOURS=72

3. Run the server

go run ./cmd/server

The app loads env vars, connects to PostgreSQL and Redis, runs migrations, and starts the HTTP server.

API

Method Route Auth Purpose
GET /health No Liveness check
GET /ready No Readiness check for DB + Redis
POST /auth/register No Create account and return JWT
POST /auth/login No Login and return JWT
POST /api/shorten Yes Create short link
GET /api/links Yes List active links for current user
DELETE /api/links/{id} Yes Soft-delete a link
GET /api/analytics/{id} Yes View analytics for one link
GET /{code} No Redirect to original URL

Manual Test Cases

Happy path

  • register a user and receive 201
  • login and receive 200
  • create a link and receive 201
  • open /{code} and receive 302
  • open analytics for the returned link id and see click data
  • delete the link and receive 204
  • open /{code} again and receive 404

Validation and auth

  • register with malformed email and expect 400
  • register with a short password and expect 400
  • send unknown JSON fields and expect 400
  • call protected routes without Authorization: Bearer <token> and expect 401
  • create a link with an invalid or non-HTTP URL and expect 400
  • create a link with negative expires_in_days and expect 400

Expiry and readiness

  • create a link with expiry, set expires_at in the DB to the past, and expect 410 Gone on redirect
  • stop Redis or Postgres and expect /ready to return 503
  • keep the server process alive and expect /health to still return 200

Curl Examples

Register

curl -X POST http://localhost:8080/auth/register \
  -H "Content-Type: application/json" \
  -d '{"email":"demo@example.com","password":"secret123"}'

Login

curl -X POST http://localhost:8080/auth/login \
  -H "Content-Type: application/json" \
  -d '{"email":"demo@example.com","password":"secret123"}'

Shorten a URL

curl -X POST http://localhost:8080/api/shorten \
  -H "Authorization: Bearer YOUR_JWT" \
  -H "Content-Type: application/json" \
  -d '{"url":"https://example.com/docs","expires_in_days":30}'

List links

curl http://localhost:8080/api/links \
  -H "Authorization: Bearer YOUR_JWT"

View analytics

curl http://localhost:8080/api/analytics/LINK_ID \
  -H "Authorization: Bearer YOUR_JWT"

Delete a link

curl -X DELETE http://localhost:8080/api/links/LINK_ID \
  -H "Authorization: Bearer YOUR_JWT"

Automated Checks

Run the local verification suite with:

go test ./...

CI is defined in .github/workflows/ci.yml and runs:

  • gofmt
  • go vet
  • go test ./...
  • docker build

Deployment Notes

  • Dockerfile is already multi-stage and suitable for platforms like Railway or Render.
  • Production hosting should inject env vars directly instead of relying on .env.
  • The app now has readiness and shutdown behavior that make platform deployments safer than the original MVP.

Current Status

Today this project is more than just a proof of concept:

  • Version 1 proved the product and architecture.
  • Version 2 makes it easier to deploy, verify, and operate.

It is still best described as a production-style MVP backend rather than a fully enterprise-hardened system, but it is meaningfully stronger now than the original prototype.

For project structure and architecture flow, see ARCHITECTURE.md.

About

Production-style and scalability-focused backend URL shortener built with Go, PostgreSQL, and Redis, featuring auth, cached redirects, analytics, expiry handling, and rate limiting.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors