Skip to main content
RapidDev - Software Development Agency
how-to-build-replit30-60 minutes

How to Build a Weather Application with Replit

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.

What you'll build

  • Current weather display with temperature, conditions, humidity, wind speed, and 'feels like'
  • 5-day forecast with daily high/low temperatures and condition icons
  • City search with autocomplete using OpenWeatherMap's geocoding API
  • 30-minute PostgreSQL cache to stay within the free OpenWeatherMap 1,000 calls/day limit
  • Saved locations sidebar with pinned cities and current temperatures
  • Fahrenheit/Celsius toggle that converts values client-side
  • Condition-based background gradients (sunny, cloudy, rainy, night)
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Beginner13 min read30-60 minutesReplit FreeApril 2026RapidDev Engineering Team
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.

What you're building

A weather app is the classic beginner project because it teaches three fundamental skills in one go: calling an external API, caching responses to respect rate limits, and displaying structured data with dynamic UI. These are exactly the patterns you'll use in almost every other app you build.

Replit Agent scaffolds the entire Express + PostgreSQL backend and React frontend from a single prompt. Add your OpenWeatherMap API key to Replit Secrets (it's free to get), and Agent wires it into the server with `process.env.OPENWEATHERMAP_API_KEY` — never hardcoded. The caching layer stores API responses in PostgreSQL for 30 minutes, keeping you well within the free tier's 1,000 calls per day even with dozens of users.

The architecture is intentionally simple: Express routes call OpenWeatherMap's API or serve from cache, Drizzle ORM manages three small tables (saved_locations, weather_cache, search_history), and React renders the weather data with condition-appropriate styling. There's no auth required — Replit Auth is optional for saving locations.

Final result

A weather app with city search, current conditions, 5-day forecast, saved locations, 30-minute caching, and dynamic condition-based backgrounds — all deployed on Replit Autoscale.

Tech stack

ReplitIDE & Hosting
ExpressBackend Framework
PostgreSQLDatabase
Drizzle ORMDatabase ORM
OpenWeatherMap APIWeather Data
ReactFrontend

Prerequisites

  • 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

Build steps

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.

prompt.txt
1// Steps to add your API key:
2// 1. Go to openweathermap.org/api and sign up for the free tier
3// 2. Copy your API key from the API keys tab in your account
4// 3. In Replit, click the lock icon 🔒 in the left sidebar
5// 4. Click 'Add new secret'
6// 5. Key name: OPENWEATHERMAP_API_KEY
7// 6. Value: paste your API key
8// 7. Click 'Add secret'
9
10// Your app accesses it as:
11const apiKey = process.env.OPENWEATHERMAP_API_KEY;
12// 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.

prompt.txt
1// Paste this into Replit Agent:
2// Build a weather application with Express and PostgreSQL using Drizzle ORM.
3// Schema:
4// saved_locations (id serial PK, user_id text, name text, latitude numeric, longitude numeric,
5// is_default bool default false, created_at),
6// weather_cache (id serial PK, location_key text unique,
7// data jsonb, fetched_at timestamp default now()),
8// search_history (id serial, user_id text, query text, latitude numeric,
9// longitude numeric, searched_at timestamp default now()).
10// Routes:
11// GET /api/weather/current?lat=&lng= (check cache first — if fetched_at < 30 min, return cached;
12// otherwise fetch from OpenWeatherMap API using process.env.OPENWEATHERMAP_API_KEY,
13// cache result, return data),
14// GET /api/weather/forecast?lat=&lng= (5-day forecast, same caching pattern with 3hr cache key),
15// GET /api/weather/search?q= (geocode city name to coordinates using OpenWeatherMap geocoding API),
16// POST /api/locations/save (save favorite location, user_id from Replit Auth),
17// GET /api/locations (user's saved locations with cached current weather),
18// DELETE /api/locations/:id,
19// GET /api/weather/alerts?lat=&lng= (severe weather alerts if available).
20// Cache key format: 'current:lat:{lat_1dp},lng:{lng_1dp}' (round to 1 decimal place).
21// React frontend: search bar at top for city name,
22// current weather card with temperature (Fahrenheit/Celsius toggle), condition icon,
23// humidity, wind speed, feels-like; 5-day forecast row with daily cards;
24// saved locations sidebar; condition-based background gradients
25// (sunny=warm yellow, cloudy=gray, rainy=blue-gray, night=dark blue).
26// 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
1// server/routes/weather.js
2const express = require('express');
3const fetch = require('node-fetch'); // or use built-in fetch in Node 18+
4const { db } = require('../db');
5const { weatherCache } = require('../schema');
6const { eq } = require('drizzle-orm');
7
8const router = express.Router();
9const CACHE_TTL_MINUTES = 30;
10
11router.get('/api/weather/current', async (req, res) => {
12 const { lat, lng } = req.query;
13 if (!lat || !lng) return res.status(400).json({ error: 'lat and lng required' });
14
15 // Round to 1 decimal place for cache key grouping
16 const roundedLat = Math.round(parseFloat(lat) * 10) / 10;
17 const roundedLng = Math.round(parseFloat(lng) * 10) / 10;
18 const cacheKey = `current:${roundedLat},${roundedLng}`;
19
20 // Check cache
21 const [cached] = await db.select().from(weatherCache)
22 .where(eq(weatherCache.locationKey, cacheKey))
23 .limit(1);
24
25 const cacheAge = cached
26 ? (Date.now() - new Date(cached.fetchedAt).getTime()) / 1000 / 60
27 : Infinity;
28
29 if (cached && cacheAge < CACHE_TTL_MINUTES) {
30 return res.json({ ...cached.data, cached: true });
31 }
32
33 // Fetch from OpenWeatherMap
34 const apiKey = process.env.OPENWEATHERMAP_API_KEY;
35 const url = `https://api.openweathermap.org/data/2.5/weather?lat=${lat}&lon=${lng}&appid=${apiKey}&units=metric`;
36
37 const response = await fetch(url);
38 if (!response.ok) {
39 const errText = await response.text();
40 console.error('[weather] API error:', errText);
41 // Serve stale cache if available rather than erroring
42 if (cached) return res.json({ ...cached.data, stale: true });
43 return res.status(502).json({ error: 'Weather API unavailable' });
44 }
45
46 const data = await response.json();
47
48 // Upsert cache
49 await db.insert(weatherCache)
50 .values({ locationKey: cacheKey, data, fetchedAt: new Date() })
51 .onConflictDoUpdate({
52 target: weatherCache.locationKey,
53 set: { data, fetchedAt: new Date() },
54 });
55
56 res.json(data);
57});
58
59module.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
1// server/lib/retryDb.js
2async function withDbRetry(fn, { retries = 2, delayMs = 300 } = {}) {
3 let lastErr;
4 for (let attempt = 0; attempt <= retries; attempt++) {
5 try {
6 return await fn();
7 } catch (err) {
8 lastErr = err;
9 const msg = String(err?.message || '');
10 const isTransient =
11 msg.includes('terminating connection') ||
12 msg.includes('Connection terminated') ||
13 msg.includes('not queryable') ||
14 msg.includes('fetch failed') ||
15 err?.code === 'ECONNRESET' ||
16 err?.code === '57P01';
17
18 if (!isTransient || attempt === retries) throw err;
19 await new Promise(r => setTimeout(r, delayMs * (attempt + 1)));
20 }
21 }
22 throw lastErr;
23}
24
25module.exports = { withDbRetry };
26
27// Usage in weather route:
28// const { withDbRetry } = require('../lib/retryDb');
29// const [cached] = await withDbRetry(() =>
30// db.select().from(weatherCache).where(eq(weatherCache.locationKey, cacheKey)).limit(1)
31// );

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
1// server/index.js — verify deployment binding
2const express = require('express');
3const path = require('path');
4const app = express();
5
6app.use(express.json());
7
8// API routes
9app.use(require('./routes/weather'));
10app.use(require('./routes/locations'));
11
12// Serve React build
13app.use(express.static(path.join(__dirname, '../public')));
14app.get('*', (req, res) => {
15 res.sendFile(path.join(__dirname, '../public/index.html'));
16});
17
18// MUST bind to 0.0.0.0 for Replit deployment
19const PORT = process.env.PORT || 3000;
20app.listen(PORT, '0.0.0.0', () => {
21 console.log(`Weather app running on port ${PORT}`);
22});

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

server/routes/weather.js
1const express = require('express');
2const { db } = require('../db');
3const { weatherCache, searchHistory } = require('../schema');
4const { eq } = require('drizzle-orm');
5
6const router = express.Router();
7const CACHE_TTL_MINUTES = 30;
8const FORECAST_CACHE_TTL_MINUTES = 180;
9
10const OWM_BASE = 'https://api.openweathermap.org';
11
12async function fetchWeather(path) {
13 const apiKey = process.env.OPENWEATHERMAP_API_KEY;
14 const url = `${OWM_BASE}${path}&appid=${apiKey}&units=metric`;
15 const res = await fetch(url);
16 if (!res.ok) throw new Error(`OWM error ${res.status}: ${await res.text()}`);
17 return res.json();
18}
19
20async function getCached(key, ttl) {
21 const [row] = await db.select().from(weatherCache)
22 .where(eq(weatherCache.locationKey, key)).limit(1);
23 if (!row) return null;
24 const ageMin = (Date.now() - new Date(row.fetchedAt).getTime()) / 60000;
25 return ageMin < ttl ? row.data : null;
26}
27
28async function setCached(key, data) {
29 await db.insert(weatherCache)
30 .values({ locationKey: key, data, fetchedAt: new Date() })
31 .onConflictDoUpdate({
32 target: weatherCache.locationKey,
33 set: { data, fetchedAt: new Date() },
34 });
35}
36
37// GET /api/weather/current?lat=&lng=
38router.get('/api/weather/current', async (req, res) => {
39 const { lat, lng } = req.query;
40 if (!lat || !lng) return res.status(400).json({ error: 'lat and lng required' });
41
42 const rLat = Math.round(parseFloat(lat) * 10) / 10;
43 const rLng = Math.round(parseFloat(lng) * 10) / 10;
44 const key = `cur:${rLat},${rLng}`;
45
46 const cached = await getCached(key, CACHE_TTL_MINUTES);
47 if (cached) return res.json({ ...cached, _cached: true });
48
49 try {
50 const data = await fetchWeather(`/data/2.5/weather?lat=${lat}&lon=${lng}`);
51 await setCached(key, data);
52 res.json(data);
53 } catch (err) {
54 console.error('[weather/current]', err.message);
55 res.status(502).json({ error: 'Weather service unavailable' });
56 }
57});
58
59// GET /api/weather/forecast?lat=&lng=
60router.get('/api/weather/forecast', async (req, res) => {
61module.exports = router;

Customization ideas

Severe weather alerts banner

OpenWeatherMap's free tier includes a basic alerts API for some regions. Call the alerts endpoint when loading the current weather page. If alerts are returned, display a red banner at the top of the page with the alert title and description.

Hourly forecast with chart

The /data/2.5/forecast endpoint returns 40 three-hourly forecasts (5 days). Parse the first 24 hours and render a line chart of temperature over time using a simple SVG or the recharts library. Show it below the current conditions card.

Weather-based activity suggestions

After fetching current conditions, add a simple rule engine: if temp > 25C and clear sky → 'Great day for a picnic', if rain → 'Bring an umbrella', if wind > 50km/h → 'Stay indoors'. Display as a suggestion chip below the current conditions.

Daily weather summary push notification

Add a Scheduled Deployment that runs every morning at 7am. For each user with saved locations, fetch their default location's weather and send a brief email via SendGrid. Store the SendGrid API key in Replit Secrets.

Common pitfalls

Pitfall: All weather requests return 401 Unauthorized

How to avoid: 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.

Pitfall: API quota exhausted — all requests return 429

How to avoid: 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.

Pitfall: OPENWEATHERMAP_API_KEY returns undefined in the deployed app

How to avoid: 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

AI prompts to try

Copy these prompts to build this project faster.

ChatGPT Prompt

I'm building a weather app with Express.js and PostgreSQL on Replit using the OpenWeatherMap API. Help me design an efficient caching strategy. I want to minimize API calls (free tier is 1,000/day) while keeping data reasonably fresh. My weather_cache table has: location_key text unique, data jsonb, fetched_at timestamp. Write the Express middleware that: (1) rounds coordinates to 1 decimal place to generate the cache key, (2) checks if a cache entry exists and is under 30 minutes old, (3) fetches from OpenWeatherMap if no valid cache exists, (4) upserts the result into the cache, and (5) returns stale cache data if the API call fails.

Build Prompt

Add a Fahrenheit/Celsius toggle to my weather React app. OpenWeatherMap returns temperatures in Celsius when using units=metric. Store the user's unit preference in localStorage. When the user clicks the toggle, switch between displaying values in Celsius (raw from API) and Fahrenheit (formula: F = C * 9/5 + 32). Apply the conversion to current temperature, feels-like, and all daily forecast high/low values. Show 'C' or 'F' next to each temperature value. Persist the preference in localStorage so it survives page refreshes.

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.

RapidDev

Talk to an Expert

Our team has built 600+ apps. Get personalized help with your project.

Book a free consultation

Need help building your app?

Our experts have built 600+ apps and can accelerate your development. Book a free consultation — no strings attached.

Book a free consultation

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We'll discuss your project and provide a custom quote at no cost.