A dark, sleek weather forecast app built with React and Vite, powered by the OpenWeatherMap API. Built as a portfolio project to demonstrate component architecture, custom hooks, and clean API layering in a real-world frontend app.
- 🔍 City search with autocomplete — debounced (350ms) lookups against the OpenWeatherMap geocoding API
- 🌡️ Current conditions — temperature, "feels like," description, wind, humidity, pressure, visibility, sunrise & sunset
- 📅 5-day forecast — grouped from 3-hour interval data, showing daily highs/lows and a midday icon
- 📍 Geolocation — auto-detects your location on first visit, with a manual "use my location" button and graceful fallback if permission is denied
- ⭐ Favorites — save cities with one tap; persisted in
localStorage - 🌗 Unit toggle — instant °C / °F switching, persisted in
localStorage - ⏳ Full async coverage — loading, error, and empty states handled on every screen
- 🎨 Hand-crafted SVG icons — no icon library, ten custom weather icons mapped to OpenWeatherMap's icon codes
| Layer | Choice |
|---|---|
| UI | React 18 (functional components + hooks) |
| Build tool | Vite |
| Styling | Plain CSS with custom properties |
| Data | OpenWeatherMap (Geocoding, Current, Forecast) |
| Language | JavaScript |
src/
├── components/ UI only — no fetch calls, no business logic
├── hooks/ Reusable stateful logic (data fetching, debouncing, persistence)
├── services/ The only layer allowed to talk to the network
└── utils/ Pure functions — formatting, grouping, no side effects
Why a service layer? Every OpenWeatherMap call lives in services/weatherApi.js. Components never call fetch directly — this keeps API shape changes, error handling, and auth (the API key) in one place, and makes the rest of the app trivially testable.
Why custom hooks? useWeather encapsulates fetching + loading/error state so App.jsx stays declarative. It accepts either a city name string or { lat, lon } coordinates, so the same hook powers search, geolocation, and favorites. useLocalStorage and useDebounce are generic and reusable outside this project.
Why the folder split? Separating components (view), hooks (state), services (I/O), and utils (pure logic) keeps each file testable in isolation and makes it obvious where new code belongs as the app grows.
- Node.js 18+
- A free OpenWeatherMap API key
# Install dependencies
npm install
# Create your local environment file
cp .env.example .envOpen .env and paste your API key:
VITE_OPENWEATHER_API_KEY=your_api_key_here
npm run devnpm run buildAll requests are made from src/services/weatherApi.js.
| Function | Endpoint | Purpose |
|---|---|---|
searchCities(query) |
geo/1.0/direct |
City autocomplete suggestions |
getCurrentWeatherByCity(city) |
data/2.5/weather?q= |
Current weather by city name |
getCurrentWeatherByCoords(lat,lon) |
data/2.5/weather?lat=&lon= |
Current weather by coordinates |
getForecastByCity(city) |
data/2.5/forecast?q= |
5-day / 3-hour forecast by city |
getForecastByCoords(lat,lon) |
data/2.5/forecast?lat=&lon= |
5-day / 3-hour forecast by coords |
Failed requests throw a WeatherApiError with a status code and a human-readable message (invalid API key, city not found, or a generic network failure).
MIT © Erijon Bytyqi
Built with ☕ in Pristina, Kosovo