9.9 KiB
KMountain Flower Radio Station
A three-tier web application for a community radio station: Angular 21 frontend, FastAPI backend, PostgreSQL database.
Renders a hero page, about page, broadcast schedule, donation tiers, upcoming events, and a contact form — styled with a warm mountain/nature aesthetic.
Quick Start
Option A — Docker Compose (recommended)
# Start all services
docker compose up --build
# Seed the database (first run only)
docker compose exec api python seed.py
Open http://localhost:4200.
Option B — Manual (frontend + backend separately)
# Database
createdb kmountain
export KMTN_DATABASE_URL=postgresql+asyncpg://postgres:postgres@localhost:5432/kmountain
# Backend
cd backend && pip install -r requirements.txt
uvicorn app.main:app --reload
# Frontend (new terminal)
npm install && ng serve
Open http://localhost:4200 and ensure the backend is running on http://localhost:8000.
Prerequisites
| Requirement | Version | Notes |
|---|---|---|
| Docker + Docker Compose | 24+ | For the Docker Compose path |
| Node.js | 20+ | For manual frontend development |
| npm | 11+ | |
| Python | 3.12+ | For manual backend development |
| PostgreSQL | 16+ | For manual backend development (Docker handles it) |
Architecture
┌──────────────┐
│ Browser │
│ :4200 / :80│
└──────┬───────┘
│
┌────────────┴────────────┐
│ │
┌────▼─────┐ ┌──────▼─────┐
│ Nginx │ /api/* │ FastAPI │
│ (static) │─ proxy ──▶ │ :8000 │
└────┬─────┘ └──────┬─────┘
│ │
┌────▼─────┐ ┌──────▼─────┐
│ Angular 21│ │ PostgreSQL │
│ SPA │ │ :5432 │
└──────────┘ └────────────┘
Three tiers:
- Frontend — Angular 21 SPA built via multi-stage Dockerfile and served by Nginx. In production, Nginx serves the static files and proxies
/api/*requests to the backend. In development, both run on separate ports and the browser talks directly to both. - Backend — Python FastAPI with async SQLAlchemy. Provides CRUD REST endpoints for programs (broadcast schedule), events (upcoming events), and donation tiers. Auto-generates Swagger/OpenAPI docs at
/docs. - Database — PostgreSQL 16. Schema contains three tables:
programs,events,donation_tiers. Initial data is loaded via seed.py.
Project Structure
.
├── docker-compose.yml # 3-service stack (db, api, frontend)
├── Dockerfile # Multi-stage: Angular build → Nginx serve
├── nginx.conf # Static files + /api/* proxy
├── .env.example # Environment variable template
├── .devcontainer/ # VS Code dev container
│
├── src/ # Angular 21 frontend
│ ├── app/ # Standalone components
│ │ ├── app.routes.ts # Lazy-loaded route map
│ │ ├── hero/ about/ schedule/ donate/ events/ contact/ navbar/ footer/
│ │ ├── services/ # HTTP services (program, event, tier)
│ │ └── interfaces/ # TypeScript types (Program, Event, Tier)
│ ├── environments/ # dev/prod config (apiBaseUrl)
│ └── styles/ # SCSS design tokens
│ ├── _variables.scss # Colors, spacing, breakpoints
│ ├── _mixins.scss # Card, button, responsive mixins
│ └── _themes.scss # SCSS → CSS custom properties
│
└── backend/ # FastAPI backend
├── Dockerfile # Python gunicorn/uvicorn
├── requirements.txt
├── seed.py # Initial data for all models
└── app/
├── main.py # FastAPI app, CORS, router registration
├── config.py # ENV-based settings (KMTN_* prefix)
├── database.py # async engine + session factory
├── models.py # SQLAlchemy ORM (Program, Event, DonationTier)
├── schemas.py # Pydantic schemas (Create/Update/Response)
└── api/
├── programs.py # CRUD /api/programs
├── events.py # CRUD /api/events
└── tiers.py # CRUD /api/tiers
Local Development
Docker Compose
docker compose up --build # Start all services
docker compose exec api python seed.py # Seed database (first run)
docker compose logs -f api # Stream backend logs
docker compose down # Stop and remove containers
The pgdata volume persists the database across container restarts.
Manual Development
Run all three layers in parallel:
# 1. Database (install PostgreSQL 16 locally)
createdb kmountain
# 2. Backend (new terminal)
cd backend
pip install -r requirements.txt
export KMTN_DATABASE_URL=postgresql+asyncpg://postgres:postgres@localhost:5432/kmountain
export KMTN_CORS_ORIGINS='["http://localhost:4200"]'
uvicorn app.main:app --reload
# 3. Frontend (another terminal)
npm install
ng serve
Configuration
| Variable | Default | Where | Description |
|---|---|---|---|
KMTN_DATABASE_URL |
postgresql+asyncpg://postgres:postgres@localhost:5432/kmountain |
backend/app/config.py | Async PostgreSQL connection string |
KMTN_CORS_ORIGINS |
["http://localhost:4200"] |
backend/app/config.py | CORS allowlist (JSON array) |
apiBaseUrl |
http://localhost:8000 (dev) / '' (prod) |
src/environments/ | Where the Angular app finds the API |
Environment variables are prefixed with KMTN_ in the backend. For production, set KMTN_DATABASE_URL to your managed PostgreSQL connection and update KMTN_CORS_ORIGINS to your actual domain.
Building
# Frontend (produces dist/radio-station/browser/)
ng build --configuration production
# Backend Docker image
docker build -t kmountain-api -f backend/Dockerfile backend/
# Full production stack
docker compose up --build -d
Testing
ng test # Angular + Vitest
The backend API is self-documented at http://localhost:8000/docs (Swagger UI). Run docker compose exec api python seed.py and visit that URL to interactively test endpoints.
API Reference
The backend exposes CRUD endpoints for three models:
| Method | Endpoint | Description |
|---|---|---|
GET |
/api/programs |
List all programs (?day=1..7 to filter by day of week) |
GET |
/api/programs/:id |
Single program |
POST |
/api/programs |
Create program |
PUT |
/api/programs/:id |
Update program |
DELETE |
/api/programs/:id |
Delete program |
GET |
/api/events |
List events (?active=true to filter active events) |
GET |
/api/events/:id |
Single event |
POST |
/api/events |
Create event |
PUT |
/api/events/:id |
Update event |
DELETE |
/api/events/:id |
Delete event |
GET |
/api/tiers |
List donation tiers (ordered by display_order) |
GET |
/api/tiers/:id |
Single tier |
POST |
/api/tiers |
Create tier |
PUT |
/api/tiers/:id |
Update tier |
DELETE |
/api/tiers/:id |
Delete tier |
Full interactive API docs at http://localhost:8000/docs.
Deployment
Production (Docker Compose on a VPS)
# 1. Clone repo on server
git clone <repo-url> kmountain
cd kmountain
# 2. Edit docker-compose.yml
# - Change POSTGRES_PASSWORD to a strong value
# - Update KMTN_CORS_ORIGINS to your domain
# - Optionally set KMTN_DATABASE_URL to a managed PostgreSQL
# 3. Build and start
docker compose up --build -d
# 4. Seed the database
docker compose exec api python seed.py
Production hardening checklist
- Change the default
POSTGRES_PASSWORDin docker-compose.yml - Update
KMTN_CORS_ORIGINSto your actual domain (notlocalhost) - Set
KMTN_DATABASE_URLto a managed PostgreSQL connection (RDS, Cloud SQL, etc.) - Add a reverse proxy (Caddy, Traefik) in front of Nginx for HTTPS
- Set up log rotation for Docker containers
- Configure Docker image registry and push/pull workflow
Development Tools
- Dev container — Open the repo in VS Code → "Reopen in Container" for a pre-configured environment with Angular CLI, Node.js, Python 3, and Git (.devcontainer/).
- VS Code workspace — workspace file in the repo root configures both Angular and Python extensions.
- Prettier — Run
npx prettier --write .to format the codebase. - Prettier config — .prettierrc (100 char width, single quotes, Angular HTML parser).
Tech Stack
| Layer | Technology |
|---|---|
| Frontend | Angular 21 (standalone components, signals-ready) |
| Backend | FastAPI 0.115, Python 3.12 |
| Database | PostgreSQL 16 (async via SQLAlchemy 2.0) |
| Styling | SCSS with design tokens (variables → mixins → CSS custom properties) |
| Routing | Angular Router (lazy loaded) + Nginx SPA fallback |
| HTTP | RxJS observables (frontend → FastAPI) |
| Containers | Docker Compose (3 services: db, api, frontend) |
| CI/CD | (not yet configured) |
| Testing | Vitest (via Angular CLI) |