---
id: adrs-adr014
title: 'ADR014: Proper use of HTTP fetching libraries'
description: Architecture Decision Record (ADR) for the proper use of fetchApiRef, native fetch, and cross-fetch for data fetching.
---

## Context

Until now we have been recommending the use of `node-fetch` in Node.js contexts
through [ADR013](./adr013-use-node-fetch.md). 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](https://github.com/backstage/backstage/issues/24590)
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:

```ts
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`](https://backstage.io/api/stable/variables/_backstage_core-plugin-api.index.fetchApiRef.html).

```ts
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:

```ts
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](https://github.com/mswjs/msw/issues/1563#issuecomment-1694249010) 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.
