Skip to content
Integration

Using a football API from JavaScript and Node.js

A server-side Node.js pattern for calling football feeds safely, with timeouts, status handling and a shared cache.

FootballAPI.net editorial team

Written and reviewed by the organisation

Published 30 Oct 2026Updated 1 Nov 20265 min read

FootballAPI.net — Using a football API from JavaScript and Node.js, illustrated by a floodlit stadium pitch

JavaScript runs on both sides of most web products, which makes it easy to make a mistake: calling the football API directly from browser code. That ships your key to every visitor. The right place for the call is a Node.js process on your server, which fetches data, caches it and exposes only what your pages need.

This guide includes a small server-only request and explains how to turn it into a shared polling job. It uses the built-in fetch and AbortSignal.timeout available in Node.js 20 and later, so no extra HTTP library is required. The code fetches a response envelope; production code must also validate the feed data before caching it.

Keep the key on the server

Store your key in an environment variable such as FOOTBALLAPI_KEY and read it at startup. Fail fast with a clear error if it is missing. Never bundle it into front-end code, never commit it to source control and never log it. If you use a framework with both server and client code, make sure the variable is not exposed with a public prefix.

Your browser code then calls your own endpoint, for example a route that returns the cached league table. That endpoint reads from storage and never touches the key.

Use separate keys for development, staging and production. They can be rotated independently from the dashboard if one leaks.

  • Read the key from an environment variable
  • Expose only your own endpoints to the browser
  • One key per environment

Making a request

Build the URL from your base URL, the customer path for the feed and its query parameters. Send the key in the X-API-Key header and ask for JSON. Use an AbortController with a timeout of a few seconds, so a slow upstream response does not hold your worker indefinitely.

Use the API base URL shown in your dashboard and a leagueId copied from the competition directory, not a league name or a guessed numeric ID. A 401 means authentication failed; a 403 requires checking account, plan, league or service permissions. A 429 is a rate or usage restriction: inspect its error code and Retry-After. A 5xx response means the refresh failed, so retain the last-good snapshot.

Only parse the body as JSON after confirming the status, and handle parse errors separately from network errors so your logs tell you what really happened.

const { FOOTBALLAPI_BASE_URL, FOOTBALLAPI_KEY, LEAGUE_ID } = process.env;
if (!FOOTBALLAPI_BASE_URL || !FOOTBALLAPI_KEY || !LEAGUE_ID) {
  throw new Error("Configure the API base URL, secret key and directory league ID");
}
const url = new URL("/api/v1/football/fixtures", FOOTBALLAPI_BASE_URL);
url.searchParams.set("leagueId", LEAGUE_ID);
const response = await fetch(url, {
  headers: { "X-API-Key": FOOTBALLAPI_KEY, Accept: "application/json" },
  signal: AbortSignal.timeout(15000),
});
if (!response.ok) throw new Error(`Football API HTTP ${response.status}`);
const result = await response.json();
if (!result || typeof result !== "object" || !("data" in result) || !("meta" in result)) {
  throw new Error("Unexpected response envelope");
}
// Validate result.data for this feed before updating your last-good cache.

Retries and backoff

Retry only on network errors, timeouts, 429 and 5xx statuses. Never retry 401 or 403, which will not fix themselves. Use exponential backoff with jitter and a maximum number of attempts, and honour the Retry-After header when present.

Keep retries inside your polling job, not inside page requests. A reader should never wait while your server retries an upstream call; they should get the cached copy immediately.

  • Retry network errors, timeouts, 429 and 5xx
  • Never retry 401 or 403
  • Honour Retry-After

Caching the result

After a successful, validated response, write it to your cache with the time it was fetched. A small table keyed by feed and parameters works well, and so does a key-value store. Your page routes read the cache and include the fetch time in their output.

Validate before writing. Check the data and meta envelope and the particular fields your view uses. If validation fails, keep the previous cache and log only a redacted diagnostic summary. Do not dump a response body, authorisation headers or request configuration into general application logs.

Scheduling the poller

Run the poller as a scheduled job separate from your web server, or as a background task with a single active instance. Give each feed its own interval based on how often it changes. Space requests within each run so you stay well under per-second limits.

If you deploy several instances of your app, make sure only one runs the poller, otherwise you multiply requests. A simple database lock or a dedicated worker process solves this.

Types and validation

If you use TypeScript, define types for the fields you use and validate responses at runtime with a schema library. Types alone do not protect you from unexpected data; runtime validation does. Treat every field as optional unless the documentation states otherwise, and narrow types after checking.

Keep identifiers as strings in your code, even when they look numeric. That avoids precision problems and leading-zero surprises, and it matches how they appear in URLs.

Testing

Save real responses from schema mode and from live calls once access is granted, and use them as fixtures in your tests. Test your client against a fake server that returns each status code, so your error handling is exercised before production finds the gaps. Test the poller's locking with two instances running at once.

Putting it together

The finished shape is a small module with one function to fetch a feed with timeout, status checks and retries, one to validate and cache, a scheduler that calls them per feed, and page routes that read only from the cache. It is perhaps a hundred lines, and it will serve a busy football site without drama.

Our code examples page has a ready-made version of the fetch function. Feed identifiers in examples are illustrative; confirm current identifiers in the catalogue or the playground.

Serverless functions

If you deploy to a serverless platform, the same rules apply with a twist. Functions can scale out to many instances, so do not fetch upstream inside a request handler. Use a scheduled function to refresh the cache and plain handlers to read it. Store the cache in a managed database or key-value service shared by all instances, and keep the key in the platform's secret store rather than in code.

Watch cold starts: a scheduled job that times out before finishing should leave the previous cache untouched, so write only after the whole refresh validates.

Key takeaways

  • Keep the key in a server environment variable and never ship it to the browser.
  • Check status codes, add timeouts and retry only recoverable errors.
  • Poll from one instance and serve pages from a validated cache.

Integration checklist

0/10 complete
Progress is saved in this browser only.

Frequently asked questions

Can I call the API from React or another front-end framework?

Not directly. Call it from your server and have the front end read your own endpoint.

Which header carries the key?

X-API-Key.

What does a 403 mean?

Check the response's error code: a 403 can mean missing subscription access, a restricted league, a disabled service or a suspended account. Confirm your plan and saved leagues in the dashboard before contacting support. Do not retry it in a loop.

Do I need an HTTP library?

No. The built-in fetch in current Node.js versions is sufficient.

Sources and references

Use these implementation references alongside this guide. Current endpoint parameters and account settings take precedence over illustrative examples.