Skip to content

Latest commit

Β 

History

68 Commits

Folders and files

NameName
Last commit message
Last commit date
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

β›… Weather Forecast Service

A monolithic TypeScript application that retrieves weather forecast data from the OpenWeatherMap API, persists it in a PostgreSQL database with Redis caching, and serves it through both a server-rendered web dashboard and a RESTful API β€” no authentication required.


Table of Contents


Summary

The Weather Forecast Service allows visitors to look up current weather conditions and forecasts for any city worldwide. Data is sourced from the OpenWeatherMap API, stored in PostgreSQL via Drizzle ORM, and cached in Redis to minimise external API calls and improve response times.

Key capabilities include:

  • Current Weather β€” temperature, humidity, wind speed/direction, pressure, sunrise/sunset, and conditions for a given city.
  • Weather Forecast β€” short-term forecast data including temperature, humidity, wind, rain volume, and precipitation probability.
  • Web Dashboard β€” an EJS-rendered frontend with a search form, recent searches list, and detailed weather view.
  • REST API β€” JSON endpoints for programmatic access to current weather and forecast data.
  • Background Refresh β€” scheduled cron jobs that automatically update weather data for frequently searched cities.
  • Graceful Degradation β€” a layered data-fetching strategy (Cache β†’ Database β†’ API) ensures the service remains responsive even when the external API is unavailable.

Features

Feature Description
City search Look up weather by exact city name
Multi-layer data fetching Redis cache β†’ PostgreSQL β†’ OpenWeatherMap API fallback chain
Automatic data refresh Cron jobs update current weather (every 2 hrs) and forecasts (every 3 hrs)
Server-side rendering EJS templates for a visitor-friendly dashboard
Structured logging Winston + Logtail (Better Stack) for local and remote log aggregation
Standardised responses Consistent JSON response format via a shared response handler
Global error handling Centralised Express error middleware with network error detection
Environment-aware config Separate database and Redis URLs for development, test, and production

Tech Stack

Backend

Technology Purpose
TypeScript Primary language
Express 5 Web framework
Drizzle ORM Type-safe PostgreSQL ORM & migrations
PostgreSQL Relational database
Redis In-memory cache
node-fetch HTTP client for OpenWeatherMap API
node-schedule Cron-style scheduled tasks
Winston Logging framework
Logtail Remote log transport (Better Stack)
Joi Request validation schemas
CORS Cross-origin resource sharing
dotenv Environment variable management

Frontend

Technology Purpose
EJS Server-side HTML templating
Tailwind CSS 4 Utility-first CSS framework

Development & Testing

Technology Purpose
tsx TypeScript execution & watch mode
Jest Unit & integration testing
SuperTest HTTP assertion library for API tests
Drizzle Kit Database migration tooling

Architecture

The application follows a layered architecture within a monolithic Express application:

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                   Weather Forecast Service              β”‚
β”‚                                                         β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
β”‚  β”‚  Web Layer  β”‚   β”‚Service Layer β”‚   β”‚  Data Layer  β”‚  β”‚
β”‚  β”‚(Controllers/│◄─►│  (Services)  │◄─►│  (Repos /    β”‚  β”‚
β”‚  β”‚   Views)    β”‚   β”‚              β”‚   β”‚   Cache)     β”‚  β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
β”‚          β–²                 β–²                β–²           β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
           β”‚                 β”‚                β”‚
           β–Ό                 β–Ό                β–Ό
  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
  β”‚  Web Browser β”‚  β”‚ OpenWeatherMap β”‚  β”‚  PostgreSQL  β”‚
  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚     API        β”‚  β”‚  + Redis     β”‚
                    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Data-fetching waterfall: Every read request follows a three-tier strategy:

  1. Redis Cache β€” fastest; returns immediately if a cache hit is found.
  2. PostgreSQL β€” checked next; results are cached on retrieval.
  3. OpenWeatherMap API β€” last resort; data is persisted to both DB and cache.

Folder Structure

