curl https://mini-url.me/health
{ "status": "ok" }
miniurl is a Go service backed by PostgreSQL and Redis. It exposes five HTTP endpoints — shorten a URL, shorten a batch, redirect, look up metadata, and a health probe. Clean architecture in the source, Docker Compose for local dev, and the UI you're looking at is served by the same binary.
The source follows a clean-architecture layout. Business rules sit in
internal/domain and internal/usecase, which
depend on nothing but each other. The Postgres and Redis adapters live
under internal/repository, the HTTP handlers under
internal/delivery/http, and main.go wires it
all together at the composition root.
On the data side: a single urls table in Postgres holds
every mapping, indexed by short_code. Redis fronts the hot
lookups and also backs the per-API-key rate limiter. Short codes come
from a Snowflake ID generator, Base62-encoded, so they stay
collision-free without a lookup.
Bulk inserts go through pgx.CopyFrom rather than per-row
INSERTs, which is why a single bulk call can take up to 100,000 URLs.
API keys are checked by a header middleware, rate limits are enforced
per-minute per-key, and the redirect path falls through Redis to
Postgres and warms the cache on the way back.
Five endpoints. JSON in and out. Errors come back as
{ "error": "..." } with a matching HTTP status — 400 for
bad input, 401 without a key, 404 on unknown codes, 409 on a
custom-code collision, 410 if the link has expired.
Anything under /api/* requires X-API-Key.
The dev key for local runs is dev-key; set
API_KEYS in .env to change it.
curl https://mini-url.me/health
{ "status": "ok" }
curl -X POST https://mini-url.me/api/shorten \
-H "X-API-Key: dev-key" \
-H "Content-Type: application/json" \
-d '{
"long_url": "https://example.com/very/long/path",
"custom_code": "launch24",
"expires_in_days": 30
}'
{
"short_code": "launch24",
"short_url": "https://mini-url.me/launch24",
"long_url": "https://example.com/very/long/path",
"created_at": "2026-06-20T10:00:00Z",
"expires_at": "2026-07-20T10:00:00Z"
}
curl -X POST https://mini-url.me/api/bulk/shorten \
-H "X-API-Key: dev-key" \
-H "Content-Type: application/json" \
-d '{
"urls": [
"https://example.com/a",
"https://example.com/b"
],
"expires_in_days": 7
}'
{
"total": 2,
"results": [
{ "short_code": "aB3xZ", "short_url": "https://mini-url.me/aB3xZ" },
{ "short_code": "kP9mQ", "short_url": "https://mini-url.me/kP9mQ" }
]
}
curl -i https://mini-url.me/launch24
HTTP/1.1 301 Moved Permanently Location: https://example.com/very/long/path
curl https://mini-url.me/api/urls/launch24 \ -H "X-API-Key: dev-key"
{
"id": 1024,
"short_code": "launch24",
"long_url": "https://example.com/very/long/path",
"created_at": "2026-06-20T10:00:00Z",
"expires_at": "2026-07-20T10:00:00Z",
"click_count": 0
}
The repo ships with a docker-compose.yml that brings up
Postgres, Redis, and the API together. You don't need Go installed to
try it.
git clone <repo> cd short-url/api docker compose up -d --build
curl http://localhost:8080/health
# { "status": "ok" }
curl -X POST http://localhost:8080/api/shorten \
-H "X-API-Key: dev-key" \
-H "Content-Type: application/json" \
-d '{"long_url":"https://example.com"}'
# replace <code> with the short_code from the previous response curl -iL http://localhost:8080/<code>
docker compose down # stop containers docker compose down -v # also wipe the Postgres volume
Without Docker: run postgres and redis
locally, copy .env.example to .env, and
go run . — the schema migrates on first boot.