# How to Build a Weather Application with Replit

- Tool: How to Build with Replit
- Difficulty: Beginner
- Compatibility: Replit Free
- Last updated: April 2026

## TL;DR

Build a weather app with Replit in 30-60 minutes. You'll create an Express API that fetches from OpenWeatherMap, caches results in PostgreSQL (Drizzle ORM) to stay within the free API quota, and serves a React frontend with current conditions, a 5-day forecast, and saved locations. Replit Agent scaffolds the full app from one prompt. Deploy on Autoscale.

## Before you start

- A Replit account (Free tier is sufficient)
- A free OpenWeatherMap API key — sign up at openweathermap.org/api (the free tier allows 1,000 calls/day)
- No coding experience needed — Replit Agent generates the full app

## Step-by-step guide

### 1. Get your OpenWeatherMap API key and add it to Replit Secrets

Before generating the app, you need an API key from OpenWeatherMap. Sign up at openweathermap.org/api, copy the key from your account dashboard, and add it to Replit Secrets. New keys take up to 2 hours to activate on the free tier.

```
// Steps to add your API key:
// 1. Go to openweathermap.org/api and sign up for the free tier
// 2. Copy your API key from the API keys tab in your account
// 3. In Replit, click the lock icon 🔒 in the left sidebar
// 4. Click 'Add new secret'
// 5. Key name: OPENWEATHERMAP_API_KEY
// 6. Value: paste your API key
// 7. Click 'Add secret'

// Your app accesses it as:
const apiKey = process.env.OPENWEATHERMAP_API_KEY;
// NEVER put the actual key value in your code — always use process.env
```

> Pro tip: OpenWeatherMap API keys take up to 2 hours to activate. If you get 401 errors in the first hour, just wait — the key needs to propagate through their system.

**Expected result:** OPENWEATHERMAP_API_KEY is visible in the Replit Secrets panel. You can verify it by typing the key name (not the value) to find it.

### 2. Scaffold the weather app with Replit Agent

Use the Agent prompt below to generate the full app. Agent will create the Express server, Drizzle schema for caching and saved locations, all the weather API routes, and a React frontend with weather cards and a search bar.

```
// Paste this into Replit Agent:
// Build a weather application with Express and PostgreSQL using Drizzle ORM.
// Schema:
// saved_locations (id serial PK, user_id text, name text, latitude numeric, longitude numeric,
// is_default bool default false, created_at),
// weather_cache (id serial PK, location_key text unique,
// data jsonb, fetched_at timestamp default now()),
// search_history (id serial, user_id text, query text, latitude numeric,
// longitude numeric, searched_at timestamp default now()).
// Routes:
// GET /api/weather/current?lat=&lng= (check cache first — if fetched_at < 30 min, return cached;
// otherwise fetch from OpenWeatherMap API using process.env.OPENWEATHERMAP_API_KEY,
// cache result, return data),
// GET /api/weather/forecast?lat=&lng= (5-day forecast, same caching pattern with 3hr cache key),
// GET /api/weather/search?q= (geocode city name to coordinates using OpenWeatherMap geocoding API),
// POST /api/locations/save (save favorite location, user_id from Replit Auth),
// GET /api/locations (user's saved locations with cached current weather),
// DELETE /api/locations/:id,
// GET /api/weather/alerts?lat=&lng= (severe weather alerts if available).
// Cache key format: 'current:lat:{lat_1dp},lng:{lng_1dp}' (round to 1 decimal place).
// React frontend: search bar at top for city name,
// current weather card with temperature (Fahrenheit/Celsius toggle), condition icon,
// humidity, wind speed, feels-like; 5-day forecast row with daily cards;
// saved locations sidebar; condition-based background gradients
// (sunny=warm yellow, cloudy=gray, rainy=blue-gray, night=dark blue).
// Use process.env.OPENWEATHERMAP_API_KEY. Bind server to 0.0.0.0.
```

> Pro tip: If Agent asks which weather API to use, specify 'OpenWeatherMap API v2.5' — it's the free tier endpoint. The v3.0 OneCall API requires a paid subscription.

