Full-stack types without the glue code
How the Hype Stack SDK turns Hono route definitions into fully typed HyperFetch requests and WebSocket events on the frontend, with no manual type wiring.

A backend route and the frontend call that hits it should never disagree about the shape of the data. Hype Stack closes that loop with a type bridge that reads your Hono routes and produces a fully typed SDK the frontend imports as a single object.
No hand-written request types. No redeclared payloads. The SDK's type is the backend's type, full stop.
The two halves
On the backend, routes are Hono apps. You declare a path, a validator, and a handler, and Hono infers the request and
response types from that declaration. On the frontend, the SDK is built on HyperFetch. Reads go through
useFetch(sdk.<path>.$get), mutations through useSubmit(sdk.<path>.$post), and WebSocket events through
useListener(socketSdk.<domain>.<event>.$listener).
The bridge between them is generated. The frontend SDK is created from typeof client and the backend's ApiRoutesSdk
type, so the moment you add a route on the backend, it shows up on sdk.
import { createSdk } from "@hyper-fetch/core";
import type { ApiRoutesSdk, WsSocketSdk } from "@internal/backend";
import { client, socket } from "./client";
export const sdk = createSdk<typeof client, ApiRoutesSdk>(client);
export const socketSdk = createSocketSdk<typeof socket, WsSocketSdk>(socket);What that buys you
Change a response shape and every useFetch consuming it flags the mismatch at typecheck time. Rename a path and the
call site breaks with a real error, not a 404 at runtime. Add a new endpoint and it is on sdk immediately, with its
request and response types carried over for free.
The same applies to sockets. A new backend event appears on socketSdk.<domain>.<event>, and the payload type is
whatever the backend emitter declared. You import the payload type directly from the backend package instead of
re-exporting it through a listener index file.
Rename a route on the backend and the frontend call fails at typecheck, not as a 404 in production.
Cache invalidation, done right
HyperFetch caches by endpoint key. After a mutation that affects data another component reads, you invalidate with
client.cache.invalidate(/\/invitations\/pending/), using the client import directly. The regex matches every
endpoint whose path fits, so every useFetch sharing that cache key refetches on its own.
The one rule that matters: never traverse through an SDK request to reach the cache.
sdk.foo.$get.client.cache.invalidate(...) looks like it should work and quietly does the wrong thing. Always go
through the client import.
import { client } from "@/api/client";
// Invalidate every endpoint whose path matches after a mutation.
client.cache.invalidate(/\/invitations\/pending/);WebSockets without the usual mess
Raw useEffect plus .listen() is the default way people wire sockets, and it is where bugs live: stale closures,
double subscriptions, listeners that never clean up. The typed socket SDK replaces that with
useListener(socketSdk.<domain>.<event>.$listener) and an onEvent callback. Subscribe in one line, unsubscribe
automatically, and the payload is typed.
Emitting is symmetric: useEmitter(socketSdk.<domain>.<event>.$emitter) and emit().
One useListener call subscribes, auto-unsubscribes, and infers the payload type straight from the backend emitter.
Why this is worth the setup
The alternative is a frontend that knows the backend exists only through string URLs and hand-maintained types. Every endpoint is a leap of faith, and every refactor is a runtime gamble. With the bridge, a typecheck is a contract check. If the build passes, the calls line up.
For a template that ships auth, billing, and notifications across a real frontend and a real backend, that is the difference between a demo and a starting point you can ship. The HTTP data-fetching docs and WebSocket docs cover the exact patterns.