How to Use a Weather API in Node.js

Adding weather data to a Node.js application requires only a few lines of JavaScript. In this tutorial, we’ll use the Visual Crossing Timeline Weather API to retrieve historical weather, current conditions, and weather forecast data from a Node.js application.

Visual Crossing provides a complete Weather API for developers who need to integrate weather into applications, websites, analytics platforms, and other software.

If you want to explore the available datasets before writing code, the Visual Crossing Weather Data platform lets you interactively search, view, and download historical and forecast weather data.

We’ll build this example using modern JavaScript features including:

  • fetch()
  • async and await
  • URLSearchParams
  • Environment variables
  • The Visual Crossing Timeline Weather API

No additional Node.js packages are required.

What we’ll build

We’ll start by retrieving a weather forecast for a location:

const weather = await getWeather("New York, NY");

We’ll then extend the same function to retrieve historical weather for a specific date or date range:

const weather = await getWeather(
  "New York, NY",
  "2026-07-01",
  "2026-07-07"
);

Finally, we’ll create a simple Node.js web application that accepts a location and displays daily weather data in an HTML table.

The same Timeline Weather API endpoint can provide historical weather, current conditions, forecasts, and other weather information, so you do not need separate APIs for each type of weather data.

Prerequisites

You’ll need:

  • Node.js 18 or later
  • A Visual Crossing Weather account
  • Your Visual Crossing Weather API key
  • A text or code editor

This tutorial uses the built-in fetch() function available in modern Node.js, so we don’t need to install an HTTP-request library.

1. Get your Weather API key

Create a free Visual Crossing account and retrieve your Weather API key from your account page.

Your API key authenticates requests to the Visual Crossing Weather API.

Do not commit your API key directly into source code or a public Git repository. We’ll store the key in an environment variable instead.

Visual Crossing plans include usage and request limits. Applications should be designed to remain within the limits of your account plan and should avoid making unnecessary duplicate requests.

If you’re new to the API, you can also use the Visual Crossing Weather Data Query Builder to explore locations, dates, weather elements, and API responses before writing any code.

2. Create a Node.js project

Create a new directory for the application:

mkdir weather-node-example
cd weather-node-example

Initialize the project:

npm init --yes

Then create a file named:

main.js

Because this example uses only functionality built into Node.js, we don’t need to install any additional npm packages.

3. Store your API key in an environment variable

Our JavaScript will read the API key from:

process.env.VISUAL_CROSSING_API_KEY

On macOS or Linux, you can set the variable before running your application:

export VISUAL_CROSSING_API_KEY="YOUR_API_KEY"

In Windows PowerShell:

$env:VISUAL_CROSSING_API_KEY="YOUR_API_KEY"

Then your Node.js application can retrieve the key using:

const API_KEY = process.env.VISUAL_CROSSING_API_KEY;

We’ll also check that the key exists:

const API_KEY = process.env.VISUAL_CROSSING_API_KEY;

if (!API_KEY) {
  throw new Error(
    "VISUAL_CROSSING_API_KEY environment variable is not set"
  );
}

Using an environment variable helps keep credentials separate from your application source code.

4. Understand the Timeline Weather API request

The Visual Crossing Timeline Weather API uses the following basic request structure:

/timeline/[location]/[start-date]/[end-date]

Only the location is required.

For example, a forecast request for New York City is:

/timeline/New%20York%20City%2CNY

A historical weather request for July 1, 2026 is:

/timeline/New%20York%20City%2CNY/2026-07-01

A date-range request is:

/timeline/New%20York%20City%2CNY/2026-07-01/2026-07-07

When dates are omitted, the Timeline Weather API returns the available weather forecast.

When historical dates are supplied, it returns historical weather data for those dates.

This unified structure means the same application code can retrieve both historical and forecast weather.

5. Build the Weather API request in Node.js

Let’s create a function that builds the request URL.

Add the following to main.js:

const API_KEY = process.env.VISUAL_CROSSING_API_KEY;
const UNIT_GROUP = "us";

if (!API_KEY) {
  throw new Error(
    "VISUAL_CROSSING_API_KEY environment variable is not set"
  );
}

function buildWeatherUrl(location, startDate, endDate) {
  let url =
    "https://weather.visualcrossing.com/" +
    "VisualCrossingWebServices/rest/services/timeline/" +
    encodeURIComponent(location);

  if (startDate) {
    url += "/" + encodeURIComponent(startDate);
  }

  if (startDate && endDate) {
    url += "/" + encodeURIComponent(endDate);
  }

  const params = new URLSearchParams({
    key: API_KEY,
    unitGroup: UNIT_GROUP,
    include: "days,hours,current",
    contentType: "json"
  });

  return `${url}?${params}`;
}

There are two important parts to this function.

First:

encodeURIComponent(location)

