Skip to main content

ADR014: Proper use of HTTP fetching libraries

Context

Until now we have been recommending the use of node-fetch in Node.js contexts through ADR013. Since then, Backstage has had its minimum requirements upgraded to Node.js 20 or newer. The Node.js platform has established a stable, reliable undici based native fetch in these versions. Additionally, there are some issues with using third party libraries that only appeared in newer versions of Node.js.

Decision

All code that is executed in Node.js (including backend and CLIs) should use the native fetch for HTTP data fetching, and typeof fetch as the TypeScript type in code where a fetch implementation can be injected or is referred to. Example:

import { ResponseError } from '@backstage/errors';

// this is implicitly global.fetch
const response = await fetch('https://example.com/api/v1/users.json');
if (!response.ok) {
throw await ResponseError.fromResponse(response);
}
const users = await response.json();

Frontend plugins and packages should prefer to use the fetchApiRef.

import { useApi } from '@backstage/core-plugin-api';

// Inside some React component...
const { fetch } = useApi(fetchApiRef);

const response = await fetch('https://example.com/api/v1/users.json');
if (!response.ok) {
throw await ResponseError.fromResponse(response);
}
const users = await response.json();

Isomorphic packages should have a dependency on the cross-fetch package for mocking and type definitions. Preferably, classes and functions in isomorphic packages should accept an argument of type typeof fetch to let callers supply their preferred implementation of fetch. This lets them adorn the calls with auth or other information, and track metrics etc, in a cross-platform way. Example:

import crossFetch from 'cross-fetch';

export class MyClient {
private readonly fetch: typeof crossFetch;

constructor(options: { fetch?: typeof crossFetch }) {
this.fetch = options.fetch || crossFetch;
}

async users() {
return await this.fetch('https://example.com/api/v1/users.json');
}
}

Consequences

We will gradually transition away from third party fetch replacement packages such as node-fetch and others on the Node.js platform.

The @mswjs/interceptors library as used by msw version 1.x does not support native fetch properly and likely never will. When you switch to using native fetch, you may see msw based tests start to fail to both capture and block traffic. Certain tests may need to be rewritten to use msw 2.x or newer instead, which uses a newer version of the interceptors.