Skip to content

sermachage/urlshort

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

23 Commits
 
 
 
 
 
 

URL Shortener (Go)

A beginner-friendly URL shortener service written in Go.

It supports:

  • POST /shorten to create short links
  • GET /{code} to redirect to the original URL
  • GET /healthz for liveness checks
  • GET /readyz for readiness checks (store/cache health)
  • Optional Redis caching
  • Optional Firestore persistence
  • Request hardening (rate limiting + max body size)
  • Graceful shutdown on SIGINT/SIGTERM
  • Optional Next.js UI (HeroUI) for browser-based usage

Project layout

  • src/main.go: HTTP handlers, app wiring, Redis cache client, graceful shutdown
  • src/firestore_store.go: Firestore-backed implementation of the store interface
  • src/main_test.go: unit tests for handlers and behavior
  • src/integration_test.go: optional integration tests for Redis and Firestore emulator
  • ui/: Next.js + HeroUI frontend for creating and opening short links

How it works

Shorten flow (POST /shorten)

  1. Validate request JSON: { "url": "https://example.com" }
  2. Validate URL scheme (http or https)
  3. Generate a short code
  4. Save mapping in the configured store
  5. Return short_url

Redirect flow (GET /{code})

  1. Try Redis cache first (if enabled)
  2. If cache miss, read from store
  3. Write store result back into cache (best effort)
  4. Return 302 Found redirect

Architecture

sequenceDiagram
    participant C as Client
    participant API as Go API
    participant R as Redis (optional cache)
    participant S as Store (InMemory or Firestore)

    Note over C,S: Shorten
    C->>+API: POST /shorten {url}
    API->>+S: Save(short_code, original_url)
    S-->>-API: ok
    API-->>-C: 200 {short_url}

    Note over C,S: Redirect
    C->>+API: GET /{code}
    API->>+R: GET code
    alt cache hit
      R-->>-API: original_url
      API-->>C: 302 redirect
    else cache miss/error
      API->>+S: Get(code)
      S-->>-API: original_url
      API->>R: SET code original_url TTL
      API-->>C: 302 redirect
    end
Loading

Requirements

  • Go 1.25+
  • Optional: Redis (REDIS_ADDR)
  • Optional: Firestore project or emulator (FIRESTORE_PROJECT)

Tech stack

  • Language: Go 1.25+
  • HTTP server: Go standard library (net/http)
  • Cache: Redis (github.com/redis/go-redis/v9)
  • Persistent store: Google Firestore (cloud.google.com/go/firestore)
  • Rate limiting: golang.org/x/time/rate
  • Testing: Go testing, httptest, race detector, go vet
  • Frontend UI: Next.js + HeroUI (@heroui/react)

Configuration

The server starts on :8080.

Variable Required Default Description
BASE_URL No http://localhost:8080 Base URL used in short_url responses
REQUEST_TIMEOUT No 2s Per-request timeout (Go duration format)
REDIS_ADDR No empty Redis host:port for caching
FIRESTORE_PROJECT No empty If set, Firestore store is enabled
FIRESTORE_COLLECTION No url_mappings Firestore collection name
RATE_LIMIT_RPS No 20 Requests per second allowed per client IP on POST /shorten
RATE_LIMIT_BURST No 40 Burst capacity per client IP
MAX_BODY_BYTES No 1048576 Maximum request body size in bytes
INTEGRATION_REDIS_ADDR No empty Used only by Redis integration tests
FIRESTORE_EMULATOR_HOST No empty Used only by Firestore integration tests

If FIRESTORE_PROJECT is not set, the app uses an in-memory store.

Run locally

From the src directory:

go run .

Example with Redis:

REDIS_ADDR=127.0.0.1:6379 go run .

Example with Firestore emulator:

FIRESTORE_EMULATOR_HOST=127.0.0.1:8081 FIRESTORE_PROJECT=local-dev go run .

Run the UI (optional)

From the ui directory:

npm install
cp .env.example .env.local
npm run dev

By default the UI calls http://localhost:8080.

If your API runs elsewhere, set:

NEXT_PUBLIC_API_BASE_URL=http://your-api-host:8080

API examples

Create a short URL:

curl -s -X POST http://localhost:8080/shorten \
  -H "Content-Type: application/json" \
  -d '{"url":"https://example.com"}'

Follow a redirect:

curl -i http://localhost:8080/<short-code>

Health checks:

curl -i http://localhost:8080/healthz
curl -i http://localhost:8080/readyz

Testing

From the src directory:

go test ./...
go test -race ./...
go vet ./...

Optional Redis integration test:

INTEGRATION_REDIS_ADDR=127.0.0.1:6379 go test ./...

Optional Firestore emulator integration test:

FIRESTORE_EMULATOR_HOST=127.0.0.1:8081 FIRESTORE_PROJECT=local-dev go test ./...

If env variables are not set, integration tests are skipped automatically.

Reliability behavior

  • Graceful shutdown: on SIGINT/SIGTERM, server stops accepting new requests, waits for in-flight requests, then closes Redis/Firestore clients.
  • Cache failures: cache read/write errors are logged, but the API still serves from the primary store when possible.
  • Collision handling: short-code collisions retry with backoff before returning an error.

Open-source TODO suggestions

  • Add request rate limiting for POST /shorten
  • Add structured metrics (Prometheus/OpenTelemetry)
  • Add end-to-end tests in CI with Redis + Firestore emulator containers
  • Add API versioning and OpenAPI spec

Contributing

Please read CONTRIBUTING.md before opening a pull request.

Code of Conduct

Community expectations are in CODE_OF_CONDUCT.md.

Security

Vulnerability reporting process is in SECURITY.md.

License

This project is licensed under the MIT License. See LICENSE.

About

A serverless URL shortening API built with Go , Firestore for URL mappings, and Redis (Memorystore) for sub-millisecond redirect caching.

Resources

Code of conduct

Contributing

Security policy

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages