Node.js Development

Building a Simple Weather App using the MySQL, Express, React, and Node.js

By the Domain India teamPublished 8 min read
Knowledge base article
Contents (7 sections)

A weather app is a compact full-stack project: a React page asks your own Node.js and Express API for a city's weather, the API calls a weather service, and MySQL caches the answer. This guide builds it with current tools (Vite, Express 5, mysql2) and one rule kept throughout: the weather API key stays on the server.

Key takeaways

Build the front end with Vite and React, and call only your own /api/weather route. The Express server holds the weather API key in an environment variable, validates the city name, calls the provider, and caches each result in MySQL for ten minutes using parameterised queries. For production, build the React app and let Express serve it. On Domain India cPanel or DirectAdmin hosting, run it with the panel's Node.js tool and set the key in its environment variables.

1. How the app fits together

  1. Browser.
    The React page sends GET /api/weather?city=Pune to your own server. It never talks to the weather provider and never sees the key.
  2. Express.
    Validates the city name, then looks for a fresh cached answer in MySQL.
  3. Weather provider.
    On a cache miss, Express calls the provider with the secret key.
  4. MySQL.
    Stores the answer, so repeat searches are fast and you stay inside the provider's free quota.
Why the key must not be in React

Anything bundled into the front end, including variables Vite exposes with the VITE_ prefix, is readable by every visitor. A key there can be copied and your quota used up. Keep it in the server's environment only.

2. Set up the project

You need a current Node.js LTS release (24 at the time of writing), a MySQL or MariaDB database, and a free API key from a weather provider. This guide uses OpenWeatherMap's current weather endpoint; any provider with a similar JSON API works the same way.

bash
mkdir weather-app && cd weather-app
npm init -y && npm pkg set type=module
npm install express mysql2

Create a .env file and add it to .gitignore:

bash
WEATHER_API_KEY=your-key-here
DB_HOST=localhost
DB_USER=weather_user
DB_PASSWORD=a-strong-password
DB_NAME=weather_db

Node.js reads it directly with node --env-file=.env server.js, so the dotenv package is optional.

3. Create the cache table

Run this once, in phpMyAdmin or the MySQL command line:

sql
CREATE TABLE weather_cache (
  city       VARCHAR(80) NOT NULL PRIMARY KEY,
  data       TEXT        NOT NULL,
  fetched_at TIMESTAMP   NOT NULL DEFAULT CURRENT_TIMESTAMP
);

The JSON response is stored as text, which works the same on MySQL and MariaDB. A migration tool is not needed for one table.

4. Build the Express API

javascript
// server.js
import express from 'express';
import mysql from 'mysql2/promise';
import path from 'node:path';

const app = express();
const db = mysql.createPool({
  host: process.env.DB_HOST,
  user: process.env.DB_USER,
  password: process.env.DB_PASSWORD,
  database: process.env.DB_NAME,
  connectionLimit: 5,
});

