BADA helps beachgoers and families in Sweden find safe, EU-classified bathing waters with real-time quality updates.
It replaces outdated or clunky websites with a clean, mobile-friendly experience where you can browse nearby beaches on a map, check water quality, and save your favourites.
Try it out here (deployed on Netlify)
- πΊ Map of all EU-classified beaches in Sweden (MapLibre + OpenStreetMap)
- π Find the nearest beach using your device's location
- π¬ View water quality, classification, and recent test results (data from HaV)
- π Real-time beach search with autocomplete dropdown
- β€οΈ Create an account and save favourite beaches to your profile
- π Drag-and-drop sorting for favorites
- π 3D animated backgrounds (WebGL water simulation in dark mode, sand texture in light mode)
- π¨ Glassmorphism UI with backdrop blur effects
- π€οΈ Weather & sun times panel on each beach β air temp, feels like, UV index, water temperature, and a visual sun arc with golden hour, sunrise/sunset, and twilight phases
- π Dark mode and responsive design (mobile β desktop)
- π Multi-language support (Swedish / English)
- π API Documentation (Swagger UI at
/api/docs)
Frontend
- React 18 + Vite + TypeScript
- React Router v7
- Zustand (global state)
- TanStack Query (server state & caching)
- Tailwind CSS v4 (with CSS-based theme system)
- i18next (translations)
- MapLibre GL (maps)
- Three.js ecosystem (@react-three/fiber, @react-three/drei) for 3D backgrounds
- @dnd-kit (drag-and-drop)
- react-hook-form + Zod (form validation)
- react-hot-toast (notifications)
Custom React Hooks
useGeolocationβ Device location accessuseOutsideCloseβ Close popovers on outside click/Escape keyuseToggleDarkModeβ Writes the.darkclass (used in Header)useDarkModeObserverβ Reads/observes.darkclass via MutationObserver (used in AmbientBackground)usePrefersReducedMotionβ Respect user's motion preferencesuseBeachesβ Beach data fetchinguseWeatherβ Weather data with 30-min stale timeuseSunTimesβ Sun times keyed by date with 12-hr stale time
Backend
- Node.js + Express 5
- MongoDB + Mongoose
- JWT Authentication
- Zod (validation)
- In-memory caching for HaV API responses
- Swagger UI (API documentation)
External APIs
- HaV Bathing Waters API (official Swedish Agency for Marine and Water Management)
- MapTiler (map styles)
- Open-Meteo (weather forecast + marine/water temperature)
- sunrise-sunset.org (sunrise, sunset, golden hour, and twilight times)
BADA is built with accessibility in mind:
- Skip navigation link β Jump directly to main content
- ARIA labels β Proper labeling of interactive elements
- ARIA live regions β Screen reader announcements for dynamic content
- ARIA busy states β Loading indicators for assistive technology
- Keyboard navigation β Search is an ARIA combobox (arrow keys / Enter / Escape); menus and dialogs are keyboard-operable with focus management
- Escape key handling β Close menus and return focus to trigger
- Focus visible rings β Clear focus indicators
- Reduced motion support β Respects
prefers-reduced-motion(both CSS and JS) - Semantic HTML β Proper document structure with roles
- Dynamic lang attribute β HTML lang updates with language selection
- Accessible notifications β Toast messages with proper ARIA live regions
(Coming soon)
Clone the repo and install dependencies:
git clone https://github.com/govargas/bada.git
cd badaBackend
cd backend
cp .env.example .env.local # then fill in your values
npm install
npm run devBackend runs on http://localhost:3000
API Documentation available at http://localhost:3000/api/docs
Frontend
cd frontend
cp .env.example .env.local # then fill in your values
npm install
npm run devFrontend runs on http://localhost:5173
- See .env.example in both backend/ and frontend/.
- Fill in with your own values (MongoDB Atlas, JWT secret, MapTiler key).
- Use these to try the app without registering:
Email: smoke@test.com
Password: Test1234
This account already has some favourite beaches saved.
- Frontend: Deployed on Netlify: https://badaweb.netlify.app/
- Backend: Deployed on Vercel β https://bada-backend.vercel.app/api/health
- β React frontend
- β Node.js + Express backend
- β MongoDB database
- β Authentication (JWT)
- β React Router navigation
- β Global state management (Zustand)
- β β₯2 external libraries (TanStack Query, MapLibre, Three.js, react-hook-form, i18next, react-hot-toast, @dnd-kit)
- β Custom React hooks (useGeolocation, useOutsideClose, useToggleDarkMode, useDarkModeObserver, usePrefersReducedMotion, useBeaches, useWeather, useSunTimes)
- β Responsive (320px β 1600px+)
- β Accessibility features (comprehensive a11y implementation)
- β Clean Code practices
- β Clear structure using box model with consistent margins/paddings
- β Consistent typography across views and breakpoints
- β Cohesive color scheme with CSS design tokens
- β Mobile-first responsive design
- β Dark mode support with 3D backgrounds
- β Multi-language support (Swedish/English)
- β Glassmorphism UI design system
- β Error Boundaries
- β Toast notifications with a11y
- β Reduced motion support
- β Comprehensive documentation
- β Meta tags for SEO (Open Graph, Twitter Cards)
- β API Documentation (Swagger UI)
- β Performance optimizations (lazy-loaded 3D backgrounds)
These are conscious trade-offs for an MVP, documented rather than hidden:
- Backend cache is per-instance. The HaV proxy cache is an in-memory
Map. On Vercel serverless it does not survive cold starts or span instances, so it mainly de-dupes bursts within a warm instance. The beach list sidesteps this entirely via a static CDN snapshot (frontend/public/beaches.json, refreshed daily by a GitHub Action); only per-beach detail still hits the proxy. - Data is seasonal. HaV samples bathing waters roughly JuneβAugust. Outside the season
latestSampleDateand classification reflect the previous year. - Golden hour is approximate. It is computed as sunrise + 1h / sunset β 1h, and the sun arc degrades north of the Arctic Circle during the midnight sun (sunrise-sunset.org returns degenerate times there).
- Third-party APIs called from the client. Open-Meteo and sunrise-sunset.org are called directly from the browser with no SLA β acceptable for an MVP, not for production traffic.
- Auth token in
localStorage. Simple and fine for a no-sensitive-data MVP, but XSS-exposed; an httpOnly cookie would be the production choice. - No SSR. SEO is limited to per-route
<title>, a generatedsitemap.xml, and site-wide Open Graph tags. Per-beach Open Graph previews would require prerendering.
- β 3D animated backgrounds (WebGL water/sand)
- β Enhanced glassmorphism UI
- β Header search with autocomplete
- β Comprehensive accessibility implementation
- β API documentation with Swagger
- β Performance optimization (deferred 3D loading)
- β Focus management and keyboard navigation
- β Weather & sun times panel (Open-Meteo + sunrise-sunset.org) with visual sun arc, golden hour zones, and water temperature
- π Filter beaches by classification
- π Add user beach photos
- π Allow notes/tips per beach (e.g. "good for kids")
- π PWA support with offline capability
- Data from the Swedish Agency for Marine and Water Management (HaV)
- Maps powered by OpenStreetMap + MapTiler
- Built during the Technigo Fullstack JavaScript Bootcamp (2025)
Created by Talo Vargas, 2025