Weather Forecast Service/
β”œβ”€β”€ drizzle/                        # Drizzle-Kit generated migrations
β”‚   β”œβ”€β”€ 0000_square_sir_ram.sql     # Initial migration SQL
β”‚   └── meta/                       # Migration metadata
β”œβ”€β”€ public/                         # Static assets served by Express
β”‚   β”œβ”€β”€ input.css                   # Tailwind CSS source
β”‚   β”œβ”€β”€ output.css                  # Compiled Tailwind CSS
β”‚   └── bitimg.jpg                  # Image asset
β”œβ”€β”€ views/                          # EJS templates
β”‚   β”œβ”€β”€ home.ejs                    # Dashboard β€” search form & recent cities
β”‚   └── weather.ejs                 # Weather detail β€” current + forecast view
β”œβ”€β”€ src/
β”‚   β”œβ”€β”€ index.ts                    # Application entry point (starts server)
β”‚   β”œβ”€β”€ app.ts                      # Express app setup (middleware, routes, crons)
β”‚   β”œβ”€β”€ configs/
β”‚   β”‚   β”œβ”€β”€ db.config.ts            # PostgreSQL pool & Drizzle initialisation
β”‚   β”‚   β”œβ”€β”€ cache.config.ts         # Redis client with reconnection strategy
β”‚   β”‚   └── logger.config.ts        # Winston + Logtail logger configuration
β”‚   β”œβ”€β”€ db/
β”‚   β”‚   └── schema.ts              # Re-exports all Drizzle schemas
β”‚   β”œβ”€β”€ middleware/
β”‚   β”‚   └── errorHandler.ts        # Global Express error handler
β”‚   β”œβ”€β”€ modules/
β”‚   β”‚   └── weather/
β”‚   β”‚       β”œβ”€β”€ weather.routes.ts   # Route definitions (API + Dashboard)
β”‚   β”‚       β”œβ”€β”€ weather.controller.ts # Request handlers
β”‚   β”‚       β”œβ”€β”€ weather.service.ts  # Business logic & DB operations
β”‚   β”‚       β”œβ”€β”€ weather.api.ts      # OpenWeatherMap API integration
β”‚   β”‚       β”œβ”€β”€ weather.cache.ts    # Redis cache operations
β”‚   β”‚       β”œβ”€β”€ weather.cron.ts     # Scheduled background jobs
β”‚   β”‚       β”œβ”€β”€ weather.schema.ts   # Drizzle table definitions
β”‚   β”‚       β”œβ”€β”€ weather.middleware.ts # Route-level middleware (placeholder)
β”‚   β”‚       └── tests/
β”‚   β”‚           β”œβ”€β”€ weather.integration.test.ts
β”‚   β”‚           β”œβ”€β”€ fixtures/       # Test fixtures
β”‚   β”‚           └── tsconfig.json   # Test-specific TS config
β”‚   β”œβ”€β”€ types/
β”‚   β”‚   └── weather.d.ts           # TypeScript type declarations
β”‚   └── utils/
β”‚       β”œβ”€β”€ responseHandler.ts     # Standardised JSON response helper
β”‚       └── isStringArray.ts       # Type guard utility
β”œβ”€β”€ .env                            # Environment variables
β”œβ”€β”€ .gitignore
β”œβ”€β”€ drizzle.config.ts               # Drizzle Kit configuration
β”œβ”€β”€ jest.config.js                  # Jest test configuration
β”œβ”€β”€ tsconfig.json                   # TypeScript compiler options
β”œβ”€β”€ package.json
β”œβ”€β”€ PRD.md                          # Product Requirements Document
β”œβ”€β”€ TDD.md                         # Technical Design Document
└── todo.md                         # Development notes & learnings

Prerequisites

Before setting up the project, ensure you have the following installed:

  • Node.js β‰₯ 18
  • PostgreSQL β€” running locally or a remote instance
  • Redis β€” running locally or a remote instance
  • OpenWeatherMap API key β€” sign up at openweathermap.org

Setup Guide

1. Clone the Repository

git clone https://github.com/MaxKolbe/Weather-Forecast-Service.git
cd Weather-Forecast-Service

2. Install Dependencies

npm install

3. Configure Environment Variables

Create a .env file in the project root (see Environment Variables for the full list):

NODE_ENV=development

# PostgreSQL
PG_DATABASE_DEV_URL=postgresql://postgres:password@localhost:5432/devdb
PG_DATABASE_TEST_URL=postgresql://postgres:password@localhost:5432/testdb
PG_DATABASE_PROD_URL=<your-production-database-url>

# Redis
REDIS_DEV_URL=redis://localhost:6379
REDIS_TEST_URL=redis://localhost:6379
REDIS_PROD_URL=<your-production-redis-url>

# OpenWeatherMap
WEATHER_APIKEY=<your-api-key>

# Logging
LOG_LEVEL=debug
SOURCE_TOKEN=<your-logtail-source-token>
INGESTING_HOST=<your-logtail-ingesting-host>

PORT=3000

4. Run Database Migrations

Generate and apply the database schema:

npm run db:gen-mig

Or run each step separately:

npm run db:generate   # Generate migration files
npm run db:migrate    # Apply migrations

5. Compile Tailwind CSS (optional, for frontend changes)

npm run style

6. Start the Development Server

npm run dev

The server will start at http://localhost:3000.

7. Production Build

npm run build       # Compile TypeScript to dist/
npm run start       # Run the compiled application

API Endpoints

All API routes are prefixed with /api/v1/weather.

Get Current Weather

GET /api/v1/weather/current?city={cityName}

Returns the current weather conditions for the specified city.

Query Parameters:

