A distributed, microservices-based jokes platform deployed on Azure. Users can browse jokes by category, submit new ones, and an authenticated moderator reviews submissions before they go live.
┌─────────────────────────────────────┐
│ Kong API Gateway │
│ (rate limiting, SSL termination) │
└──────┬──────────┬───────────┬───────┘
│ │ │
┌───────────┘ ┌─────┘ ┌────┘
▼ ▼ ▼
┌────────────┐ ┌────────────┐ ┌───────────┐
│ jokes │ │ submit │ │ moderate │
│ service │ │ service │ │ service │
│ :3000 │ │ :3200 │ │ :3100 │
└─────┬──────┘ └─────┬──────┘ └─────┬─────┘
│ │ │
│ ┌──────▼────────────────▼──────┐
│ │ RabbitMQ │
│ │ submit queue → moderate │
│ │ moderated queue → ETL │
│ │ type_update exchange (fanout)│
│ └──────────────┬────────────────┘
│ │
▼ ▼
┌────────────────────────────────┐
│ ETL service │
│ seeds DB · inserts jokes │
│ broadcasts type_update events │
└────────────────────────────────┘
│
▼
┌──────────────────┐
│ MySQL / MongoDB │
│ (switchable via │
│ DB_TYPE env) │
└──────────────────┘
| Service | Port | Description |
|---|---|---|
| jokes | 3000 | REST API — serves jokes from the database |
| submit | 3200 | Public UI + API — accepts joke submissions via RabbitMQ |
| moderate | 3100 | Auth0-protected UI — moderators approve or reject submissions |
| etl | 3001 | Consumes approved jokes from queue, writes to DB, broadcasts new types |
| Kong | 80/443 | API gateway with rate limiting (100 req/min on jokes API) |
| RabbitMQ | 5672 | Message broker between submit → moderate → ETL |
- User submits a joke via submit → pushed to
submitqueue - Moderator reviews it via moderate → approved joke pushed to
moderatedqueue - ETL consumes the
moderatedqueue → inserts joke into DB - If a new joke type was created, ETL publishes a
type_updatefanout event - submit and moderate subscribe to
type_updateand update their local type caches
- Node.js / Express — all services
- MySQL 8 (default) or MongoDB 7 — switchable via
DB_TYPEenv var - RabbitMQ 3 — message queuing (durable queues, fanout exchange)
- Kong — API gateway (DB-less declarative config)
- Auth0 — OIDC authentication for the moderation UI
- Docker / Docker Compose — local development and production deployment
- Terraform — Azure infrastructure as code (VNets, NSGs, Linux VMs)
- Cloudflare — DNS + Let's Encrypt SSL via Terraform
- Docker and Docker Compose
# 1. Copy the env example and fill in your values
cp .env.example .env
# 2. Start all services (MySQL + RabbitMQ + all microservices + Kong)
docker compose upServices will be available at:
| URL | Service |
|---|---|
http://localhost |
Jokes frontend (via Kong) |
http://localhost/submit |
Submit a joke (via Kong) |
http://localhost/moderate |
Moderation UI (via Kong) |
http://localhost:4000 |
Jokes API (direct) |
http://localhost:4200/docs |
Submit API — Swagger docs |
http://localhost:15672 |
RabbitMQ management UI |
- Create a Regular Web Application in your Auth0 dashboard
- Add
http://localhost:4100/callbackto Allowed Callback URLs - Add
http://localhost:4100to Allowed Logout URLs - Fill in the
AUTH0_*variables in your.envfile
The jokes and etl services support both MySQL and MongoDB:
# Use MongoDB instead of MySQL
DB_TYPE=mongo docker compose -f compose/docker-compose-local.yml up| Method | Path | Description |
|---|---|---|
GET |
/types |
List all joke categories |
GET |
/joke/:type |
Get a random joke of a given type |
GET |
/joke/:type?count=N |
Get N random jokes |
GET |
/joke/count/:type |
Count jokes of a given type |
Full OpenAPI documentation available at http://localhost:4200/docs
| Method | Path | Description |
|---|---|---|
GET |
/types |
List all joke categories (cached) |
POST |
/submit |
Submit a joke { setup, punchline, type } |
| Method | Path | Description |
|---|---|---|
GET |
/moderate |
Fetch next pending joke from queue |
POST |
/moderated |
Approve and publish a joke |
POST |
/reject |
Reject a joke |
Infrastructure is managed with Terraform and deploys one Linux VM per service into separate subnets with NSG rules.
infra/
├── main.tf # Azure + Cloudflare providers, resource groups
├── network.tf # VNet, subnets, NSGs, public IPs, NICs
├── vm.tf # VMs, Docker image build/push, docker compose up
└── variables.tf # All variable declarations
cd infra
# Copy and fill in your variables
cp terraform.tfvars.example terraform.tfvars
# Sensitive values (SSH key, tokens) go in a separate gitignored file
cat > secret.tfvars <<EOF
pub_key = "$(cat ~/.ssh/id_rsa.pub)"
dockerhub_token = "YOUR_TOKEN"
cloudflare_token = "YOUR_TOKEN"
cloudflare_zone_id = "YOUR_ZONE_ID"
EOF
terraform init
terraform apply -var-file="secret.tfvars"Terraform will:
- Create the Azure resource groups, VNet, subnets and NSGs
- Build and push Docker images to Docker Hub
- Provision the VMs, install Docker, copy compose files
- Start all services via
docker compose up -d - Obtain a Let's Encrypt certificate via Certbot
- Create a Cloudflare DNS A record pointing to the Kong VM
.
├── jokes/ # Jokes REST API service
├── submit/ # Joke submission service + UI
├── moderate/ # Moderation service + UI (Auth0 protected)
├── etl/ # ETL: seeds DB, consumes approved jokes
├── kong/ # Kong declarative config (local + prod)
├── infra/ # Terraform (Azure + Cloudflare)
├── data/ # Initial jokes dataset (JSON)
├── compose/ # Deployment compose files (per-VM + variants)
│ ├── docker-compose-local.yml # Local dev with MongoDB
│ ├── docker-compose-build.yml # Build + push images to Docker Hub
│ ├── docker-compose-db.yml # Prod: DB VM (MySQL + jokes + ETL)
│ ├── docker-compose-rabbitmq.yml # Prod: RabbitMQ VM
│ ├── docker-compose-submit.yml # Prod: submit VM
│ └── docker-compose-moderate.yml # Prod: moderate VM
└── docker-compose.yml # Local dev (all services)