Tiny URL shortener built with Bun + Hono, a Svelte frontend, Redis for storage, and Traefik as the local API gateway.
This README covers local development (Traefik in Docker) and a production outline (Terraform on AWS). See infra/README.md
for full cloud details.
In addition, a GitHub Actions CI/CD pipeline validates changes on pull requests and deploys to AWS on pushes to main
.
infra/
Routing (Traefik):
Docs:
Run from repo root:
docker compose -f docker-compose.dev.yml up -d
What it does:
Bun loads .env files automatically. Create per-service .env files:
packages/shortening-service/.env
REDIS_URL=redis://localhost:6379
BASE_URL=http://localhost
PORT=3001
packages/forwarding-service/.env
REDIS_URL=redis://localhost:6379
BASE_URL=http://localhost
PORT=3002
Frontend needs the API base exposed via Traefik:
packages/frontend/.env.local
VITE_API_BASE_URL=http://localhost
From the repo root (uses workspaces):
bun install
Shortening API (port 3001):
cd packages/shortening-service
bun run dev
Forwarding API (port 3002):
cd packages/forwarding-service
bun run dev
Frontend (Vite dev server):
cd packages/frontend
bun run dev
Traefik will route:
curl -sS -X POST http://localhost/shorten \
-H 'Content-Type: application/json' \
-d '{"longUrl":"https://example.com"}'
Expected response:
{ "shortUrl": "http://localhost/abc123" }
Open the returned shortUrl in the browser to be redirected.
Run all unit tests from the repo root:
bun test
micro-url
├─ docker-compose.dev.yml # Traefik + Redis for local dev
├─ traefik/
│ └─ dynamic/routes.yml # File provider config for Traefik
├─ packages/
│ ├─ frontend/ # Svelte app
│ ├─ shortening-service/ # POST /shorten
│ ├─ forwarding-service/ # GET /{slug}
│ └─ shared/ # common code (env, redis, logger, slug)
└─ redis-data/ # Redis persistence
docker compose ... up -d
is running and REDIS_URL points to redis://localhost:6379
.High-level flow. For details, see infra/README.md
.
Prerequisites:
cd infra && terraform init && terraform apply
infra/lambda/index.js
routes paths. If you change domain/region, update the hardcoded ALB/S3 hostnames and re-apply, or ask to template it.make ecr-login
(uses AWS_ACCOUNT_ID
and AWS_REGION
, defaults set in Makefile)make deploy-all
make SERVICE=shortening deploy
or make SERVICE=forwarding deploy
make frontend
(uses DOMAIN
from Makefile; uploads packages/frontend/dist
to S3)murl.pw
) to the CloudFront distribution (terraform output cloudfront_domain_name
). If using Cloudflare, enable proxy on user-facing records and keep ACM validation CNAMEs DNS-only.make force-aws-redeploy
Useful outputs (cd infra && terraform output
):
cloudfront_domain_name
— target for DNSs3_bucket_domain_name
— frontend bucketalb_dns_name
— ALB endpoint (used by Lambda@Edge and for debugging)Two workflows live under .github/workflows
:
ci.yml
— Continuous Integrationbun test
).packages/frontend/**
, packages/shared/**
, or root config files trigger the check.deploy.yml
— Continuous Deploymentmain
and manual dispatch.shortening
and forwarding
) to ECR using the root Dockerfile
with SERVICE=<name>-service
build-arg.latest
and the commit SHA.linux/amd64
(matches Makefile’s buildx note).forwarding-ecs-service
and shortening-ecs-service
so the new images go live.packages/frontend/dist
to the S3 bucket named ${DOMAIN}-frontend
.Required repository secrets (Settings → Secrets and variables → Actions):
AWS_DEPLOY_ROLE_ARN
— IAM role to assume via OIDC (e.g., arn:aws:iam::877525430326:role/git-deployment-role
).AWS_REGION
— e.g., eu-central-1
.AWS_ACCOUNT_ID
— e.g., 877525430326
.DOMAIN
— your apex, e.g., murl.pw
(used to address S3: murl.pw-frontend
).Notes and options:
terraform plan
on PRs and apply
on main
, migrate state to a remote backend (e.g., S3 + DynamoDB) and add a workflow.