**Expected result:** Agent creates the full app. The preview shows a weather UI. The first search will fail if your API key isn't activated yet — check back in an hour.

### 3. Review and verify the caching implementation

The caching layer is what keeps your app within the free OpenWeatherMap quota. Review the weather route to make sure it checks the cache before calling the API. If Agent generated the route without caching, add it using the code below.

```
// server/routes/weather.js
const express = require('express');
const fetch = require('node-fetch');  // or use built-in fetch in Node 18+
const { db } = require('../db');
const { weatherCache } = require('../schema');
const { eq } = require('drizzle-orm');

const router = express.Router();
const CACHE_TTL_MINUTES = 30;

router.get('/api/weather/current', async (req, res) => {
  const { lat, lng } = req.query;
  if (!lat || !lng) return res.status(400).json({ error: 'lat and lng required' });

  // Round to 1 decimal place for cache key grouping
  const roundedLat = Math.round(parseFloat(lat) * 10) / 10;
  const roundedLng = Math.round(parseFloat(lng) * 10) / 10;
  const cacheKey = `current:${roundedLat},${roundedLng}`;

  // Check cache
  const [cached] = await db.select().from(weatherCache)
    .where(eq(weatherCache.locationKey, cacheKey))
    .limit(1);

  const cacheAge = cached
    ? (Date.now() - new Date(cached.fetchedAt).getTime()) / 1000 / 60
    : Infinity;

  if (cached && cacheAge < CACHE_TTL_MINUTES) {
    return res.json({ ...cached.data, cached: true });
  }

  // Fetch from OpenWeatherMap
  const apiKey = process.env.OPENWEATHERMAP_API_KEY;
  const url = `https://api.openweathermap.org/data/2.5/weather?lat=${lat}&lon=${lng}&appid=${apiKey}&units=metric`;

  const response = await fetch(url);
  if (!response.ok) {
    const errText = await response.text();
    console.error('[weather] API error:', errText);
    // Serve stale cache if available rather than erroring
    if (cached) return res.json({ ...cached.data, stale: true });
    return res.status(502).json({ error: 'Weather API unavailable' });
  }

  const data = await response.json();

  // Upsert cache
  await db.insert(weatherCache)
    .values({ locationKey: cacheKey, data, fetchedAt: new Date() })
    .onConflictDoUpdate({
      target: weatherCache.locationKey,
      set: { data, fetchedAt: new Date() },
    });

  res.json(data);
});

module.exports = router;
```

> Pro tip: The stale cache fallback on line where API errors return cached data means your app stays functional even when OpenWeatherMap has an outage — users just see slightly older data.

### 4. Add the DB retry wrapper for reliable cold starts

Weather apps often go idle for hours (no one checks the weather at 3am). When the first visitor arrives, PostgreSQL needs to wake up from its 5-minute sleep. The retry wrapper handles this transparently so users never see a database error on the first request.

```
// server/lib/retryDb.js
async function withDbRetry(fn, { retries = 2, delayMs = 300 } = {}) {
  let lastErr;
  for (let attempt = 0; attempt <= retries; attempt++) {
    try {
      return await fn();
    } catch (err) {
      lastErr = err;
      const msg = String(err?.message || '');
      const isTransient =
        msg.includes('terminating connection') ||
        msg.includes('Connection terminated') ||
        msg.includes('not queryable') ||
        msg.includes('fetch failed') ||
        err?.code === 'ECONNRESET' ||
        err?.code === '57P01';

      if (!isTransient || attempt === retries) throw err;
      await new Promise(r => setTimeout(r, delayMs * (attempt + 1)));
    }
  }
  throw lastErr;
}

module.exports = { withDbRetry };

