Skip to content
DocsTry Aspire
DocsTry

Node.js weather explorer with Aspire

Aspire sample
TypeScript AppHost

Clone, run, and explore this sample

An interactive weather map backed by an Express + OpenTelemetry API that reads live data from the Open-Meteo external service, all orchestrated by a TypeScript AppHost (apphost.mts).

DashboardDatabasesHealth ChecksJavaScriptMetricsNode.jsTypeScript
AppHost

The entry point that composes every resource and dependency in this sample's distributed application.

View on GitHub
apphost.mts
import { createBuilder } from './.aspire/modules/aspire.mjs';
const builder = await createBuilder();
// Real weather comes from Open-Meteo — a free forecast API that needs no key.
// Model it as an external service so the dashboard shows the dependency and polls
// its health, then inject its base URL into the API for on-demand, server-side calls.
const openMeteoUrl = "https://api.open-meteo.com";
const weather = await builder
.addExternalService("open-meteo", openMeteoUrl)
.withHttpHealthCheck({
statusCode: 200,
path: "/v1/forecast?latitude=0&longitude=0&current=temperature_2m",
});
// Run the Express API and expose its HTTP endpoint externally.
// The API is written in TypeScript; load the tsx runtime so Node can execute
// `.ts` files directly (native type stripping is only unflagged in Node >= 22.18).
const api = await builder
.addNodeApp("api", "./api", "src/index.ts")
.withEnvironment("NODE_OPTIONS", "--import tsx")
.withReference(weather)
.withEnvironment("OPEN_METEO_URL", openMeteoUrl)
.withHttpEndpoint({ env: "PORT" })
.withExternalHttpEndpoints()
.withUrlForEndpoint("http", async (url) => {
url.displayText = "API";
});
// Run the Vite frontend after the API and inject the API URL for local proxying.
const frontend = await builder
.addViteApp("frontend", "./frontend")
.withReference(api)
.waitFor(api);
// Bundle the frontend build output into the API container for publish/deploy.
await api.publishWithContainerFiles(frontend, "./static");
await builder.build().run();

An interactive weather map backed by an Express + OpenTelemetry API that reads live data from the Open-Meteo external service, all orchestrated by a TypeScript AppHost (apphost.mts).

The sample consists of two apps:

  • api: A TypeScript Express API run directly with tsx. It returns on-demand forecasts and place names by calling Open-Meteo (modeled as an Aspire external service) plus keyless reverse/forward geocoding, and serves an interactive Scalar API reference. OpenTelemetry instrumentation streams traces, metrics, and logs to the Aspire dashboard.

  • frontend: A React 19 + Vite app rendering a full-screen Leaflet map. Click or search any point to load real current conditions, an hourly strip, and a 5-day forecast, with an animated RainViewer radar overlay, switchable basemaps, geolocation, and a °F/°C toggle.

Run mode:

flowchart LR
    Browser -->|app + HMR| Vite[Vite dev server<br/>React + Leaflet]
    Browser -->|/api/*| Vite
    Vite -. proxy .-> Express[Express API<br/>tsx + OpenTelemetry]
    Express --> OpenMeteo[Open-Meteo<br/>external service]
    Browser -. map & radar tiles .-> Tiles[OSM / CARTO / Esri<br/>RainViewer]

Publish mode:

flowchart LR
    Browser -->|SPA + /api/*| Express[Express API<br/>serves ./static build output + API]
    Express --> OpenMeteo[Open-Meteo<br/>external service]
    Browser -. map & radar tiles .-> Tiles[OSM / CARTO / Esri<br/>RainViewer]
  • addExternalService + withHttpHealthCheck: model the third-party Open-Meteo API as a first-class Aspire resource with a health check surfaced on the dashboard.

  • addNodeApp: run the TypeScript Express API directly with tsx (NODE_OPTIONS=--import tsx).

  • addViteApp: run the React + Vite frontend with hot module replacement.

  • withReference + waitFor: inject the Open-Meteo URL into the API and the API URL into the frontend, and order startup so the frontend waits for the API.

  • withHttpEndpoint / withExternalHttpEndpoints / withUrlForEndpoint: configure and label the endpoints shown in the dashboard.

  • publishWithContainerFiles: bundle the Vite build output into the API container so a single container serves both the SPA and the API in publish mode.

  • OpenTelemetry: distributed traces, metrics, and structured logs from the Node.js API, viewable in the dashboard.

If using the Aspire CLI, run aspire run from this directory.

If using VS Code, open this directory as a workspace and start the AppHost with the Aspire extension.

The dashboard links to the frontend. Click any point on the map, or search for a place, to load a live forecast for that location.

The api app exposes:

  • GET /api/weather?lat={lat}&lon={lon} - current conditions, hourly, and 5-day forecast for a coordinate.

  • GET /api/geocode?q={query} - forward geocoding for the search box.

  • GET /health - health probe used by the AppHost.

  • GET /openapi.json - the raw OpenAPI 3.1 document.

  • GET /reference - an interactive Scalar API reference.

This sample keeps the API endpoints public and unauthenticated so it is easy to run and inspect locally. It does not add authentication, authorization, CSRF protection, or rate limiting, so treat it as a demo pattern rather than production-ready API security.

Inputs are validated at the boundary (latitude/longitude ranges and query lengths) and every outbound call uses a bounded timeout, but the sample calls third-party services (Open-Meteo, geocoding, and RainViewer tiles) without a caching or quota strategy. Production services should add real authN/authZ, request rate limiting, bounded request bodies, response caching, and resilient handling of upstream outages.

For production applications, see the Node.js security best practices, Express security best practices, and the OWASP API Security Top 10.

This sample relies on several free, community-maintained data sources. If you build on it, please honor their attribution and licensing requirements:

Preview

Sample screenshots

Select the image to zoom in.

Screenshot of the interactive weather map in the Node.js weather explorer sample (light theme)
Screenshot of the interactive weather map in the Node.js weather explorer sample (light theme)