encodes spaces, commas, and other characters that may appear in addresses.

Second, URLSearchParams creates the API query parameters:

const params = new URLSearchParams({
  key: API_KEY,
  unitGroup: UNIT_GROUP,
  include: "days,hours,current",
  contentType: "json"
});

This is easier and safer than manually concatenating every query parameter into the URL.

6. Retrieve weather data using fetch()

Now we can retrieve the weather data.

Add this function:

async function getWeather(location, startDate, endDate) {
  const url = buildWeatherUrl(
    location,
    startDate,
    endDate
  );

  const response = await fetch(url);

  if (!response.ok) {
    const message = await response.text();

    throw new Error(
      `Weather API request failed (${response.status}): ${message}`
    );
  }

  return response.json();
}

The important line is:

const response = await fetch(url);

fetch() sends the HTTPS request to the Visual Crossing Weather API.

We then check:

response.ok

to make sure the API returned a successful HTTP response.

Finally:

return response.json();

converts the JSON response into a JavaScript object.

7. Retrieve a weather forecast

We can now retrieve the forecast for any location.

For example:

async function main() {
  const weather = await getWeather(
    "New York, NY"
  );

  console.log(weather);
}

main().catch(console.error);

Run the application:

node main.js

The Timeline Weather API returns a JSON response containing information including:

weather.resolvedAddress
weather.timezone
weather.days
weather.currentConditions

Daily forecast data is available in:

weather.days

For example:

const today = weather.days[0];

console.log(today.datetime);
console.log(today.tempmax);
console.log(today.tempmin);
console.log(today.precip);
console.log(today.conditions);

8. Retrieve historical weather data

The same function can retrieve historical weather simply by supplying dates.

For example:

const weather = await getWeather(
  "New York, NY",
  "2026-07-01"
);

To retrieve a date range:

const weather = await getWeather(
  "New York, NY",
  "2026-07-01",
  "2026-07-07"
);

The daily records are again available in:

weather.days

For example:

weather.days.forEach(day => {
  console.log(
    day.datetime,
    day.tempmax,
    day.tempmin,
    day.precip,
    day.conditions
  );
});

One of the advantages of the Timeline Weather API is that your application doesn’t need different code for historical and forecast weather. The requested dates determine the appropriate weather data automatically.

If you’re primarily researching, downloading, or analyzing datasets rather than integrating weather directly into an application, the Visual Crossing Weather Data service provides tools for accessing historical and forecast datasets without writing code.

9. Retrieve hourly weather data

Each daily weather record can also contain hourly data.

For example:

const day = weather.days[0];

day.hours.forEach(hour => {
  console.log(
    hour.datetime,
    hour.temp,
    hour.precipprob,
    hour.conditions
  );
});

A typical hourly record contains fields including:

hour.datetime
hour.temp
hour.feelslike
hour.humidity
hour.precip
hour.precipprob
hour.windspeed
hour.winddir
hour.conditions
hour.icon

You can choose whether your application needs daily, hourly, current, or other weather information using the include request parameter.

10. Request only the weather elements you need

The API can return a large number of weather variables. If your application needs only a subset, use the elements parameter.

For example:

const params = new URLSearchParams({
  key: API_KEY,
  unitGroup: UNIT_GROUP,
  include: "days",
  elements:
    "datetime,tempmax,tempmin,precip,precipprob,conditions",
  contentType: "json"
});

This tells the API that we only need:

  • Date
  • Maximum temperature
  • Minimum temperature
  • Precipitation
  • Precipitation probability
  • Conditions

Reducing the fields returned can decrease response size and simplify application processing.

For the complete list of available weather fields and options, see the Visual Crossing Weather API documentation.

11. Create a simple Node.js weather web application

Now let’s use our weather function in a simple web server.

Node.js includes the http module, so we can create a basic server without installing Express or another framework.

At the top of main.js, add:

const http = require("node:http");

Then create the server:

const server = http.createServer(
  async (request, response) => {
    try {
      const requestUrl = new URL(
        request.url,
        `http://${request.headers.host}`
      );

      const location =
        requestUrl.searchParams.get("location");

      const startDate =
        requestUrl.searchParams.get("start");

      const endDate =
        requestUrl.searchParams.get("end");

      response.setHeader(
        "Content-Type",
        "text/html; charset=utf-8"
      );

      if (!location) {
        response.statusCode = 400;

        response.end(
          "Please include a location query parameter."
        );

        return;
      }

      const weather = await getWeather(
        location,
        startDate,
        endDate
      );

      response.end(createWeatherHtml(weather));

    } catch (error) {
      console.error(error);

      response.statusCode = 500;

      response.end(
        "Unable to retrieve weather data."
      );
    }
  }
);

server.listen(8081, "127.0.0.1", () => {
  console.log(
    "Server running at http://127.0.0.1:8081/"
  );
});

