Here's the complete, ready-to-deploy code for your public health dashboard with all tools integrated using free APIs:
1. Project Setup
bash
# Create project folder
mkdir health-dashboard
cd health-dashboard
# Initialize Node.js project
npm init -y
# Install dependencies
npm install express axios cors web-push body-parser dotenv
npm install nodemon --save-dev
# Create necessary files
touch server.js .env .gitignore
mkdir public
cd public
touch index.html styles.css app.js sw.js manifest.json
2. Backend (server.js)
javascript
require('dotenv').config();
const express = require('express');
const axios = require('axios');
const webpush = require('web-push');
const cors = require('cors');
const path = require('path');
const bodyParser = require('body-parser');
const app = express();
// Middleware
app.use(cors());
app.use(bodyParser.json());
app.use(express.static('public'));
// ======================
// API PROXY ENDPOINTS
// ======================
// 1. COVID-19 Data (CDC)
app.get('/api/covid/:state', async (req, res) => {
try {
const { state } = req.params;
const url = `https://data.cdc.gov/resource/9mfq-cb36.json?state=${state}&$limit=7`;
const response = await axios.get(url, {
headers: { "X-App-Token": process.env.CDC_TOKEN || "" }
});
// Process data
const processed = response.data.map(item => ({
date: new Date(item.date).toLocaleDateString(),
cases: item.new_cases,
deaths: item.new_deaths
})).reverse();
res.json(processed);
} catch (error) {
console.error("COVID API Error:", error.message);
res.status(500).json({ error: "Failed to fetch COVID data" });
}
});
// 2. Air Quality (AirNow)
app.get('/api/air/:zip', async (req, res) => {
try {
const url = `https://www.airnowapi.org/aq/observation/zipCode/current/?format=application/json&zipCode=${req.params.zip}&distance=25&API_KEY=${process.env.AIRNOW_KEY}`;
const response = await axios.get(url);
if (response.data.length === 0) {
return res.status(404).json({ error: "No air quality data for this location" });
}
// Get primary pollutant
const primary = response.data.find(item => item.parameter_name === "PM2.5") || response.data[0];
res.json({
aqi: primary.aqi,
pollutant: primary.parameter_name,
category: primary.category.name,
reportingArea: primary.reporting_area
});
} catch (error) {
console.error("AirNow API Error:", error.message);
res.status(500).json({ error: "Failed to fetch air quality data" });
}
});
// 3. Flu Activity (CDC)
app.get('/api/flu', async (req, res) => {
try {
const response = await axios.get(
'https://gis.cdc.gov/grasp/fluview/FluViewPhase2QuickData.json'
);
// Process national data
const nationalData = response.data
.filter(item => item.region === "National")
.sort((a, b) => new Date(b.weekend) - new Date(a.weekend))
.slice(0, 8);
res.json(nationalData);
} catch (error) {
console.error("Flu API Error:", error.message);
res.status(500).json({ error: "Failed to fetch flu data" });
}
});
// 4. Location Services (Nominatim)
app.get('/api/location', async (req, res) => {
try {
const { lat, lng } = req.query;
const response = await axios.get(
`https://nominatim.openstreetmap.org/reverse?format=json&lat=${lat}&lon=${lng}`
);
res.json({
state: response.data.address.state,
zip: response.data.address.postcode,
county: response.data.address.county
});
} catch (error) {
console.error("Geocoding Error:", error.message);
res.status(500).json({ error: "Failed to determine location" });
}
});
// ======================
// PUSH NOTIFICATIONS
// ======================
const subscriptions = [];
// Initialize web-push
webpush.setVapidDetails(
'mailto:admin@healthdashboard.com',
process.env.VAPID_PUBLIC_KEY,
process.env.VAPID_PRIVATE_KEY
);
// Store subscription
app.post('/api/subscribe', (req, res) => {
const subscription = req.body;
subscriptions.push(subscription);
res.status(201).json({});
});
// Send notification (admin endpoint)
app.post('/api/notify', (req, res) => {
const { title, message } = req.body;
const payload = JSON.stringify({
title: title || "Health Alert",
body: message || "New update available",
icon: '/icon-192.png'
});
// Send to all subscribers
const sendPromises = subscriptions.map(sub =>
webpush.sendNotification(sub, payload)
.catch(err => console.error("Push failed:", err))
);
Promise.all(sendPromises)
.then(() => res.json({ success: true }))
.catch(() => res.status(500).json({ error: "Some notifications failed" }));
});
// Serve frontend
app.get('*', (req, res) => {
res.sendFile(path.join(__dirname, 'public', 'index.html'));
});
// Start server
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
3. Frontend (public/app.js)
javascript
// DOM Elements
const covidChartEl = document.getElementById('covidChart');
const fluChartEl = document.getElementById('fluChart');
const aqiValueEl = document.getElementById('aqiValue');
const aqiStatusEl = document.getElementById('aqiStatus');
const locationBtn = document.getElementById('location-btn');
const notifyBtn = document.getElementById('notify-btn');
const stateSelect = document.getElementById('stateSelect');
const zipInput = document.getElementById('zipInput');
// Charts
let covidChart, fluChart;
// Initialize
document.addEventListener('DOMContentLoaded', () => {
loadDefaultData();
setupEventListeners();
checkNotificationSupport();
});
// ======================
// DATA LOADING
// ======================
async function loadDefaultData() {
// Default to California
loadCovidData('CA');
loadAirQuality('90001');
loadFluData();
}
async function loadCovidData(state) {
try {
const response = await fetch(`/api/covid/${state}`);
const data = await response.json();
updateCovidChart(data);
} catch (error) {
console.error("Failed to load COVID data:", error);
showError("Failed to load COVID-19 data");
}
}
async function loadAirQuality(zip) {
try {
const response = await fetch(`/api/air/${zip}`);
const data = await response.json();
updateAirQualityDisplay(data);
} catch (error) {
console.error("Failed to load air quality:", error);
showError("Failed to load air quality data");
}
}
async function loadFluData() {
try {
const response = await fetch('/api/flu');
const data = await response.json();
updateFluChart(data);
} catch (error) {
console.error("Failed to load flu data:", error);
showError("Failed to load flu data");
}
}
// ======================
// VISUALIZATION
// ======================
function updateCovidChart(data) {
const labels = data.map(item => item.date);
const cases = data.map(item => item.cases);
if (covidChart) {
covidChart.data.labels = labels;
covidChart.data.datasets[0].data = cases;
covidChart.update();
} else {
covidChart = new Chart(covidChartEl, {
type: 'line',
data: {
labels: labels,
datasets: [{
label: 'New Cases',
data: cases,
borderColor: 'rgba(220, 53, 69, 1)',
backgroundColor: 'rgba(220, 53, 69, 0.1)',
tension: 0.3,
fill: true
}]
},
options: {
responsive: true,
plugins: {
title: {
display: true,
text: 'COVID-19 Cases (Last 7 Days)'
}
}
}
});
}
}
function updateFluChart(data) {
const labels = data.map(item => `Week ${item.week}`);
const activity = data.map(item => item.activity_level);
if (fluChart) {
fluChart.data.labels = labels;
fluChart.data.datasets[0].data = activity;
fluChart.update();
} else {
fluChart = new Chart(fluChartEl, {
type: 'bar',
data: {
labels: labels,
datasets: [{
label: 'Activity Level',
data: activity,
backgroundColor: 'rgba(13, 110, 253, 0.7)'
}]
},
options: {
responsive: true,
plugins: {
title: {
display: true,
text: 'Influenza Activity Level'
}
}
}
});
}
}
function updateAirQualityDisplay(data) {
// AQI color scale
const aqiColors = {
"Good": "#28a745",
"Moderate": "#ffc107",
"Unhealthy for Sensitive Groups": "#fd7e14",
"Unhealthy": "#dc3545",
"Very Unhealthy": "#6f42c1",
"Hazardous": "#6c757d"
};
aqiValueEl.textContent = data.aqi;
aqiStatusEl.textContent = `${data.category} (${data.pollutant})`;
const bgColor = aqiColors[data.category] || "#6c757d";
aqiValueEl.parentElement.style.backgroundColor = bgColor;
}
// ======================
// LOCATION SERVICES
// ======================
function setupEventListeners() {
// Location button
locationBtn.addEventListener('click', () => {
getLocation()
.then(location => {
stateSelect.value = location.state;
zipInput.value = location.zip;
loadCovidData(location.state);
loadAirQuality(location.zip);
})
.catch(error => {
console.error("Location error:", error);
alert("Could not determine your location. Using default values.");
});
});
// State/ZIP changes
stateSelect.addEventListener('change', (e) => {
loadCovidData(e.target.value);
});
zipInput.addEventListener('change', (e) => {
loadAirQuality(e.target.value);
});
}
function getLocation() {
return new Promise((resolve, reject) => {
if (!navigator.geolocation) {
reject("Geolocation not supported");
return;
}
navigator.geolocation.getCurrentPosition(
async position => {
try {
const { latitude, longitude } = position.coords;
const response = await fetch(`/api/location?lat=${latitude}&lng=${longitude}`);
const location = await response.json();
resolve({
state: location.state,
zip: location.zip.split('-')[0],
county: location.county
});
} catch (error) {
reject("Address lookup failed");
}
},
error => {
reject(error.message);
},
{ timeout: 10000 }
);
});
}
// ======================
// PUSH NOTIFICATIONS
// ======================
function checkNotificationSupport() {
if (!('serviceWorker' in navigator) || !('PushManager' in window)) {
notifyBtn.style.display = 'none';
return;
}
notifyBtn.addEventListener('click', () => {
requestNotificationPermission();
});
}
async function requestNotificationPermission() {
const permission = await Notification.requestPermission();
if (permission === 'granted') {
registerServiceWorker();
} else {
alert("Notifications blocked. You can enable them in browser settings.");
}
}
async function registerServiceWorker() {
try {
const reg = await navigator.serviceWorker.register('/sw.js');
console.log("Service worker registered");
const sub = await reg.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: urlBase64ToUint8Array(process.env.VAPID_PUBLIC_KEY)
});
await fetch('/api/subscribe', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(sub)
});
notifyBtn.textContent = "✓ Alerts Enabled";
notifyBtn.classList.remove('btn-outline-primary');
notifyBtn.classList.add('btn-success');
} catch (error) {
console.error("Push registration failed:", error);
alert("Failed to enable notifications");
}
}
function urlBase64ToUint8Array(base64String) {
const padding = '='.repeat((4 - base64String.length % 4) % 4);
const base64 = (base64String + padding).replace(/-/g, '+').replace(/_/g, '/');
const rawData = window.atob(base64);
return Uint8Array.from([...rawData].map(char => char.charCodeAt(0)));
}
// ======================
// UTILITIES
// ======================
function showError(message) {
const alertEl = document.createElement('div');
alertEl.className = 'alert alert-danger position-fixed top-0 end-0 m-3';
alertEl.textContent = message;
document.body.appendChild(alertEl);
setTimeout(() => {
alertEl.remove();
}, 5000);
}
4. Service Worker (public/sw.js)
javascript
const CACHE_NAME = 'health-dashboard-v1';
const urlsToCache = [
'/',
'/index.html',
'/styles.css',
'/app.js',
'/icon-192.png'
];
// Install service worker
self.addEventListener('install', event => {
event.waitUntil(
caches.open(CACHE_NAME)
.then(cache => cache.addAll(urlsToCache))
);
});
// Cache-first strategy
self.addEventListener('fetch', event => {
event.respondWith(
caches.match(event.request)
.then(response => response || fetch(event.request))
);
});
// Push notifications
self.addEventListener('push', event => {
const payload = event.data?.json() || {
title: 'Health Alert',
body: 'New update available'
};
event.waitUntil(
self.registration.showNotification(payload.title, {
body: payload.body,
icon: payload.icon || '/icon-192.png',
vibrate: [200, 100, 200]
})
);
});
// Notification click handler
self.addEventListener('notificationclick', event => {
event.notification.close();
event.waitUntil(
clients.openWindow('/')
);
});
🔴🟠🟡🟢🔵🟣⚫🔴
5. Frontend HTML (public/index.html)
html
Public Health Dashboard
Public Health Dashboard
Use My Location
Enable Alerts
Select State
California
Texas
Florida
New York
Data sources: CDC, AirNow | Updated:
🔴🟠🟡🟢🔵🟣⚫🔴
6. Styles (public/styles.css)
css
:root {
--light-bg: #f8f9fa;
--dark-bg: #212529;
--light-text: #212529;
--dark-text: #f8f9fa;
}
body {
background-color: var(--light-bg);
color: var(--light-text);
transition: all 0.3s ease;
}
body.dark-mode {
background-color: var(--dark-bg);
color: var(--dark-text);
}
.card {
transition: transform 0.2s ease, box-shadow 0.2s ease;
border: none;
}
.card:hover {
transform: translateY(-5px);
box-shadow: 0 10px 20px rgba(0,0,0,0.1);
}
.chart-container {
position: relative;
height: 300px;
width: 100%;
}
#aqiDisplay {
background-color: #e9ecef;
transition: background-color 0.5s ease;
}
/* Responsive adjustments */
@media (max-width: 768px) {
.chart-container {
height: 250px;
}
.display-2 {
font-size: 3rem;
}
}
🔴🟠🟡🟢🔵🟣⚫🔴
7. Manifest (public/manifest.json)
json
{
"name": "Public Health Dashboard",
"short_name": "HealthDash",
"start_url": "/",
"display": "standalone",
"background_color": "#ffffff",
"theme_color": "#0d6efd",
"icons": [
{
"src": "/icon-192.png",
"type": "image/png",
"sizes": "192x192"
},
{
"src": "/icon-512.png",
"type": "image/png",
"sizes": "512x512"
}
]
}
8. Environment Variables (.env)
ini
# Required for AirNow API
AIRNOW_KEY=YOUR_AIRNOW_KEY
# Optional for CDC API (higher limits)
CDC_TOKEN=YOUR_CDC_TOKEN
# Required for push notifications
VAPID_PUBLIC_KEY=YOUR_PUBLIC_KEY
VAPID_PRIVATE_KEY=YOUR_PRIVATE_KEY
9. Package.json (Add these scripts)
json
"scripts": {
"start": "node server.js",
"dev": "nodemon server.js",
"test": "echo \"Error: no test specified\" && exit 1"
}
How to Run
Get API keys:
AirNow
CDC (optional)
Generate VAPID keys: npx web-push generate-vapid-keys
Start the server:
bash
npm run dev
Open in browser:
text
http://localhost:3000
No comments:
Post a Comment