# Other frameworks

> Run withBetterSupabase in any framework through a bridge, or write an adapter with handle().

Source: https://bettersupabase.com/docs/frameworks/other

better-supabase ships a bridge for each framework below. A bridge runs an
`@supabase/middleware` entry array (with
[`withBetterSupabase`](/docs/auth/middleware) first) in the framework's
middleware slot and returns the framework's response back through the
entries.

| Framework      | Import                                                   | Docs                                              |
| -------------- | -------------------------------------------------------- | ------------------------------------------------- |
| TanStack Start | `toTanStackStart` from `better-supabase/tanstack-start`  | [TanStack Start](/docs/frameworks/tanstack-start) |
| SvelteKit      | `toSvelteKit` from `better-supabase/sveltekit`           | [SvelteKit](/docs/frameworks/sveltekit)           |
| React Router   | `toReactRouter` from `better-supabase/react-router`      | [React Router](/docs/frameworks/react-router)     |
| H3 2, Nitro 3  | `toH3` from `better-supabase/h3`                         | [H3](/docs/frameworks/h3)                         |
| Nitro 2, Nuxt  | `toH3V1` from `better-supabase/h3/v1`                    | [Nuxt](/docs/frameworks/nuxt)                     |
| Elysia         | `toElysia` from `better-supabase/elysia`                 | [Elysia](/docs/frameworks/elysia)                 |
| Node, Express  | `toNodeHandler`, `toExpress` from `better-supabase/node` | [Node](/docs/frameworks/node)                     |
| Fastify, Koa   | `toFastify`, `toKoa` from `better-supabase/node`         | [Node](/docs/frameworks/node)                     |
| NestJS         | `toNestMiddleware` from `better-supabase/nestjs`         | [NestJS](/docs/frameworks/nestjs)                 |
| Astro          | `createAstro` from `better-supabase/astro`               | [Astro](/docs/frameworks/astro)                   |
| SolidStart     | `createSolidStart` from `better-supabase/solid-start`    | [SolidStart](/docs/frameworks/solid-start)        |

The bridges are typed structurally: none of them imports its framework, so
installing better-supabase adds no framework dependency.

The `@supabase/server` adapters (`adapters/hono`, `/h3`, `/elysia` and
`/nestjs`) are deprecated upstream and removed on 2026-12-01. Move to the
bridge for your framework; NestJS apps move to `better-supabase/nestjs`.

## Write a bridge [#write-a-bridge]

A framework with a middleware that gets a `Request` and a `next()` returning
a `Response` needs about ten lines. Fold the pipeline once, hand the
framework's `next` to the terminal through the context, and return the
response:

```ts title="better-supabase-acme.ts"
import { type AnyEntry, pipeline, seedContext } from "@supabase/middleware";

const NEXT = Symbol("acme.next");

export function toAcme(entries: readonly AnyEntry[]) {
  const run = pipeline(entries, (_request, ctx) => {
    const { c, next } = (
      ctx as { [NEXT]: { c: AcmeContext; next: () => Promise<Response> } }
    )[NEXT];
    Object.assign(c.locals, ctx);
    return next();
  });
  return (c: AcmeContext, next: () => Promise<Response>) =>
    run(c.request, { ...seedContext(c.env), [NEXT]: { c, next } });
}
```

## Write an adapter [#write-an-adapter]

The built-in adapters share their request handling through
`better-supabase/server`, so an adapter for another framework is a thin layer:

| Export                                            | What it does                                                                                                                                                                                                                  |
| ------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `handle(server, request, run, options)`           | resolves the caller, applies the guard (401 or 403 Problem Details), answers `run`'s value as JSON or Problem Details, turns other errors into a 500, adds the context's cookies and hands pending event sends to `waitUntil` |
| `extendServer(server, extra)`                     | the adapter's server: every `BetterServer` method plus the adapter's own, without loading the environment early                                                                                                               |
| `flushEvents(server, waitUntil)`                  | hands event sink sends still running to `waitUntil`, for adapters that build their own responses                                                                                                                              |
| `unexpectedResponse(cause, { instance, expose })` | the 500 for a thrown error, with its message only when `expose` is set                                                                                                                                                        |
| `resolveToken(server, token)`                     | verifies a bare access token (a WebSocket's, a queue message's) without cookies                                                                                                                                               |
| `ctx.cookies()`                                   | the cookies `ctx.apply(response)` would add, for frameworks that set cookies through their own API                                                                                                                            |

An adapter for a framework whose handlers take a `Request` and return a
`Response`:

```ts title="better-supabase-acme.ts"
import type { AnyFunctions, AnyModels, BetterSupabase } from "better-supabase";
import {
  type BetterServer,
  createServer,
  extendServer,
  handle,
  type MiddlewareOptions,
  type ServerContext,
  type ServerOptions,
} from "better-supabase/server";
import type { AcmeContext } from "acme";

export interface BetterAcme<
  M extends AnyModels,
  F extends AnyFunctions,
  E,
> extends BetterServer<M, F, E> {
  route(
    fn: (c: AcmeContext, ctx: ServerContext<M, F, E>) => unknown,
    options?: MiddlewareOptions,
  ): (c: AcmeContext) => Promise<Response>;
}

export function createAcme<M extends AnyModels, D, F extends AnyFunctions, E>(
  betterSupabase: BetterSupabase<M, D, F, E>,
  options: ServerOptions & { readonly exposeErrors?: boolean } = {},
): BetterAcme<M, F, E> {
  const bs = createServer(betterSupabase, options);
  return extendServer<BetterAcme<M, F, E>>(bs, {
    route(fn, routeOptions = {}) {
      return (c) =>
        handle(bs, c.request, (ctx) => fn(c, ctx), {
          ...routeOptions,
          ...(options.exposeErrors === undefined
            ? {}
            : { expose: options.exposeErrors }),
          waitUntil: (promise) => c.waitUntil(promise),
        });
    },
  });
}
```

Then prove it behaves like the built-in adapters with
[`testAdapter`](/docs/extending/conformance):

```ts title="better-supabase-acme.test.ts"
import { testAdapter } from "better-supabase/testing";
import { it } from "vitest";

it("conforms", () =>
  testAdapter("acme", {
    betterSupabase,
    serve: (server, run, { allow, waitUntil }) => {
      const route = createAcme(betterSupabase, server).route(
        (_c, ctx) => run(ctx),
        { allow },
      );
      return (request) => route({ request, waitUntil });
    },
  }));
```

Without an adapter, `createServer(betterSupabase).context(request)` gives the
caller's `db`, `auth` and `supabase` in any framework, and
`defineResource(betterSupabase, table)` serves the REST resource routes from
any router through `resource.handle(request, ctx.db, id)`.