The server accepts the following URL parameters:

location
start
end

Only location is required.

12. Display the weather data

We’ll create a simple HTML table so that the focus remains on retrieving and processing the weather information.

Add the following function:

function createWeatherHtml(weather) {
  const rows = weather.days.map(day => `
    <tr>
      <td>${escapeHtml(day.datetime)}</td>
      <td>${escapeHtml(day.conditions ?? "")}</td>
      <td>${day.tempmax ?? ""}</td>
      <td>${day.tempmin ?? ""}</td>
      <td>${day.precip ?? ""}</td>
      <td>${day.precipprob ?? ""}</td>
    </tr>
  `).join("");

  return `
    <!DOCTYPE html>
    <html lang="en">

    <head>
      <meta charset="UTF-8">
      <meta
        name="viewport"
        content="width=device-width, initial-scale=1"
      >

      <title>Weather Data</title>

      <style>
        body {
          font-family: Arial, sans-serif;
          margin: 30px;
        }

        table {
          border-collapse: collapse;
          width: 100%;
          max-width: 900px;
        }

        th,
        td {
          border: 1px solid #ddd;
          padding: 8px;
          text-align: left;
        }

        th {
          font-weight: 600;
        }
      </style>
    </head>

    <body>

      <h1>
        Weather for
        ${escapeHtml(weather.resolvedAddress)}
      </h1>

      <table>
        <thead>
          <tr>
            <th>Date</th>
            <th>Conditions</th>
            <th>High</th>
            <th>Low</th>
            <th>Precipitation</th>
            <th>Precipitation Probability</th>
          </tr>
        </thead>

        <tbody>
          ${rows}
        </tbody>
      </table>

    </body>
    </html>
  `;
}

Because we’re generating HTML, we’ll also add a small function to escape strings before inserting them into the page:

function escapeHtml(value) {
  return String(value)
    .replaceAll("&", "&amp;")
    .replaceAll("<", "&lt;")
    .replaceAll(">", "&gt;")
    .replaceAll('"', "&quot;")
    .replaceAll("'", "&#039;");
}

For a production application, you would normally use your existing web framework, template engine, or front-end application rather than manually generating HTML.

The simple server here is intended only to demonstrate how Weather API data can be added to a Node.js application without additional dependencies.

13. Run the weather application

Start the server:

node main.js

Then open a browser and request:

http://127.0.0.1:8081/?location=New%20York%2CNY

You should see the available forecast for New York.

To retrieve historical weather:

http://127.0.0.1:8081/?location=New%20York%2CNY&start=2026-07-01&end=2026-07-07

The same Node.js application now supports both forecast and historical weather.

14. Complete Node.js example

Here is the complete application:

"use strict";

const http = require("node:http");

const API_KEY =
  process.env.VISUAL_CROSSING_API_KEY;

const UNIT_GROUP = "us";

if (!API_KEY) {
  throw new Error(
    "VISUAL_CROSSING_API_KEY environment variable is not set"
  );
}


function buildWeatherUrl(
  location,
  startDate,
  endDate
) {
  let url =
    "https://weather.visualcrossing.com/" +
    "VisualCrossingWebServices/rest/services/timeline/" +
    encodeURIComponent(location);

  if (startDate) {
    url += "/" + encodeURIComponent(startDate);
  }

  if (startDate && endDate) {
    url += "/" + encodeURIComponent(endDate);
  }

  const params = new URLSearchParams({
    key: API_KEY,
    unitGroup: UNIT_GROUP,
    include: "days",
    elements:
      "datetime,tempmax,tempmin,precip,precipprob,conditions",
    contentType: "json"
  });

  return `${url}?${params}`;
}


async function getWeather(
  location,
  startDate,
  endDate
) {
  const url = buildWeatherUrl(
    location,
    startDate,
    endDate
  );

  const response = await fetch(url);

  if (!response.ok) {
    const message = await response.text();

    throw new Error(
      `Weather API request failed (${response.status}): ${message}`
    );
  }

  return response.json();
}


function escapeHtml(value) {
  return String(value)
    .replaceAll("&", "&amp;")
    .replaceAll("<", "&lt;")
    .replaceAll(">", "&gt;")
    .replaceAll('"', "&quot;")
    .replaceAll("'", "&#039;");
}


