Skip to content

ebubechi-ihediwa/reqkit

Repository files navigation

Reqkit

Reqkit — a fast, cross-platform API engine for React, React Native (including Expo), and web apps. It unifies networking, caching, background execution, streaming/polling, offline queueing, and typed React hooks into a single, composable developer experience.

Built for performance-critical apps (banking, trading, AI, dashboards) where UI responsiveness and predictable network behavior matter.


Quick facts

  • ✅ Cross-platform: Web + React Native + Expo
  • ⚡ Background execution: react-native-worklets (RN) + Web Workers (Web) with safe main-thread fallback
  • 🧠 Hooks: useReqkitQuery, useReqkitMutation, useReqkitStream, plus createQueryHook / createMutationHook / createStreamHook
  • 🗂️ Caching: in-memory with tag-based invalidation and dedupe for in-flight requests
  • 🔁 Offline queue: persist-and-retry for non-GET requests (pluggable storage)
  • 📡 Streaming & polling: append/replace modes and configurable intervals
  • 🔧 TypeScript-first API and DI-friendly (inject executor, fetch, storage)

Installation

# npm
npm install reqkit

# pnpm
pnpm add reqkit

# react-native-worklets for better RN background performance (optional but recommended)
npm install react-native-worklets

If you don't install react-native-worklets, Reqkit will still work — heavy transforms run on the main JS thread as a fallback.


Table of contents


Quick start

// src/reqkit.ts
import { createReqkit } from "reqkit";
import AsyncStorage from "@react-native-async-storage/async-storage"; // RN/Expo

const storageAdapter = {
  getItem: (k: string) => AsyncStorage.getItem(k),
  setItem: (k: string, v: string) => AsyncStorage.setItem(k, v),
  removeItem: (k: string) => AsyncStorage.removeItem(k),
};

export const {
  ReqkitProvider,
  useReqkitQuery,
  useReqkitMutation,
  useReqkitStream,
  createQueryHook,
  createMutationHook,
  createStreamHook,
  useReqkitGlobal,
  invalidateTag,
  invalidateKey,
  setOnline,
  flushOfflineQueue,
  client,
} = createReqkit({
  baseUrl: "https://api.example.com",
  defaultTimeoutMs: 12_000,
  offlineQueue: { enabled: true, storage: storageAdapter },
});

Wrap your app:

// App.tsx
import React from "react";
import { ReqkitProvider } from "./reqkit";

export default function App() {
  return (
    <ReqkitProvider>
      {/* your app */}
    </ReqkitProvider>
  );
}

Core concepts

ApiClient — the core HTTP client. Handles fetch, timeouts, retry/backoff, caching, dedupe, and optional offline enqueue.

Background Executor — executes heavy transforms off the main thread. On RN, uses react-native-worklets if available; in Web, uses a Web Worker. Fallback runs on main thread.

Cache — in-memory cache with TTLs and tag indices. It also stores inflight promises to dedupe concurrent requests.

OfflineQueue — persistent queue for non-GET requests while offline. Flushable when connectivity returns.

HooksuseReqkitQuery / useReqkitMutation / useReqkitStream provide typed, declarative access to ApiClient functionality and global state.

Named hooks — factory helpers (createQueryHook, createMutationHook, createStreamHook) to create business-level hooks like useGetUserProfile().


API reference

createReqkit(config)

Creates Reqkit instance bound to a client configuration.

Config (selected fields):

  • baseUrl: string — base URL used for requests
  • defaultHeaders?: Record<string, string>
  • getAuthToken?: () => Promise<string | undefined> — attach tokens to outgoing requests
  • defaultTimeoutMs?: number
  • offlineQueue?: { enabled: boolean; storage: StorageAdapter }
  • fetchImpl?: typeof fetch — inject custom fetch (useful for tests)
  • backgroundExecutor?: BackgroundExecutor — inject custom executor (useful in tests)

Returns an object with:

  • ReqkitProvider — React provider
  • useReqkitQuery / useReqkitMutation / useReqkitStream
  • createQueryHook, createMutationHook, createStreamHook
  • Cache invalidation helpers and offline utilities
  • client — the underlying ApiClient

Hooks

useReqkitQuery(options)

Options (common):

  • path: string
  • method?: "GET" | ... (defaults to GET)
  • params?: Record<string, any>
  • cache?: { key?: string; tags?: string[]; cacheTimeMs?: number; staleTimeMs?: number }
  • transformResponse?: (raw) => any — can be run in background if isHeavy: true
  • isHeavy?: boolean — opt into background execution
  • enabled?: boolean — control whether it runs
  • refetchOnMount?: boolean
  • refetchIntervalMs?: number — for polling

Returns:

{ data, error, status, isLoading, isFetching, refetch }

useReqkitMutation(options)

Options:

  • path: string
  • method?: "POST" | "PATCH" | "PUT" | "DELETE" (default POST)
  • offline?: { enqueueIfNetworkError?: boolean }
  • cache?: { tags?: string[] } — tags to invalidate / refresh
  • transformResponse? / isHeavy? / backgroundStrategy?

