Hype StackHypeStack

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.

May 18, 20262 min readby Hype Stack
frontendbackendtypescriptdata-fetching

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.

ts
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.

Before and after showing a renamed backend path breaking a frontend call at typecheck time 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.

ts
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().

A typed useListener hook with the socket event payload type inferred from the backend 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.

Sponsor open source

Every purchase and sponsorship funds my 8+ years of work on open source given freely to the community. It keeps the lights on, funds new packs, and keeps the ecosystem alive. Even a small tier means a lot. Thank you!

Sponsor on GitHub