function createWeatherHtml(weather) {
  const rows = weather.days.map(day => `
    <tr>
      <td>${escapeHtml(day.datetime)}</td>
      <td>${escapeHtml(day.conditions ?? "")}</td>
      <td>${day.tempmax ?? ""}</td>
      <td>${day.tempmin ?? ""}</td>
      <td>${day.precip ?? ""}</td>
      <td>${day.precipprob ?? ""}</td>
    </tr>
  `).join("");

  return `
    <!DOCTYPE html>
    <html lang="en">

    <head>
      <meta charset="UTF-8">

      <meta
        name="viewport"
        content="width=device-width, initial-scale=1"
      >

      <title>Weather Data</title>
    </head>

    <body>

      <h1>
        Weather for
        ${escapeHtml(weather.resolvedAddress)}
      </h1>

      <table border="1" cellpadding="8">
        <thead>
          <tr>
            <th>Date</th>
            <th>Conditions</th>
            <th>High</th>
            <th>Low</th>
            <th>Precipitation</th>
            <th>Precipitation Probability</th>
          </tr>
        </thead>

        <tbody>
          ${rows}
        </tbody>
      </table>

    </body>
    </html>
  `;
}


const server = http.createServer(
  async (request, response) => {
    try {
      const requestUrl = new URL(
        request.url,
        `http://${request.headers.host}`
      );

      const location =
        requestUrl.searchParams.get("location");

      const startDate =
        requestUrl.searchParams.get("start");

      const endDate =
        requestUrl.searchParams.get("end");

      response.setHeader(
        "Content-Type",
        "text/html; charset=utf-8"
      );

      if (!location) {
        response.statusCode = 400;

        response.end(
          "Please include a location query parameter."
        );

        return;
      }

      const weather = await getWeather(
        location,
        startDate,
        endDate
      );

      response.end(
        createWeatherHtml(weather)
      );

    } catch (error) {
      console.error(error);

      response.statusCode = 500;

      response.end(
        "Unable to retrieve weather data."
      );
    }
  }
);


server.listen(
  8081,
  "127.0.0.1",
  () => {
    console.log(
      "Server running at http://127.0.0.1:8081/"
    );
  }
);

15. Choosing between US and metric units

Our example uses:

const UNIT_GROUP = "us";

To retrieve metric values instead, use:

const UNIT_GROUP = "metric";

The selected unit group controls units including temperature, precipitation, wind speed, and other weather measurements.

The Timeline Weather API documentation contains the complete unit-group definitions.

16. Add current conditions

If your application needs current weather as well as forecast data, change:

include: "days"

to:

include: "current,days"

Current conditions are then available in:

weather.currentConditions

For example:

console.log(
  weather.currentConditions.temp
);

console.log(
  weather.currentConditions.conditions
);

console.log(
  weather.currentConditions.humidity
);

17. Error handling

Network requests can fail, and applications should not assume every Weather API request will succeed.

Our getWeather() function checks:

if (!response.ok)

and includes the returned status code and API response when throwing an error.

Depending on your application, you may also want to handle:

  • Missing or invalid locations
  • Invalid dates
  • Authentication errors
  • Weather API usage limits
  • Temporary network failures
  • Timeouts
  • Invalid user input

Production applications should log enough information to diagnose problems without unnecessarily logging sensitive information such as API keys..

18. Weather API usage and plan limits

Visual Crossing account plans include usage and request limits that should be followed when building Node.js applications.

Avoid designs that issue a new Weather API request every time the same information is needed if the result can reasonably be reused.

When processing large numbers of locations or dates, consider:

  • Retrieving continuous date ranges rather than individual days
  • Requesting only the weather elements required
  • Caching repeated requests
  • Monitoring account usage
  • Selecting an account plan appropriate for the application’s traffic

Using include and elements to request only the required data can also simplify your application and reduce response sizes.

Going further with Visual Crossing Weather

Once you can retrieve Timeline Weather API data from Node.js, the same techniques can be used for much more than the simple example in this tutorial.

You can build applications that use:

  • Historical weather data
  • Current weather conditions
  • Hourly forecasts
  • Daily forecasts
  • Weather alerts
  • Air quality
  • Solar and energy weather data
  • Agricultural weather variables
  • Weather events
  • Other specialized weather datasets

If your primary goal is to integrate weather directly into software, explore the Visual Crossing Weather API to learn about the available APIs and developer capabilities.

If your goal is to search, explore, download, or analyze historical and forecast datasets, visit Visual Crossing Weather Data.

For detailed request parameters, response fields, and available weather elements, see the Timeline Weather API documentation and Weather Data documentation.

Summary

Modern Node.js makes retrieving weather data straightforward because HTTPS requests can be made directly using the built-in fetch() API.

The basic process is:

  1. Create a Visual Crossing Weather account and obtain an API key.
  2. Store the API key securely using an environment variable.
  3. Build a Timeline Weather API URL for the required location and dates.
  4. Retrieve the weather data using fetch().
  5. Check the HTTP response for errors.
  6. Parse the returned JSON.
  7. Read daily, hourly, or current weather values from the response.
  8. Design the application to remain within the limits of your Visual Crossing plan.

The same Node.js code can retrieve historical weather, current conditions, and forecasts, making the Timeline Weather API a simple foundation for weather-enabled JavaScript applications.