Spoosh

Introduction

Type-safe data fetching for React. Typed hooks with automatic caching, cache invalidation, retry, polling, and a composable plugin system.

Spoosh is a type-safe API toolkit for React. Define your API schema once, then fetch data with fully typed useRead and useWrite hooks that handle caching and automatic cache invalidation based on URL hierarchy.

// 1. Define your schema (or infer it from Hono / Elysia / OpenAPI)
type API = {
  users: { GET: { data: User[]; query: { page?: number } } };
  "users/:id": {
    GET: { data: User };
    PUT: { data: User; body: Partial<User> };
  };
  "users/:id/posts": {
    GET: { data: Post[] };
    POST: { data: Post; body: NewPost };
  };
};

// 2. Create the client
const spoosh = new Spoosh<API, Error>("/api").use([
  cachePlugin({ staleTime: 5 * 60 * 1000 }),
  deduplicationPlugin(),
  invalidationPlugin(),
  // ...other plugins
]);

// 3. Use in components — paths are fully typed from your schema
export const { useRead, useWrite } = create(spoosh);

Using Hono or Elysia? You can infer the schema directly from your server routes for end-to-end type safety with zero manual definitions.

What Makes Spoosh Different

Automatic Cache Invalidation

Spoosh uses a tag-based caching system. Each query is automatically tagged based on its resolved API path:

// Tags are generated from the resolved path:
useRead((api) => api("users").GET());
// → tag: "users"

useRead((api) => api("users/:id").GET({ params: { id: 123 } }));
// → tag: "users/123"

useRead((api) => api("users/:id/posts").GET({ params: { id: 123 } }));
// → tag: "users/123/posts"

When you mutate data, Spoosh automatically invalidates related queries using wildcard pattern matching.

const { trigger } = useWrite((api) => api("users/:id/posts").POST());

// Default invalidation pattern: ["users", "users/*"]
// Matches: "users", "users/123", "users/123/posts", etc.
await trigger({ params: { id: 123 }, body: newPost });

Type-Safe Routing with Zero Strings

Define your API schema once, then use a function-based API that gives you autocomplete for every endpoint, parameter, and response.

// Fully typed paths
const { data: user } = useRead((api) =>
  api("users/:id").GET({ params: { id: 123 } })
);
const { data: posts } = useRead((api) =>
  api("users/:id/posts").GET({ params: { id: 123 }, query: { page: 1 } })
);

TypeScript knows every endpoint exists, every parameter is correct, and every response type matches your schema.

Composable Plugin System

Spoosh has a rich plugin ecosystem. Each plugin adds specific functionality, and TypeScript knows exactly what options are available based on which plugins you install.

import { Spoosh } from "@spoosh/core";
import { cachePlugin } from "@spoosh/plugin-cache";
import { retryPlugin } from "@spoosh/plugin-retry";
import { pollingPlugin } from "@spoosh/plugin-polling";

const spoosh = new Spoosh<API, Error>("/api").use([
  cachePlugin({ staleTime: 5000 }),
  retryPlugin({ retries: 3 }),
  pollingPlugin({ interval: 30000 }),
]);

// Options are typed based on installed plugins
useRead((api) => api("users").GET(), {
  staleTime: 10000, // ✓ Known from cachePlugin
  retry: { retries: 5 }, // ✓ Known from retryPlugin
  interval: 60000, // ✓ Known from pollingPlugin
  // randomOption: true  // ✗ Type error
});

Browse the full list of available plugins on the Plugins page.

Next Steps

On this page