// Usage in weather route:
// const { withDbRetry } = require('../lib/retryDb');
// const [cached] = await withDbRetry(() =>
//   db.select().from(weatherCache).where(eq(weatherCache.locationKey, cacheKey)).limit(1)
// );
```

**Expected result:** After 5+ minutes of inactivity, the first weather request still returns data correctly — no 'connection terminated' errors visible to the user.

### 5. Deploy on Autoscale and test with real city searches

Weather apps are a perfect fit for Autoscale — brief usage sessions scattered throughout the day, with no traffic overnight. Deploy from the Publish pane, then test the full flow: search for a city, verify the forecast loads, save a location, and confirm the cache works (second request for the same city should return `cached: true`).

```
// server/index.js — verify deployment binding
const express = require('express');
const path = require('path');
const app = express();

app.use(express.json());

// API routes
app.use(require('./routes/weather'));
app.use(require('./routes/locations'));

// Serve React build
app.use(express.static(path.join(__dirname, '../public')));
app.get('*', (req, res) => {
  res.sendFile(path.join(__dirname, '../public/index.html'));
});

// MUST bind to 0.0.0.0 for Replit deployment
const PORT = process.env.PORT || 3000;
app.listen(PORT, '0.0.0.0', () => {
  console.log(`Weather app running on port ${PORT}`);
});
```

> Pro tip: Add OPENWEATHERMAP_API_KEY to Deployment Secrets in the Publish pane — Workspace Secrets are not available in deployed apps. Without this, all weather requests will fail with 401.

**Expected result:** The app is live at your *.replit.app URL. Searching 'London' returns current conditions. The same search within 30 minutes returns `cached: true` in the response.

## Complete code example

File: `server/routes/weather.js`

```javascript
const express = require('express');
const { db } = require('../db');
const { weatherCache, searchHistory } = require('../schema');
const { eq } = require('drizzle-orm');

const router = express.Router();
const CACHE_TTL_MINUTES = 30;
const FORECAST_CACHE_TTL_MINUTES = 180;

const OWM_BASE = 'https://api.openweathermap.org';

async function fetchWeather(path) {
  const apiKey = process.env.OPENWEATHERMAP_API_KEY;
  const url = `${OWM_BASE}${path}&appid=${apiKey}&units=metric`;
  const res = await fetch(url);
  if (!res.ok) throw new Error(`OWM error ${res.status}: ${await res.text()}`);
  return res.json();
}

async function getCached(key, ttl) {
  const [row] = await db.select().from(weatherCache)
    .where(eq(weatherCache.locationKey, key)).limit(1);
  if (!row) return null;
  const ageMin = (Date.now() - new Date(row.fetchedAt).getTime()) / 60000;
  return ageMin < ttl ? row.data : null;
}

async function setCached(key, data) {
  await db.insert(weatherCache)
    .values({ locationKey: key, data, fetchedAt: new Date() })
    .onConflictDoUpdate({
      target: weatherCache.locationKey,
      set: { data, fetchedAt: new Date() },
    });
}

// GET /api/weather/current?lat=&lng=
router.get('/api/weather/current', async (req, res) => {
  const { lat, lng } = req.query;
  if (!lat || !lng) return res.status(400).json({ error: 'lat and lng required' });

  const rLat = Math.round(parseFloat(lat) * 10) / 10;
  const rLng = Math.round(parseFloat(lng) * 10) / 10;
  const key = `cur:${rLat},${rLng}`;

  const cached = await getCached(key, CACHE_TTL_MINUTES);
  if (cached) return res.json({ ...cached, _cached: true });

  try {
    const data = await fetchWeather(`/data/2.5/weather?lat=${lat}&lon=${lng}`);
    await setCached(key, data);
    res.json(data);
  } catch (err) {
    console.error('[weather/current]', err.message);
    res.status(502).json({ error: 'Weather service unavailable' });
  }
});

