Relay

Examples

Short, copy-paste recipes for things most apps need. Each one works with the default export or your own instance.

On this page

#Fetch and render a list

JavaScript
const list = document.querySelector('#products');
const products = await relay.get('/api/products', { searchParams: { inStock: true } }).json();

list.innerHTML = products
  .map((p) => `<li>${p.name} <span>$${p.price.toFixed(2)}</span></li>`)
  .join('');

#Submit a form

JavaScript
form.addEventListener('submit', async (event) => {
  event.preventDefault();
  const data = Object.fromEntries(new FormData(form));

  try {
    await relay.post('/api/contact', { json: data });
    form.replaceWith('Thanks! We\'ll be in touch.');
  } catch (error) {
    status.textContent = error.status === 422 ? 'Please check the highlighted fields.' : 'Something went wrong.';
  }
});

#Refresh an expired token

Refresh the session once when the API returns 401, then replay the original request.

auth.js
let refreshing = null;

api.interceptors.response.use(async (response, request) => {
  if (response.status !== 401 || request.retried) return response;

  refreshing ??= relay.post('/auth/refresh').json().finally(() => (refreshing = null));
  const { token } = await refreshing;
  session.token = token;

  return api.retry(request);
});

#Upload a file

JavaScript
const body = new FormData();
body.append('file', input.files[0]);
body.append('folder', 'invoices');

const { url } = await relay.post('/api/uploads', { body, timeout: 60_000 }).json();

#Poll until a job finishes

JavaScript
async function waitForExport(id) {
  for (let attempt = 0; attempt < 20; attempt++) {
    const job = await api.get(`exports/${id}`).json();
    if (job.status === 'done') return job.downloadUrl;
    if (job.status === 'failed') throw new Error(job.reason);
    await new Promise((r) => setTimeout(r, Math.min(1000 * 2 ** attempt, 15_000)));
  }
  throw new Error('Export timed out');
}

#Run requests in parallel

JavaScript
const [user, orders, invoices] = await Promise.all([
  api.get('me').json(),
  api.get('orders', { searchParams: { limit: 5 } }).json(),
  api.get('invoices', { searchParams: { status: 'open' } }).json(),
]);

#Use it in a React hook

useApi.js
import { useEffect, useState } from 'react';

export function useApi(path) {
  const [state, setState] = useState({ data: null, error: null, loading: true });

  useEffect(() => {
    const controller = new AbortController();
    api.get(path, { signal: controller.signal }).json()
      .then((data) => setState({ data, error: null, loading: false }))
      .catch((error) => error.code !== 'ABORTED' && setState({ data: null, error, loading: false }));
    return () => controller.abort();
  }, [path]);

  return state;
}

Was this page helpful?

Edit this page on GitHub

Last updated

© 2026 Relay contributors. Released under the MIT License.

v3.2.0 · FAQ · GitHub