Back to projects

Project Case Study

Weather Normalizer.

A TypeScript and Express service that isolates external weather providers behind a stable internal contract.

TypeScriptNode.jsExpressVitest
Architecture diagram for the Weather Normalizer service

The Problem.

Third-party APIs have a habit of leaking into the rest of an application. Once controllers, services, tests, and business logic all know the provider's response shape, changing providers stops being a small integration change and becomes a rewrite.

I built this service around one rule: the rest of the application should never care what shape a weather provider returns.

How It Works.

Requests enter through an Express controller that handles HTTP validation and optional field projection. The service layer owns the business rules and chooses a provider through a small factory. Providers handle the external request, and normalizers translate provider-specific data into one stable response contract.

The default implementation talks to Open-Meteo, while a mock provider proves the provider can be swapped without changing controllers, routes, or business logic. The service also owns derived data such as isFreezing instead of pretending it came from the upstream API.

Decisions That Mattered.

  • Keep provider-specific response shapes inside provider and normalizer code instead of letting them spread through the application.
  • Use a small factory for provider selection rather than building a plugin system the project did not need.
  • Put derived business fields in the service layer so external data concerns and application rules stay separate.
  • Use a deterministic mock provider for tests so the test suite never depends on an external weather service being available.

Challenges & Tradeoffs.

The main tradeoff was knowing where to stop abstracting. It would have been easy to build a generic adapter framework, but that would make a small service harder to understand without buying much flexibility.

The project was intentionally timeboxed, so the goal was a complete demonstration of swapability and error handling rather than a production weather platform with every possible provider and resilience pattern.

Testing & Reliability.

Vitest and Supertest cover request validation, provider switching, field filtering, error handling, and the normalized response contract.

Tests use the mock provider instead of the network, which keeps them fast and makes failures about the application rather than an upstream API.

What I'd Change Today.

  • Add a second real provider to prove the abstraction against another production API instead of only the mock implementation.
  • For a production service, add provider-level observability and resilience such as retry policy, circuit breaking, and request metrics where the traffic actually justifies them.