// GET /api/weather/forecast?lat=&lng=
router.get('/api/weather/forecast', async (req, res) => {
module.exports = router;
```

## Common mistakes

- **All weather requests return 401 Unauthorized** — OpenWeatherMap API keys take up to 2 hours to activate after signup. The key exists in your account but isn't enabled yet. Fix: Wait 1-2 hours after getting your key. In the meantime, you can test with the API's example response data hardcoded in the route, or use a key from a different account that's already activated.
- **API quota exhausted — all requests return 429** — The caching layer wasn't implemented correctly, or the cache key is too specific (per-exact-coordinate instead of per-rounded-coordinate), so identical searches create separate cache entries. Fix: Round coordinates to 1 decimal place for the cache key. With 1-decimal-place precision, all requests within about 10km of each other share the same cache entry.
- **OPENWEATHERMAP_API_KEY returns undefined in the deployed app** — Workspace Secrets (set in the Replit sidebar) are not automatically available in deployed apps. Deployment Secrets are configured separately in the Publish pane. Fix: Open the Publish pane, find the Secrets section, and add OPENWEATHERMAP_API_KEY with your API key value. Redeploy after adding it.

## Best practices

- Store OPENWEATHERMAP_API_KEY in Replit Secrets (lock icon) — add it in Workspace Secrets for development AND in Deployment Secrets for production separately
- Round coordinates to 1 decimal place for cache keys — this groups nearby searches together and dramatically reduces API calls
- Cache current conditions for 30 minutes and forecasts for 3 hours — weather doesn't change faster than that, and this keeps you well within the free tier's 1,000 calls/day
- Add the DB retry wrapper from server/lib/retryDb.js — weather apps go idle for hours, and the first request after sleep needs to reconnect to PostgreSQL
- Return stale cache data on API errors rather than failing — slightly outdated weather is better than an error page
- Use Drizzle Studio (open from the Database tool) to inspect the weather_cache table and verify caching is working correctly
- Test with multiple city searches to verify the cache is being used on repeated requests before deploying

## Frequently asked questions

### Is the OpenWeatherMap free tier enough for a real app?

Yes, for a personal app or small team. The free tier allows 1,000 API calls per day and 60 calls per minute. With 30-minute caching and coordinates rounded to 1 decimal place, a single city only needs one API call per 30 minutes — so 1,000 calls supports about 500 unique city lookups per day.

### Why is my API key returning 401 errors?

OpenWeatherMap API keys take up to 2 hours to activate after you sign up. The key exists in your account but isn't enabled in their system yet. Wait an hour and try again. If it still fails, verify the key was correctly copied into Replit Secrets with no extra spaces.

### Do I need Replit Auth for this app?

No. Current weather and forecasts work without authentication. Replit Auth is only needed if you want users to save their own list of favorite locations across sessions. For a simple weather lookup tool, skip auth entirely.

### Should I use Autoscale or Reserved VM?

Autoscale is perfect for a weather app. Usage is brief and scattered throughout the day — someone checks the weather, then leaves. Autoscale scales to zero when no one's using it, reducing costs to near zero. The only consideration is the PostgreSQL sleep issue, which the retry wrapper handles.

### How do I show weather icons?

OpenWeatherMap provides an icon code in the API response (e.g., '01d' for clear sky daytime). Load the icon from their CDN: `https://openweathermap.org/img/wn/01d@2x.png`. Substitute the icon code dynamically. Alternatively, use emoji weather symbols (use a mapping from OWM condition codes to emojis) for a no-dependency solution.

### Can I use a different weather API like Open-Meteo?

Yes. Open-Meteo is completely free with no API key required, just different endpoint parameters. It's a good alternative if you hit OpenWeatherMap's rate limits. The caching and Express route structure in this guide works identically — just change the fetch URL and parse the different response format.

### How do I auto-detect the user's location?

Use the browser's Geolocation API: `navigator.geolocation.getCurrentPosition(position => { ... })`. This gives you latitude and longitude to pass directly to the weather API. The browser prompts the user for permission — show a 'Use my location' button rather than requesting location automatically on page load.

### Can RapidDev help me build a custom weather-based app?

Yes. RapidDev has built 600+ apps and can help you build weather-integrated products like agricultural tools, outdoor event platforms, or travel recommendation engines. Book a free consultation at rapidevelopers.com.

---

Source: https://www.rapidevelopers.com/how-to-build-replit/weather-application
© RapidDev — https://www.rapidevelopers.com/how-to-build-replit/weather-application
