A simple Weather Dashboard web app where users search a city to get realβtime weather using the OpenWeatherMap API. Searches are saved to a MySQL database. Frontend is HTML/CSS/JS; backend is Node.js + Express.
- Search weather by city name
- Shows temperature, humidity, wind, conditions
- Search history persisted in MySQL
- Simple, responsive UI
- Clean REST API endpoints
Frontend: HTML, CSS, JavaScript
Backend: Node.js, Express.js
Database: MySQL
API: OpenWeatherMap
weather-dashboard/
βββ backend/
β βββ server.js # Express app (serves API)
β βββ db.js # MySQL connection
β βββ routes.js # API routes
β
βββ frontend/
β βββ index.html # UI
β βββ style.css # Styles
β βββ script.js # Client logic (fetches API)
β
βββ database/
β βββ schema.sql # DB schema (table: searches)
β
βββ .env # Environment variables
βββ package.json # Node dependencies/scripts
βββ README.md # You are here
git clone https://github.com/yourusername/weather-dashboard.git
cd weather-dashboard/backend
npm installPORT=5000
DB_HOST=localhost
DB_USER=root
DB_PASSWORD=yourpassword
DB_NAME=weather_dashboard
API_KEY=your_openweathermap_api_key
CREATE DATABASE IF NOT EXISTS weather_dashboard;
USE weather_dashboard;
CREATE TABLE IF NOT EXISTS searches (
id INT AUTO_INCREMENT PRIMARY KEY,
city VARCHAR(100) NOT NULL,
searched_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);You can run the SQL inside
database/schema.sqlas well.
node server.js
# or
npm run start- Open
frontend/index.htmlin your browser (or serve it with a simple HTTP server). - Ensure the frontend calls your backend, e.g.
http://localhost:5000.
Base URL (local): http://localhost:5000
Fetch live weather for a city (proxies OpenWeatherMap and logs the search).
Response (example):
{
"city": "Hyderabad",
"temp": 30.5,
"humidity": 62,
"wind": 3.6,
"condition": "Clouds",
"icon": "04d",
"timestamp": "2025-09-01T13:45:00Z"
}Returns recent searches from MySQL.
Response (example):
[
{"id": 1, "city": "Hyderabad", "searched_at": "2025-09-01T13:40:10Z"},
{"id": 2, "city": "Chennai", "searched_at": "2025-09-01T13:42:33Z"}
]PORTβ Express server port (default 5000)DB_HOST,DB_USER,DB_PASSWORD,DB_NAMEβ MySQL connectionAPI_KEYβ OpenWeatherMap API key
Sign up at OpenWeatherMap to get your API key.
Add these to backend/package.json:
{
"scripts": {
"start": "node server.js",
"dev": "nodemon server.js",
"lint": "eslint ."
}
}db.js
const mysql = require('mysql2/promise');
const pool = mysql.createPool({
host: process.env.DB_HOST,
user: process.env.DB_USER,
password: process.env.DB_PASSWORD,
database: process.env.DB_NAME,
waitForConnections: true,
connectionLimit: 10
});
module.exports = pool;routes.js
const express = require('express');
const fetch = require('node-fetch');
const pool = require('./db');
const router = express.Router();
router.get('/weather', async (req, res) => {
const city = (req.query.city || '').trim();
if (!city) return res.status(400).json({ error: 'City is required' });
try {
const url = \`https://api.openweathermap.org/data/2.5/weather?q=\${encodeURIComponent(city)}&appid=\${process.env.API_KEY}&units=metric\`;
const r = await fetch(url);
if (!r.ok) return res.status(r.status).json({ error: 'Weather API error' });
const data = await r.json();
const payload = {
city: data.name,
temp: data.main.temp,
humidity: data.main.humidity,
wind: data.wind.speed,
condition: data.weather?.[0]?.main || '',
icon: data.weather?.[0]?.icon || ''
};
// log search
await pool.execute('INSERT INTO searches (city) VALUES (?)', [payload.city]);
res.json(payload);
} catch (e) {
console.error(e);
res.status(500).json({ error: 'Server error' });
}
});
router.get('/history', async (_req, res) => {
try {
const [rows] = await pool.execute('SELECT id, city, searched_at FROM searches ORDER BY searched_at DESC LIMIT 20');
res.json(rows);
} catch (e) {
console.error(e);
res.status(500).json({ error: 'Server error' });
}
});
module.exports = router;server.js
require('dotenv').config();
const express = require('express');
const cors = require('cors');
const routes = require('./routes');
const app = express();
app.use(cors());
app.use(express.json());
app.get('/', (_req, res) => res.json({ status: 'ok' }));
app.use('/api', routes);
const port = process.env.PORT || 5000;
app.listen(port, () => console.log('Server listening on', port));const form = document.querySelector('#searchForm');
const input = document.querySelector('#city');
const result = document.querySelector('#result');
const historyList = document.querySelector('#history');
const API_BASE = 'http://localhost:5000'; // change if deployed
async function fetchWeather(city) {
const res = await fetch(\`\${API_BASE}/api/weather?city=\${encodeURIComponent(city)}\`);
if (!res.ok) throw new Error('Failed to fetch weather');
return res.json();
}
async function fetchHistory() {
const res = await fetch(\`\${API_BASE}/api/history\`);
if (!res.ok) return [];
return res.json();
}
form.addEventListener('submit', async (e) => {
e.preventDefault();
const city = input.value.trim();
if (!city) return;
result.textContent = 'Loading...';
try {
const data = await fetchWeather(city);
result.innerHTML = \`
<h3>\${data.city}</h3>
<p>Temp: \${data.temp} Β°C</p>
<p>Humidity: \${data.humidity}%</p>
<p>Wind: \${data.wind} m/s</p>
<p>Condition: \${data.condition}</p>
\`;
loadHistory();
} catch (err) {
result.textContent = err.message;
}
});
async function loadHistory() {
const items = await fetchHistory();
historyList.innerHTML = items.map(i => \`<li>\${i.city} β \${new Date(i.searched_at).toLocaleString()}</li>\`).join('');
}
loadHistory();- Frontend (Vercel/Netlify): Deploy
frontend/and setAPI_BASEto your backend URL. - Backend (Render/Railway): Deploy
backend/, add environment variables from.env. - Database (Railway/PlanetScale/Aiven): Create a MySQL instance and update DB vars.
- ECONNREFUSED / ER_ACCESS_DENIED_ERROR: Check MySQL host/user/password and that MySQL is running.
- CORS errors: Ensure
cors()is enabled andAPI_BASEmatches your backend URL. - 401/404 from OpenWeather: Verify
API_KEYand city spelling. - History not updating: Confirm
INSERT INTO searchesis executed and table exists.
MIT