Returns:

{ mutate, data, error, status, isLoading }

mutate(vars) executes the request and returns the ApiResponse.

useReqkitStream(options)

Options:

  • path: string
  • intervalMs: number — poll frequency
  • append?: boolean — append results into array
  • maxItems?: number — for rolling windows
  • enabled?: boolean
  • autoStart?: boolean — set false in tests and call start() manually

Returns:

{ data, error, isStreaming, start, stop, restart }

useReqkitGlobal()

Returns global request state:

{ activeRequests: number, lastError?: ApiError, extras: Record<string, unknown> }

Use it for global indicators and centralized error toasts.


Examples

Query example (GET)

function Profile() {
  const { data, isLoading, error, refetch } = useReqkitQuery({
    path: "/me",
    cache: { key: "me", tags: ["user:me"], cacheTimeMs: 60_000 }
  });

  if (isLoading) return <div>Loading…</div>;
  if (error) return <div>Error: {error.message}</div>;
  return <div>Hello {data?.name}</div>;
}

Mutation example (POST/PATCH)

function EditProfile() {
  const { mutate, isLoading } = useReqkitMutation({
    path: "/me",
    method: "PATCH",
    cache: { tags: ["user:me"] }
  });

  const save = async (payload) => {
    try {
      await mutate(payload);
      // optimistic UI or success actions
    } catch (err) {
      // handle error
    }
  };
}

Streaming / Polling example

function PriceTicker() {
  const { data, isStreaming } = useReqkitStream({
    path: "/ticker/BTC-USD",
    intervalMs: 1000,
    append: true,
    maxItems: 100,
  });

  return <div>{isStreaming ? "Live" : "Stopped"}{JSON.stringify(data)}</div>;
}

React Native / Expo

  • Add react-native-worklets for best background execution performance.
  • Use AsyncStorage as the default storage adapter for offline queueing.
  • For Expo-managed apps: react-native-worklets works in most managed builds; otherwise Reqkit falls back to main-thread execution.

Example storage adapter for createReqkit in RN:

import AsyncStorage from '@react-native-async-storage/async-storage';

const storageAdapter = {
  getItem: (k: string) => AsyncStorage.getItem(k),
  setItem: (k: string, v: string) => AsyncStorage.setItem(k, v),
  removeItem: (k: string) => AsyncStorage.removeItem(k),
};

Web

  • Reqkit uses a Web Worker for background transforms if available.
  • No extra dependency required for workers — Reqkit auto-generates a worker blob to run pure transform functions.
  • Use localStorage or IndexedDB as a storage adapter for offline queues.

Example storage adapter for Web:

const storageAdapter = {
  getItem: (k) => Promise.resolve(localStorage.getItem(k)),
  setItem: (k, v) => Promise.resolve(localStorage.setItem(k, v)),
  removeItem: (k) => Promise.resolve(localStorage.removeItem(k)),
};

Background execution

Reqkit will attempt to offload heavy JSON transforms and parsing off the main thread.

  • RN: uses react-native-worklets runOnUIAsync when available (mark heavy functions with "worklet" where applicable).
  • Web: uses a generated Web Worker (Blob) to eval and run the transform function (pure functions only).
  • Fallback: main-thread execution when neither is available.

Important: transformResponse must be a pure function without closure over React component state — this allows safe serialization / execution in the background.


Caching & invalidation

  • Cache keys are derived from method + path + params by default, but you can specify cache.key.

  • cache.tags lets you group resources (e.g. user:me).

  • Helpers:

    • invalidateTag(tag) — invalidates all cache entries that were indexed under tag.
    • invalidateKey(key) — remove a single key.
    • invalidatePrefix(prefix) — invalidates keys with a prefix.

Dedupe: concurrent identical GET requests share the same in-flight promise so you only execute the network call once.


Offline queue

  • Off-line queue persists non-GET requests when offline and retries them when connectivity returns.
  • Queue uses a pluggable storage adapter (AsyncStorage or localStorage) and is flushable via flushOfflineQueue().
  • Config example in createReqkit:
createReqkit({
  baseUrl: "https://api.example.com",
  offlineQueue: { enabled: true, storage: storageAdapter }
});

To mark a mutation for offline enqueue:

useReqkitMutation({
  path: "/transfer",
  method: "POST",
  offline: { enqueueIfNetworkError: true }
});

Error handling & retries

  • Retry is automatic for transient failures (configurable backoff and retry count).
  • Errors surfaced by hooks are ApiError objects with code and message.
  • Global error surface: useReqkitGlobal() exposes lastError for centralized toasts.

Contributing

Contributions welcome! Suggested workflow:

  1. Fork repo and create a branch.
  2. Add tests for new features or bug fixes.
  3. Keep PRs focused and small.
  4. Update README and CHANGELOG.

Please follow conventional commits for release automation.


License

MIT © Ebubechi Ihediwa


About

Reqkit is a unified, cross-platform API engine that makes network requests fast, reliable, cached, smooth and reactive, perfect for performance-critical apps.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors