Building a Weather Platform That Doesn't Blow Its API Budget
Forecasts are a free commodity. The value is doing something proactive with them — without burning your paid-API budget. A case study of a full-stack TypeScript monorepo.

Why another weather app
Today, every major operating system provides users with free weather forecasts. Apple and Google have native widgets that are visually appealing, fast, and pre-installed on billions of devices. When a developer decides to build a new weather app, the first question they get from the community and investors isn't "where will you get the data," but "why would anyone download and open your app in the first place" and "how do you plan to run it without API fees ruining you in the very first month."
The weather app market is saturated with visually flawless products. Beautiful animated icons, smooth transitions, and detailed maps have become a commodity. However, users rarely want to spend time just looking at charts. The real value of a modern software service does not lie in passively showing the user that it is going to rain. Value is created when the app takes a proactive step—warning the user in time, adapting to their specific context, or automatically performing an action on their behalf. People do not pay to look at the weather; they pay to minimize risks, save time, or protect their property.
When designing Weather InTouch, I decided to build on two core pillars. The first is proactive data handling, where the app does not try to be just another passive dashboard, but an active assistant. The second pillar is strict operational cost optimization. The goal was to design an architecture that keeps paid API costs to an absolute minimum, ideally close to zero, even in the event of a sudden surge in the user base. This article describes how to build such a robust, scalable, and cost-effective application from scratch.
A three-app monorepo and a single source of truth for data
Keeping the frontend, backend, and promotional website in sync is a constant source of frustration in many projects. Changing a single data field on the backend often leads to frontend compilation errors or breaks the API contract. For this reason, Weather InTouch is designed as a TypeScript monorepo built on npm workspaces. This approach allows sharing code, type definitions, and configuration files across the entire ecosystem with minimal overhead.
The entire monorepo consists of three main applications and several shared packages:
- Frontend application: A client interface built on the Angular 22 platform. Angular was chosen for its robust architecture, strong support for dependency injection, and built-in state management tools, which simplify working with complex reactive forms and data streams.
- Backend API: A server-side application running on the NestJS 11 framework. NestJS provides a modular structure that perfectly complements Angular on the frontend and allows for easy integration with databases and external services.
- Static website: A custom TypeScript static-site generator (SSG) used to generate the marketing presentation and documentation. Using a custom, lightweight generator eliminates dependency on heavy CMS platforms and ensures maximum loading speed and optimal SEO without the need to run additional server infrastructure.
A single source of truth for data structures
At the heart of the monorepo is a shared DTO (Data Transfer Objects) package. This package defines the exact shapes of all data flowing between the backend and frontend. Both backend and frontend import identical TypeScript types and interfaces. If the structure of the weather forecast changes, the compiler immediately flags all locations in the client application where the code needs to be updated.
// packages/shared-dtos/src/weather.dto.ts
export interface CoordinateDto {
latitude: number;
longitude: number;
}
export interface CurrentWeatherDto {
temperature: number;
windSpeed: number;
relativeHumidity: number;
weatherCode: number;
updatedAt: string;
}
export interface WeatherForecastResponseDto {
coordinates: CoordinateDto;
current: CurrentWeatherDto;
timezone: string;
}
This approach eliminates any manual rewriting of data structures or relying on outdated API documentation.
Architectural separation of concerns
From a security and performance perspective, where data is processed is strictly defined. The backend fully owns all access to paid APIs, handles user authorization, subscription management, and communication with the payment gateway. The frontend is designed to be as stateless as possible.
Individual components and visual cards on the frontend do not receive raw data from external APIs. Instead, they consume cleaned, transformed, and localized data ready for immediate rendering. This approach significantly simplifies client application testing, as components can easily be tested using mock data that matches the shared DTO interfaces.
Free backbone, paid API only for refinement
A fundamental mistake when designing weather applications is immediately connecting all data streams to expensive commercial APIs. Providers like OpenWeather or Apple WeatherKit charge fees per thousand requests. If your application queries the server every time a widget is opened or a map is panned, infrastructure bills can quickly exceed generated revenue.
The Weather InTouch architecture addresses this issue with a hybrid data acquisition model. The free Open-Meteo API serves as the primary data backbone. It provides highly accurate data for current conditions, hourly forecasts, and daily forecasts up to 7 days ahead. Open-Meteo offers very favorable terms for both non-commercial and commercial use, and its data models are perfectly sufficient for most common scenarios.
The paid API (OpenWeather in this case) is used only as an overlay layer to refine specific real-time values where free models might exhibit slight delays. However, this paid layer is implemented on the backend behind a configurable feature flag.
Graceful degradation
If the paid API exhausts its daily limit, encounters a network error, or is intentionally disabled to save costs, the system automatically switches to the fallback data source without user intervention. Although the user might lose a minor nuance in the accuracy of the current temperature, the application remains fully functional, providing a complete forecast from the primary free source.
The following example illustrates how the NestJS service handles this fallback behavior when the paid provider fails:
// apps/api/src/weather/weather.service.ts
import { Injectable, Logger } from '@nestjs/common';
import { OpenMeteoService } from './open-meteo.service';
import { OpenWeatherService } from './open-weather.service';
import { WeatherForecastResponseDto } from '@weather-intouch/dtos';
@Injectable()
export class WeatherService {
private readonly logger = new Logger(WeatherService.name);
constructor(
private readonly openMeteo: OpenMeteoService,
private readonly openWeather: OpenWeatherService,
) {}
async getWeatherForecast(lat: number, lon: number): Promise<WeatherForecastResponseDto> {
const baseForecast = await this.openMeteo.fetchForecast(lat, lon);
try {
// Attempt to enrich data from the paid API if active
const refinedCurrent = await this.openWeather.fetchCurrentOverlay(lat, lon);
return {
...baseForecast,
current: {
...baseForecast.current,
temperature: refinedCurrent.temperature ?? baseForecast.current.temperature,
relativeHumidity: refinedCurrent.humidity ?? baseForecast.current.relativeHumidity,
updatedAt: new Date().toISOString(),
},
};
} catch (error) {
this.logger.warn(`Paid API failed or is unavailable. Using fallback data from Open-Meteo. Error: ${error.message}`);
return baseForecast;
}
}
}
This ensures that an outage of a single provider or a sudden API key restriction does not cause the entire application to become unavailable.
Cost-control engine as the backbone of the product
The real magic of cost savings does not happen at the provider selection level, but in how we handle data within our infrastructure. If a thousand users in the same city open the app at the same moment, there is no reason to send a thousand requests to external weather servers. Weather does not change every second, and values do not differ over a distance of a few meters.
To reduce load and costs, a robust caching engine was developed, built on two key concepts: rounding coordinates to a spatial grid and the Single-Flight pattern. A more detailed look at this topic is available in the full project case study.
Spatial grid and cache sharing
When a mobile device transmits GPS coordinates, it sends them with an accuracy of six to eight decimal places. This represents millimeter to centimeter precision. For weather forecasting purposes, however, such precision is counterproductive because it generates an infinite number of unique cache keys.
Weather InTouch therefore implements coordinate rounding to four decimal places. In the latitude of Central Europe, this corresponds to a grid of approximately 11 × 11 meters.
- The coordinates
50.087234, 14.421123are rounded to50.0872, 14.4211. - A neighboring user standing ten meters away rounds to the exact same values.
Thanks to this rounding, all users in a given microsegment share an identical cache entry. The cache is designed as a two-tier system:
- In-memory cache: Fast memory directly within the running NestJS instance for immediate response.
- Redis cache: Distributed shared memory for synchronization across multiple backend instances.
As a result, the paid API is contacted at most once per location within a defined time interval (which is in the range of tens of minutes).
Cache stampede prevention using Single-Flight
Under high traffic, a situation known as a cache stampede or dogpiling can occur. The moment a cache entry expires and dozens of concurrent requests for the same location hit the server, all of these requests find the cache empty and simultaneously attempt to query the external API. This leads to immediate rate-limit exhaustion and unnecessary costs.
The Single-Flight pattern (implemented in Node.js using a map of active Promises) ensures that only one active outgoing request to the external API can run for any unique location at any given time.
// apps/api/src/weather/single-flight.manager.ts
import { Injectable } from '@nestjs/common';
@Injectable()
export class SingleFlightManager {
private activeRequests = new Map<string, Promise<unknown>>();
async execute<T>(key: string, fetchFn: () => Promise<T>): Promise<T> {
const existingPromise = this.activeRequests.get(key);
if (existingPromise) {
// If a request for the same key is already running, return the existing Promise
return existingPromise as Promise<T>;
}
// Create a new request and store it in the map
const promise = fetchFn().finally(() => {
this.activeRequests.delete(key);
});
this.activeRequests.set(key, promise);
return promise;
}
}
Thus, if 150 concurrent requests for a Prague 1 forecast hit the server, only one physical query is made to the external API. The remaining 149 requests wait for this single query to complete and then receive the identical result.
Current capabilities
In its current version, Weather InTouch is a functional MVP under active development that already offers a comprehensive set of features. The application was designed from the ground up with modularity in mind, allowing for easy addition of new data layers.
Key integrated features include:
- Current conditions and multi-day forecast: Standard overview of temperature, humidity, pressure, and wind speed, complemented by a 7-day outlook.
- Hourly multi-chart: An interactive visualization that allows users to track temperature, precipitation, and wind trends hour-by-hour on a single timeline.
- Precipitation radar: A map layer showing the movement of precipitation areas with the ability to play back recent frames.
- Air quality, pollen forecast, and UV index: Critical data for allergy sufferers and outdoor enthusiasts, integrating Air Quality Index (AQI) and concentrations of major pollen allergens.
- Official weather alerts: The system processes and displays official weather alerts in the CAP (Common Alerting Protocol) format issued by national hydrometeorological institutes.
- Location management: Users can save their favorite locations for quick access.
- Localization and units: Full support for 20 world languages and automatic switching between metric and imperial unit systems.
Security and user management
From the perspective of the backend architecture (NestJS + MongoDB + Redis), a strong emphasis is placed on user security and privacy.
- Authentication: Sign-in is implemented via secure authentication using Google and Apple accounts (token-on-backend validation). The server receives the authorization token from the device, validates it against the authority, and issues its own secure, short-lived JWT token.
- Billing: Subscription and payment management is handled via an integrated Stripe platform. The system distinguishes three access tiers: Free, Plus, and Pro.
- GDPR compliance: Every user has the option in their profile settings to export all their stored data (search history, saved locations, settings) with a single click, or request immediate and complete account deletion from the MongoDB database without having to contact support.
Roadmap
The current version of Weather InTouch represents a stable foundation to build upon. Since the project's goal is to move from passive data display to proactive assistance, planned features focus on automation and integration into the broader technological ecosystem.
Watchdog with user-defined thresholds
This feature will allow users to define their own rules for weather monitoring. Instead of a generic rain warning, users will be able to set specific conditions, such as: “Send me an alert if the wind speed in my location exceeds 15 m/s” or “Alert me a day in advance if the temperature drops below freezing so I can winterize my plants.”
Automation and webhooks
Connecting weather forecasts with Smart Home setups or enterprise systems. Using outgoing webhooks, it will be possible to automatically trigger actions based on meteorological conditions. If the system detects approaching heavy rain, it will send a webhook to the home automation system, which can then close skylights or postpone scheduled lawn irrigation.
Embeddable widget for third-party websites
This will allow website operators (such as guesthouses, sports facilities, or local news portals) to easily embed a customized weather forecast widget directly into their content. The widget will be fully responsive, styleable using CSS variables, and connected to our optimized caching engine, ensuring minimal load on the host website.
Multi-tenant layer and mobile applications
A multi-tenant interface is in development for enterprise customers, allowing fleet management or weather tracking across hundreds of branches simultaneously under a single administrator account. In parallel, there are plans to develop a native mobile application that will fully leverage background push notifications and home screen widgets to deliver critical alerts instantly.
If you are interested in the technical implementation details, database architecture, or how radar imagery is integrated, visit the full project case study for a detailed breakdown of the entire development cycle and infrastructure setup.

