Introduction
A type-safe API toolkit with automatic cache invalidation and zero boilerplate
Spoosh is a type-safe API toolkit with automatic cache management. Define your API schema once, then get type-safe requests with automatic cache invalidation based on URL hierarchy.
What Makes Spoosh Different
Hierarchical Cache Invalidation
Spoosh uses a tag-based caching system. Each query is automatically tagged based on its URL path hierarchy:
// Query tags are automatically generated from the path:
injectRead((api) => api("users").GET());
// → tags: ["users"]
injectRead((api) => api("users/:id").GET({ params: { id: 123 } }));
// → tags: ["users", "users/123"]
injectRead((api) => api("users/:id/posts").GET({ params: { id: 123 } }));
// → tags: ["users", "users/123", "users/123/posts"]When you mutate data, Spoosh automatically invalidates related queries based on these tags. Create a post for user 123? Every query that depends on that user's data refreshes automatically.
write = injectWrite((api) => api("users/:id/posts").POST);
// This mutation automatically invalidates:
// - users
// - users/123
// - users/123/posts
await write.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
user = injectRead((api) => api("users/:id").GET({ params: { id: 123 } }));
posts = injectRead((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.
Custom Tags
You can also specify custom tags manually using the unified tags option:
// Mode only - 'all' generates full hierarchy
posts = injectRead(
(api) => api("users/:id/posts").GET({ params: { id: "123" } }),
{
tags: "all", // ['users', 'users/123', 'users/123/posts']
}
);
// Mode only - 'self' generates only exact path
posts = injectRead(
(api) => api("users/:id/posts").GET({ params: { id: "123" } }),
{
tags: "self", // ['users/123/posts']
}
);
// Custom tags only - replaces auto-generated tags
users = injectRead((api) => api("users").GET(), {
tags: ["custom-users", "dashboard"], // ['custom-users', 'dashboard']
});
// Mode + custom tags - 'all' mode combined with custom tags
posts = injectRead(
(api) => api("users/:id/posts").GET({ params: { id: "123" } }),
{
tags: ["all", "dashboard"], // ['users', 'users/123', 'users/123/posts', 'dashboard']
}
);
// Mode + custom tags - 'self' mode combined with custom tags
posts = injectRead(
(api) => api("users/:id/posts").GET({ params: { id: "123" } }),
{
tags: ["self", "dashboard"], // ['users/123/posts', 'dashboard']
}
);
// On mutations, specify which tags to invalidate
createPost = injectWrite((api) => api("posts").POST);
await createPost.trigger({
body: { title: "New Post" },
invalidate: ["posts", "stats", "custom-tag"],
});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
injectRead((api) => api("users").GET(), {
staleTime: 10000, // ✓ Known from cachePlugin
retries: 5, // ✓ Known from retryPlugin
interval: 60000, // ✓ Known from pollingPlugin
// randomOption: true // ✗ Type error
});Works with Any REST API
Spoosh works with any REST API. Use it with Express, Hono, Elysia, or any backend framework you prefer.
With Hono and Elysia adapters, you get automatic type inference from your server routes with zero configuration.
Lightweight & Fast
At ~10KB gzipped for the core, Spoosh keeps your bundle size minimal. Add only the plugins you need to extend functionality.
Packages
| Package | Description |
|---|---|
@spoosh/core | Core client and plugin system |
@spoosh/angular | Angular bindings (injectRead, injectWrite, injectInfiniteRead) |
@spoosh/hono | Hono type adapter |
@spoosh/elysia | Elysia type adapter |
@spoosh/openapi | Bidirectional OpenAPI converter (import/export) |
Plugins
| Plugin | Description |
|---|---|
@spoosh/plugin-cache | Response caching with stale time |
@spoosh/plugin-invalidation | Auto-refresh queries after mutations (recommended) |
@spoosh/plugin-retry | Automatic retry with configurable attempts |
@spoosh/plugin-optimistic | Optimistic updates with rollback |
@spoosh/plugin-polling | Automatic polling at intervals |
@spoosh/plugin-debounce | Request debouncing |
@spoosh/plugin-throttle | Request throttling |
@spoosh/plugin-deduplication | Prevents duplicate in-flight requests |
@spoosh/plugin-prefetch | Preload data before needed |
@spoosh/plugin-refetch | Refetch on focus/reconnect |
@spoosh/plugin-initial-data | Show data before fetch completes |
@spoosh/plugin-gc | Garbage collection for cache management |
@spoosh/plugin-transform | Transform request and response data |
@spoosh/plugin-qs | Query string serialization for nested objects |
@spoosh/plugin-progress | Upload/download progress tracking via XHR |
@spoosh/plugin-nextjs | Next.js server revalidation |
@spoosh/plugin-debug | Debug logging |