Typed Fetch Factory

Naseebullah Ahmadi  Senior Software Engineer, London

A reusable fetch factory that infers request/response types from an OpenAPI schema and adds in-memory caching, retry with backoff, and AbortController support, no per-endpoint boilerplate, no `any`.

13 min read
#frontend #tooling

One factory, called once per endpoint, gives you a typed request/response pair plus opt-in caching, opt-in retries, and cancellation. Full breakdown: Building a Typed Fetch Factory.

@itsnas use-todos.ts
codeuse-todos.ts
export function useTodos() {
  const [state, dispatch] = useReducer(reducer<Todo[]>, {
    status: 'idle',
  })
 
  useEffect(() => {
    const controller = new AbortController()
 
    async function fetchTodos() {
      try {
        dispatch({ type: 'FETCH_START' })
 
        const res = await api.getTodos(undefined, {
          signal: controller.signal,
        })
 
        dispatch({ type: 'FETCH_SUCCESS', payload: res })
      } catch (err) {
        if (err instanceof FetchAbortError) return
 
        dispatch({
          type: 'FETCH_ERROR',
          payload: describeFetchError(err),
        })
      }
    }
 
    fetchTodos()
 
    return () => controller.abort()
  }, [])
 
  return state
}
 
const BASE_URL = 'https://jsonplaceholder.typicode.com'
 
// @src/code/fetch-wrapper
const api = {
  getTodos: createFetcher('/todos', 'get', {
    baseUrl: BASE_URL,
    cacheTtlMs: 30_000,
    retries: 2,
  }),
}
 
function describeFetchError(err: unknown): string {
  if (err instanceof FetchError) {
    return `API error ${err.status}: ${err.statusText}`
  }
 
  if (err instanceof FetchParseError) {
    return 'Received an invalid response from the server'
  }
 
  return 'Something went wrong'
}
main
Nas (@itsnas)