Real-time parking availability in Turin, Italy. Open data from 5T Torino updated every 2 minutes.
Full-stack application that aggregates real-time parking data from Turin's open data platform (5T) and enriches it with static details from GTT (address, rates, payment methods, transit connections). Designed mobile-first with an Apple-inspired UI.
Built as a full-stack data engineering portfolio piece around a real problem I actually wanted solved: parking availability in Turin, surfaced on a map with the same UX standard as commercial apps but without proprietary lock-in.
The technical playground that matters more than the app itself: high-frequency external ingestion (XML feed every 2 minutes) with deduplication and snapshotting, PostGIS spatial queries for "near me" geolocation, transparent caching with ETags + compression, multi-tier sliding-window rate limiting, and clean API/frontend separation. Open data is the perfect substrate — it removes API-cost concerns and lets focus stay on the architecture.
Designed to run self-hosted on commodity infrastructure:
| Component | Cost | Notes |
|---|---|---|
| Linux host (1 vCPU / 1 GB RAM) | ~$4-6/month | Hetzner CX11, Vultr, or any equivalent VPS |
| PostgreSQL 16 + PostGIS 3.4 | $0 | Containerized, part of the Docker Compose stack |
| Redis 7 | $0 | Containerized (optional, graceful degradation if absent) |
| Mapbox tiles | Free | Up to 50,000 map loads/month on free tier |
| 5T Open Data API | $0 | Public open data, no rate limit |
| Open-Meteo (weather) | $0 | No API key required for non-commercial use |
Whole stack fits in ~600 MB RAM. The architecture is designed so a developer can self-host a city of <100 parking lots for under $10/month total — yet scales horizontally (Redis cache, async APScheduler, stateless API) the moment traffic justifies it.
- Interactive Leaflet map with Mapbox tiles (dark/light theme auto-detection)
- Color-coded markers by occupancy: green (free), amber (filling), red (full), grey (closed/out of service)
- Marker clustering with triangle indicators for nearly-full lots
- Geolocation with PostGIS spatial queries ("Near me")
- Parking detail panel: rates, payment methods, transit lines, accessibility info
- Historical availability chart (last 6 hours, hourly aggregation)
- POI layers: hospitals and universities with nearest-parking suggestions
- Real-time weather display (Open-Meteo, no API key required)
- Mobile: iOS-style bottom sheet with swipe gestures, frosted glass backdrop, 44px touch targets
- Desktop: collapsible sidebar with live statistics
- Smart auto-refresh: 2 min browsing, 30s after geolocation
- FastAPI async REST API with structured logging (structlog)
- Redis cache with transparent compression (orjson + zlib) and ETag support
- PostgreSQL + PostGIS for spatial queries and time-series snapshots
- In-process APScheduler: fetch 5T data (2 min), log cache stats (hourly), purge old snapshots (daily)
- API key management with HMAC-SHA256 hashing and configurable salt
- Multi-tier sliding-window rate limiting (anonymous / authenticated / premium)
- Input validation via Pydantic, CORS middleware, Sentry integration (optional)
Client (Browser/Mobile)
│
▼
┌──────────────────────────┐
│ React 19 + Vite + TS │ :3000
│ Leaflet + Mapbox tiles │
└──────────┬───────────────┘
│ JSON
▼
┌──────────────────────────┐
│ FastAPI Backend │ :8000
│ APScheduler (in-process) │──→ 5T Open Data API (XML)
└────┬─────────┬───────────┘
│ │
▼ ▼
PostgreSQL Redis
+ PostGIS Cache
# Clone
git clone https://github.com/MK023/TorinoParking.git
cd TorinoParking
# Configure environment
cp .env.example .env
# Edit .env: set ADMIN_API_KEY, POSTGRES_PASSWORD, VITE_MAPBOX_TOKEN
# Start all services
docker compose up -d
# Or use the helper script (auto-detects Doppler secrets)
./scripts/start.sh| Service | URL |
|---|---|
| Frontend | http://localhost:3000 |
| Backend | http://localhost:8000 |
| API Docs | http://localhost:8000/docs |
The project supports Doppler for secrets management. If Doppler CLI is configured, the start script auto-detects it and injects secrets. Otherwise, it falls back to the .env file.
# With Doppler
doppler run -- docker compose up -d
# Without Doppler
docker compose up -d # reads from .env| Method | Endpoint | Description |
|---|---|---|
| GET | /api/v1/parkings |
All parkings (cached) |
| GET | /api/v1/parkings?available=true |
Filter by availability |
| GET | /api/v1/parkings?min_spots=5 |
Filter by minimum free spots |
| GET | /api/v1/parkings/nearby |
Spatial search (lat/lng/radius) |
| GET | /api/v1/parkings/{id}/history |
Historical snapshots |
| GET | /health |
Health check |
Full interactive docs at /docs (Swagger UI) or /redoc.
| Layer | Technology |
|---|---|
| Frontend | React 19, TypeScript 5, Vite 7, Leaflet, Mapbox |
| Backend | Python 3.12, FastAPI, SQLAlchemy 2, Pydantic 2 |
| Database | PostgreSQL 16 + PostGIS 3.4 |
| Cache | Redis 7 (LRU, 512MB) |
| Scheduler | APScheduler (AsyncIO, in-process) |
| CI/CD | GitHub Actions (lint, test, build, security audit) |
| Infra | Docker Compose, Doppler (optional) |
- Real-time: 5T Torino Open Data — 40 parking facilities, updated every 2 minutes
- Static enrichment: GTT — 22 parkings with address, rates, payment methods, transit connections
- Weather: Open-Meteo — current conditions, no API key required
- POI: Hospitals (8) and universities (6) with GPS coordinates
| Document | Content |
|---|---|
| ARCHITECTURE.md | System architecture and data flow |
| SETUP.md | Development setup guide |
| SECURITY.md | Threat model and security practices |
| GDPR.md | Privacy compliance reference |
| ROADMAP.md | Roadmap and progress tracking |
# Backend linting
ruff check app/ tests/
ruff format app/ tests/
# Backend tests
pytest tests/ -v --cov=app
# Frontend type check
cd frontend && npx tsc --noEmit
# Frontend build
cd frontend && npm run buildMarco Bellingeri — @MK023