const CITY = /^[\p{L} .'-]{2,60}$/u;

app.get('/api/weather', async (req, res) => {
  const city = String(req.query.city ?? '').trim();
  if (!CITY.test(city)) return res.status(400).json({ error: 'Enter a valid city name' });
  const key = city.toLowerCase();

  const [rows] = await db.execute(
    'SELECT data FROM weather_cache WHERE city = ? AND fetched_at > NOW() - INTERVAL 10 MINUTE',
    [key],
  );
  if (rows.length) return res.json(JSON.parse(rows[0].data));

  const url = new URL('https://api.openweathermap.org/data/2.5/weather');
  url.search = new URLSearchParams({ q: city, units: 'metric', appid: process.env.WEATHER_API_KEY });
  const r = await fetch(url, { signal: AbortSignal.timeout(8000) });
  if (r.status === 404) return res.status(404).json({ error: 'City not found' });
  if (!r.ok) return res.status(502).json({ error: 'Weather service unavailable' });

  const w = await r.json();
  const result = {
    city: w.name,
    country: w.sys?.country,
    tempC: Math.round(w.main.temp),
    feelsLikeC: Math.round(w.main.feels_like),
    humidity: w.main.humidity,
    description: w.weather?.[0]?.description,
  };

  await db.execute(
    `INSERT INTO weather_cache (city, data) VALUES (?, ?)
     ON DUPLICATE KEY UPDATE data = VALUES(data), fetched_at = CURRENT_TIMESTAMP`,
    [key, JSON.stringify(result)],
  );
  res.json(result);
});

// Serve the built React app in production
const dist = path.resolve('client/dist');
app.use(express.static(dist));
app.get('/{*splat}', (req, res) => res.sendFile(path.join(dist, 'index.html')));

app.listen(process.env.PORT || 3001);

What makes this safe and current:

  • Parameterised queries (? placeholders with execute) make SQL injection impossible through the city name.
  • Input validation accepts letters in any language, spaces, dots, apostrophes and hyphens only.
  • Only the fields you need are returned, so the provider's full response and your key never reach the browser.
  • Node's built-in fetch replaces axios on the server, with a timeout so a slow provider cannot hang requests.
  • Express 5 passes errors from async handlers to its error handler automatically, and uses the /{*splat} syntax for catch-all routes.

Test it with node --env-file=.env server.js, then open http://localhost:3001/api/weather?city=Mumbai.

5. Build the React front end with Vite

Create React App is deprecated; use Vite:

bash
npm create vite@latest client -- --template react
cd client && npm install

In client/vite.config.js, add server: { proxy: { '/api': 'http://localhost:3001' } } so that /api calls reach Express during development.

jsx
// client/src/App.jsx
import { useState } from 'react';

export default function App() {
  const [city, setCity] = useState('');
  const [weather, setWeather] = useState(null);
  const [error, setError] = useState('');
  const [loading, setLoading] = useState(false);

  async function search(e) {
    e.preventDefault();
    setLoading(true); setError(''); setWeather(null);
    try {
      const r = await fetch(`/api/weather?city=${encodeURIComponent(city)}`);
      const body = await r.json();
      if (!r.ok) throw new Error(body.error || 'Something went wrong');
      setWeather(body);
    } catch (err) {
      setError(err.message);
    } finally {
      setLoading(false);
    }
  }

  return (
    <main>
      <h1>Weather</h1>
      <form onSubmit={search}>
        <label htmlFor="city">City</label>
        <input id="city" value={city} onChange={(e) => setCity(e.target.value)} required />
        <button disabled={loading}>{loading ? 'Loading…' : 'Search'}</button>
      </form>
      {error && <p role="alert">{error}</p>}
      {weather && (
        <section aria-live="polite">
          <h2>{weather.city}, {weather.country}</h2>
          <p>{weather.tempC}°C, feels like {weather.feelsLikeC}°C</p>
          <p>{weather.description} · Humidity {weather.humidity}%</p>
        </section>
      )}
    </main>
  );
}

Run npm run dev in client and the server in another terminal. When it works, run npm run build in client to produce client/dist, which Express serves.

6. Prepare for production

  • Add "start": "node server.js" to the scripts in the root package.json.
  • Set every variable from .env in your host's environment settings rather than uploading the file. If you must upload it, keep it outside the public web folder.
  • Build the React app on your own computer and upload client/dist, so the server does not need to run the build.
  • Handle provider rate limits: the ten-minute cache already cuts repeat calls sharply.

7. Running this on Domain India

WhereHow it runsDatabase
cPanel or DirectAdmin shared hostingThe panel's Node.js tool (Setup Node.js App on cPanel)MySQL, created in the panel
App PlatformNode.js detected and built automaticallyPostgreSQL included, not MySQL
VPSYou install and manage everything; no cPanelWhatever you install

Shared hosting fits this app well. In cPanel, open Setup Node.js App, create an application with the startup file server.js, upload your files (without node_modules), click Run NPM Install and start the app. Put WEATHER_API_KEY and the database settings in the app's Environment variables section, not in a file under public_html. When we checked on 23 September 2026, cPanel offered Node.js 20, 22 and 24; choose 22 or 24. The step-by-step guide is How to deploy a Node.js app on shared hosting, and database setup is in How to connect to your MySQL database. cPanel can serve a cached copy of a page for up to 120 minutes, so add &t=1 to a URL when testing a change. If the app cannot reach the weather service, ask support to check outbound access from your account.

App Platform builds Node.js apps automatically, but its included database is PostgreSQL. Switch mysql2 to the pg package and adjust the SQL (for example ON CONFLICT (city) DO UPDATE), and read the connection string from DATABASE_URL. See Getting started with the App Platform.

cPanel Starter
₹125/mo + GST
  • 25 GB NVMe SSD Storage
  • 50 GB Monthly Bandwidth
  • 1 Website
  • 10 Email Accounts
See plan details
App Starter
₹100/mo + GST
  • 512 MB RAM per app
  • 1 vCPU
  • 5 GB NVMe SSD
  • PostgreSQL Database
See plan details
Where should I keep the weather API key in a React app?

On the server only. Put it in an environment variable read by Express, and have React call your own API route. Anything bundled into the React build, including VITE_ variables, is visible to every visitor.

Why cache weather data in MySQL?

Weather changes slowly, so a ten-minute cache makes repeat searches fast and keeps you within the weather provider's free request quota.

Do I still need Sequelize for this app?

No. One table and two queries are simple with mysql2 and parameterised queries. An ORM or migration tool helps once you have many tables.

Is Create React App still recommended?

No. Create React App is deprecated. Use Vite, or a framework, to start new React projects.

Can I run a Node.js and MySQL app on Domain India shared hosting?

Yes. cPanel and DirectAdmin have a Node.js application tool in the control panel, and you create the MySQL database in the same panel. Set your API key and database password as environment variables for the app.

Can I use MySQL on the App Platform?

The App Platform includes a PostgreSQL database. To use it, switch the app from mysql2 to the pg package and adjust the SQL.

Ready to put your app online? Start with cPanel hosting, look at the App Platform, or ask us in a support ticket which option fits.

Host your Node.js and MySQL app

Run Node.js apps from the control panel, with MySQL databases and free SSL.

See cPanel plans

Was this article helpful?

Your answer helps us decide what to improve next.

Still need help? Open a support ticket and our team will reply.

Prefer an app? Add this site to your home screen.Get the app