A beginner-friendly URL shortener service written in Go.
It supports:
POST /shortento create short linksGET /{code}to redirect to the original URLGET /healthzfor liveness checksGET /readyzfor 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
src/main.go: HTTP handlers, app wiring, Redis cache client, graceful shutdownsrc/firestore_store.go: Firestore-backed implementation of the store interfacesrc/main_test.go: unit tests for handlers and behaviorsrc/integration_test.go: optional integration tests for Redis and Firestore emulatorui/: Next.js + HeroUI frontend for creating and opening short links
- Validate request JSON:
{ "url": "https://example.com" } - Validate URL scheme (
httporhttps) - Generate a short code
- Save mapping in the configured store
- Return
short_url
- Try Redis cache first (if enabled)
- If cache miss, read from store
- Write store result back into cache (best effort)
- Return
302 Foundredirect
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
- Go
1.25+ - Optional: Redis (
REDIS_ADDR) - Optional: Firestore project or emulator (
FIRESTORE_PROJECT)
- 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)
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.
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 .From the ui directory:
npm install
cp .env.example .env.local
npm run devBy default the UI calls http://localhost:8080.
If your API runs elsewhere, set:
NEXT_PUBLIC_API_BASE_URL=http://your-api-host:8080Create 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/readyzFrom 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.
- 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.
- 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
Please read CONTRIBUTING.md before opening a pull request.
Community expectations are in CODE_OF_CONDUCT.md.
Vulnerability reporting process is in SECURITY.md.
This project is licensed under the MIT License. See LICENSE.