docs/plugins/call-existing-api.md
:::caution Legacy Documentation
This section is part of the legacy plugins documentation. The frontend code examples on this page use the old frontend system APIs (discoveryApiRef, fetchApiRef from @backstage/core-plugin-api). The same APIs are available in the new frontend system via @backstage/frontend-plugin-api. The general guidance on when to use direct requests vs. the proxy vs. a backend plugin remains valid for both systems.
:::
This article describes the various options that Backstage frontend plugins have, in communicating with service APIs that already exist. Each section below describes a possible choice, and the circumstances under which it fits.
In these examples, we will be ultimately requesting data from the fictional FrobsCo API.
The most basic choice available is to issue requests directly from the plugin
frontend code to the FrobsCo API, using for example fetch or a support library
such as axios.
Example:
import useAsync from 'react-use/esm/useAsync';
function AwesomeUsersTable() {
const { value, loading, error } = useAsync(async () => {
const response = await fetch('https://api.frobsco.com/v1/list');
return response.json();
}, []);
...
}
Internally at Spotify, this has not been a very common choice. Third party APIs are sometimes accessed like this. Just a handful of internal APIs also went through the trouble of exposing themselves in a way that is useful directly from a browser, but even then, often not from the public internet but only supporting users that are already on the company VPN.
This can be used when:
Backstage has an optional proxy plugin for the backend, that can be used to easily add proxy routes to downstream APIs.
Example:
# In app-config.yaml
proxy:
'/frobs': http://api.frobsco.com/v1
import {
useApi,
discoveryApiRef,
fetchApiRef,
} from '@backstage/core-plugin-api';
import useAsync from 'react-use/esm/useAsync';
function FrobsAggregator() {
const fetchApi = useApi(fetchApiRef);
const discoveryApi = useApi(discoveryApiRef);
const { value, loading, error } = useAsync(async () => {
const baseUrl = await discoveryApi.getBaseUrl('proxy');
const response = await fetchApi.fetch(`${baseUrl}/frobs`);
return response.json();
}, [fetchApi, discoveryApi]);
// ...
}
The proxy is powered by the http-proxy-middleware package. See
Proxying for a full description of its configuration options.
Internally at Spotify, the proxy option has been the overwhelmingly most popular choice for plugin makers. Since we have DNS-based service discovery in place and a microservices framework that made it trivial to expose plain HTTP, it has been a matter of just adding a few lines of Backstage config to get the benefit of being easily and robustly reachable from users' web browsers as well.
This may be used instead of direct requests, when:
Much like the Backstage frontend, the Backstage backend also has a plugin system. The above mentioned proxy is actually one such plugin. If you were in need of a more involved integration than just direct access to the FrobsCo API, or if you needed to hold state, you may want to make such a plugin.
For example, assuming you have created a new backend plugin called
frobs-aggregator, you can add a new route like this:
import Router from 'express-promise-router';
export async function createRouter() {
const router = Router();
router.use(express.json());
/* highlight-add-start */
router.get('/summary', async (req, res) => {
const agg = await Promise.all([
fetch('https://api.frobsco.com/v1/list'),
fetch('http://flerps.partnercompany.com:8080/flerp-batch'),
database.currentThunk(),
]).then(async ([frobs, flerps, thunk]) => {
return computeAggregate(await frobs.json(), await flerps.json(), thunk);
});
res.status(200).json(agg);
});
/* highlight-add-end */
}
Then you can fetch the data from your frontend plugin like this:
import {
useApi,
discoveryApiRef,
fetchApiRef,
} from '@backstage/core-plugin-api';
import useAsync from 'react-use/esm/useAsync';
function FrobsAggregator() {
const fetchApi = useApi(fetchApiRef);
const discoveryApi = useApi(discoveryApiRef);
const { value, loading, error } = useAsync(async () => {
// highlight-next-line
const baseUrl = await discoveryApi.getBaseUrl('frobs-aggregator');
// highlight-next-line
const response = await fetchApi.fetch(`${baseUrl}/summary`);
return response.json();
}, [fetchApi, discoveryApi]);
// ...
}
For a more detailed example, see the user-settings plugin backend that stores some state in a database and surfaces an API for the frontend plugin to use.
Internally at Spotify, this has been a fairly popular choice for different reasons. Commonly, the backend has been used as a caching and data massaging layer for slow APIs or APIs whose request/response shapes or speeds were not acceptable for direct use by frontends. For example, this has made it possible to issue efficient batch queries from the frontend, e.g. in big lists or tables that want to resolve a lot of sparse data from the larger list that an underlying service supplies.
This may be used instead of the above, when:
There is a balance to strike regarding when to make an entirely separate backend for a purpose, and when to make a Backstage backend plugin that adapts something that already exists. General advice is not easy to give, but contact us on Discord if you have any questions, and we may be able to offer guidance.