Parameter Type Required Description
city string Yes City name (case-insensitive)

Success Response 200 OK:

{
  "status": 200,
  "message": "Success: Current Weather found",
  "data": {
    "city": "berlin",
    "country": "DE",
    "timestamp": "2025-03-21T14:30:00.000Z",
    "temperature": 12.5,
    "humidity": 65,
    "windSpeed": 5.2,
    "windDirection": 180,
    "pressure": 1012,
    "conditions": "Clouds",
    "description": "scattered clouds",
    "sunrise": "2025-03-21T06:12:00.000Z",
    "sunset": "2025-03-21T18:34:00.000Z"
  }
}

Error Responses:

Status Condition
400 Missing or empty city parameter
404 City not found
503 Network error (DNS resolution failure)
504 Request to weather API timed out

Get Weather Forecast

GET /api/v1/weather/forecast?city={cityName}

Returns forecast data for the specified city.

Query Parameters:

Parameter Type Required Description
city string Yes City name (case-insensitive)

Success Response 200 OK:

{
  "status": 200,
  "message": "Success: Weather Forecast found",
  "data": {
    "city": "berlin",
    "country": "DE",
    "forecast": {
      "date": "2025-03-22T12:00:00.000Z",
      "temperature": 14.2,
      "humidity": 60,
      "windSpeed": 4.8,
      "conditions": "Clear",
      "description": "clear sky"
    }
  }
}

Error Responses:

Status Condition
400 Missing or empty city parameter
404 City not found
503 Network error (DNS resolution failure)
504 Request to weather API timed out

Dashboard Routes

Method Route Description
GET /api/v1/weather/home Renders the home dashboard with recent searches
GET /api/v1/weather/?city={cityName} Renders the weather detail page for a city

Dashboard

The web dashboard provides a visitor-friendly interface built with EJS and Tailwind CSS:

  • Home Page (/api/v1/weather/home) β€” a search form to enter a city name, along with a list of recently searched cities pulled from the Redis cache.
  • Weather Detail Page (/api/v1/weather/?city=berlin) β€” displays current weather conditions and forecast data side by side, fetched in parallel via Promise.all.

Caching Strategy

Data Type Cache Key Pattern TTL
Current Weather get:currentweather:{city} 15 minutes
Forecast get:forecast:{city} 1 hour
City Name get:city:{city} 24 hours

The Redis client is configured with an exponential backoff reconnection strategy (with jitter) and a maximum of 5 retries to handle transient connection failures gracefully.


Scheduled Jobs

Background cron jobs keep weather data fresh for frequently searched cities:

Job Schedule Description
updateCurrentWeatherCron Every 2 hours Batch-updates current weather for all recently searched cities
updateForecastCron Every 3 hours Batch-updates forecast data for all recently searched cities

Both jobs identify "frequently searched" cities by reading cached city keys and filtering for those searched within the last 12 hours.


Testing

The project uses Jest with SuperTest for integration testing.

# Run the test suite
npm test

Tests are located at src/modules/weather/tests/ and cover:

  • Integration tests β€” end-to-end API endpoint testing with SuperTest
  • Fixtures β€” reusable test data in the fixtures/ directory

Note: Tests run with --experimental-vm-modules for ES module support and --detectOpenHandles to catch unclosed async operations.


Environment Variables

Variable Description
NODE_ENV Environment: development, test, or production
PORT Server port (default: 3000)
PG_DATABASE_DEV_URL PostgreSQL connection string (development)
PG_DATABASE_TEST_URL PostgreSQL connection string (test)
PG_DATABASE_PROD_URL PostgreSQL connection string (production)
REDIS_DEV_URL Redis connection string (development)
REDIS_TEST_URL Redis connection string (test)
REDIS_PROD_URL Redis connection string (production)
WEATHER_APIKEY OpenWeatherMap API key
LOG_LEVEL Winston log level (default: info)
SOURCE_TOKEN Logtail / Better Stack source token
INGESTING_HOST Logtail ingesting endpoint host

Available Scripts

Script Command Description
npm run dev tsx watch src/index.ts Start dev server with hot-reload
npm run build npm install --include=dev && npx tsc Compile TypeScript to dist/
npm run start node dist/index.js Run the production build
npm run watch npx tsc -w Watch-mode TypeScript compilation
npm run db:push npx drizzle-kit push Push schema changes directly
npm run db:generate npx drizzle-kit generate Generate migration files
npm run db:migrate npx drizzle-kit migrate Apply pending migrations
npm run db:gen-mig Generate + migrate in one step Combined migration command
npm run style Tailwind CLI Compile Tailwind CSS (watch mode)
npm test Jest Run the test suite

License

MIT

About

A monolithic Typescript application that retrieves weather forecast data from a third-party API, stores this information in a database, and makes it available through both a web-based dashboard and a REST API.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages