Skip to content

About

Schema-driven sessions for React, with TanStack Router, React Router, and Expo Router support

Topics

Resources

Contributing

Stars

1 star

Watchers

0 watching

Forks

Repository files navigation

session-kit

npm CI bundle size tree-shakeable provenance license

React TanStack Router TanStack Start React Router Next.js Expo Effect

Your backend signs users in. Session kit handles everything after.

Session kit is the session layer for React apps that have their own backend. Hand it the tokens your sign-in endpoint returns, and it saves the session, restores it on reload, refreshes tokens before they expire, keeps every tab in step, and guards your routes.

It works with TanStack Router, React Router, and Expo Router, renders on the server with TanStack Start, Next.js, and React Router, and comes with an Effect service and an effetch plugin.

Alpha. Early development: the API may change between releases, and it isn't ready for production. See the release notes.

Documentation: session-kit.pages.dev

Installation

pnpm add @monarcode/session-kit

Alpha releases are published to the latest distribution tag and may change the public API, so pin an exact version and read the release notes before upgrading.

Any Standard Schema library describes users. The examples use Zod:

pnpm add zod

Every peer dependency is optional: install React and the router your setup uses. Compatibility lists the supported versions.

Quick start

Session kit never calls your backend. Write the requests you'd write anyway: one that signs in, and one that exchanges a refresh token for new tokens.

/** Checks the password, and resolves the tokens and the user. */
export async function login(username: string, password: string) {
  const response = await post("/auth/login", { username, password });
  if (!response.ok) throw new Error(`Sign-in failed (${response.status})`);
  // DummyJSON returns the user's fields beside the tokens.
  const body: unknown = await response.json();
  return { ...tokensSchema.parse(body), user: userSchema.parse(body) };
}

/**
 * Exchanges a refresh token for new tokens. Resolves `null` when the backend
 * rejects the token, and throws for any other failure.
 */
export async function refresh(refreshToken: string, signal: AbortSignal) {
  const response = await post("/auth/refresh", { refreshToken }, { signal });
  // DummyJSON answers 403 for an invalid token and 401 for a missing one.
  if (response.status === 401 || response.status === 403) return null;
  if (!response.ok) throw new Error(`Refresh failed (${response.status})`);
  return tokensSchema.parse(await response.json());
}

Then create the client. It saves the session, restores it on reload, and refreshes the tokens with your refresh:

export const auth = createAuth({
  // Keeps this app's saved session apart from any other on the same origin.
  name: "my-app",
  // Every user is checked against this schema, and typed by it.
  user: userSchema,
  // Where the session is saved: `localStorage`.
  storage: webStorage(),
  // How expired access is replaced.
  refresh: ({ refreshToken, signal }) => refresh(refreshToken, signal),
});

And render by the session's status:

function Screen() {
  const { status, user } = useAuth();
  const client = useAuthClient();

  switch (status) {
    case "initializing":
      return <p>Restoring your session…</p>;
    case "unauthenticated":
      return <SignIn />;
    case "unavailable":
      // Access couldn't be restored or refreshed: offer another try.
      return (
        <button onClick={() => void client.retry().catch(() => {})}>
          Try again
        </button>
      );
    default:
      // Signed in: "authenticated", or "refreshing" with the user on screen.
      return (
        <main>
          <h1>Welcome, {user.firstName}</h1>
          <button onClick={() => void client.signOut().catch(() => {})}>
            Sign out
          </button>
        </main>
      );
  }
}

Getting started builds this into a working app one step at a time, against a real test backend.

Pick your setup

Setup Guide
React without a router Getting started
TanStack Router, and TanStack Start in SPA mode TanStack Router
TanStack Start with server rendering TanStack Start
React Router: declarative, data, framework SPA React Router
React Router, framework mode with SSR React Router with SSR
Next.js 16, App Router Next.js
Expo Router on iOS and Android, SDK 53+ Expo Router
Effect and effetch, with any of the above Effect and effetch

All but the first have a runnable app in examples/.

Contributing

See CONTRIBUTING.md.

License

MIT

About

Schema-driven sessions for React, with TanStack Router, React Router, and Expo Router support

Topics

Resources

Contributing

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages