p5.js Intermediate Article 10

APIs & External Data

Fetch JSON from public APIs, handle async data loading, and drive visuals with live weather, finance, or music data.

⏱ 20 min read API fetch async JSON live data WebSocket

The two approaches

loadJSON() in preload — simple, blocking, good for static data files:

let data;

function preload() {
  data = loadJSON('https://api.example.com/data.json');
}

fetch() / httpGet() in draw or event handlers — non-blocking, good for live polling or triggered loads:

let weatherData = null;

function setup() {
  createCanvas(600, 400);
  fetchWeather('London');
}

function fetchWeather(city) {
  let url = `https://wttr.in/${city}?format=j1`;
  loadJSON(url, function(data) {
    weatherData = data;
  });
}

The fetch API

Modern JavaScript’s fetch() returns a Promise. Use async/await for readable async code:

async function loadData(url) {
  try {
    let res  = await fetch(url);
    let json = await res.json();
    return json;
  } catch (err) {
    console.error('Failed to fetch:', err);
    return null;
  }
}

function setup() {
  createCanvas(700, 500);
  loadData('https://api.open-meteo.com/v1/forecast?latitude=51.5&longitude=-0.12&current_weather=true')
    .then(d => {
      if (d) weatherData = d.current_weather;
    });
}

CORS and proxy issues

Browsers block cross-origin requests by default. If an API doesn’t send Access-Control-Allow-Origin headers:

  1. Use a proxy server — run a small Node/Express server that fetches and forwards the data
  2. Use a CORS proxy — services like https://corsproxy.io/ (only for testing, not production)
  3. Use APIs that support CORS — most modern public APIs do

Public APIs for creative use

API Data Needs key?
Open-Meteo Weather, forecasts No
wttr.in Weather No
NASA APIs Astronomy, ISS, APOD Free key
OpenStreetMap Nominatim Geocoding No
Wikipedia Articles, summaries No
USGS Earthquake Feed Earthquake data No
CoinGecko Crypto prices No (limited)

Earthquake visualiser

let quakes = [];

async function loadQuakes() {
  let url = 'https://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/2.5_day.geojson';
  let res  = await fetch(url);
  let json = await res.json();
  quakes = json.features.map(f => ({
    mag:  f.properties.mag,
    lon:  f.geometry.coordinates[0],
    lat:  f.geometry.coordinates[1],
    name: f.properties.place
  }));
}

function setup() {
  createCanvas(800, 500);
  loadQuakes();
}

function draw() {
  background(10, 20, 40);

  // Simple equirectangular map projection
  for (let q of quakes) {
    let x = map(q.lon, -180, 180, 0, width);
    let y = map(q.lat, -90, 90, height, 0);
    let r = map(q.mag, 0, 8, 3, 30);

    let hot = map(q.mag, 2.5, 8, 0, 1);
    fill(lerp(50, 255, hot), lerp(200, 50, hot), lerp(255, 50, hot), 180);
    noStroke();
    circle(x, y, r * 2);
  }

  fill(255);
  textSize(12);
  text(`${quakes.length} earthquakes in the past day`, 10, 20);
}

WebSockets for real-time data

For live streaming data (financial tickers, live sensors), WebSockets provide a persistent connection:

let ws;
let latestPrice = null;

function setup() {
  createCanvas(700, 400);

  ws = new WebSocket('wss://your-realtime-api.com/stream');

  ws.onopen = function() {
    ws.send(JSON.stringify({ action: 'subscribe', symbol: 'BTC-USD' }));
  };

  ws.onmessage = function(event) {
    let data = JSON.parse(event.data);
    latestPrice = data.price;
  };

  ws.onerror = function(err) {
    console.error('WebSocket error:', err);
  };
}

function draw() {
  background(20);

  if (latestPrice) {
    fill(100, 255, 150);
    textSize(48);
    textAlign(CENTER, CENTER);
    text(`$${latestPrice.toFixed(2)}`, width / 2, height / 2);
  } else {
    fill(100);
    textSize(20);
    textAlign(CENTER, CENTER);
    text('Connecting...', width / 2, height / 2);
  }
}

Caching and rate limiting

APIs have rate limits. Cache responses and only refetch when needed:

let cache = {};
let lastFetch = 0;
const CACHE_MS = 60000;  // 1 minute

async function fetchWithCache(url) {
  let now = Date.now();
  if (cache[url] && now - lastFetch < CACHE_MS) {
    return cache[url];
  }
  let res = await fetch(url);
  let data = await res.json();
  cache[url] = data;
  lastFetch = now;
  return data;
}

Key takeaways

  • loadJSON(url, callback) is the p5 way; fetch() with async/await is more flexible
  • CORS blocks cross-origin requests — use APIs that support it, or proxy through your own server
  • Don’t call APIs inside draw() — trigger fetches from events or intervals
  • Cache responses and respect rate limits
  • WebSockets provide push-based real-time data without polling
  • Public APIs (weather, earthquakes, astronomy) are fantastic sources of live data for creative work