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.