Own 100% of your backend and your data

A batteries-included backend that deploys wherever you already run code.

Outer is an alternative to Supabase, PocketBase, and Firebase, built on Kysely, oRPC, and Better Auth. A single builder chain gives you typed schema migrations, auto-CRUD resources, auth, and realtime — with no vendor lock-in and no external services to provision.

npx giget@latest gh:ilhajs/outer/templates/minimal my-outer-app
Kysely
Typed SQL query builder
oRPC
End-to-end typed procedures
Better Auth
Sessions, users, plugins
PGlite
Zero-infra embedded Postgres
What is included

Everything a small backend needs, none of the infra.

One fetch-compatible handler. Chain .schema(), .auth(), .middleware(), and .procedure() in order, then .build() and serve it from Node, Hono, H3, or Next.js API routes.

Typed schema, auto-generated CRUD

Define tables and relations with schema(), and every .schema() call becomes a migration step that advances your DB type. Call .resource("post", ...) and get list, get, create, update, and delete procedures for free, with clean 409/400/404 error mapping instead of raw 500s.

.schema(v1_0)
.resource("post", {
  permissions: {
    list: "public",
    create: "authenticated",
    update: "owner",
    delete: "owner",
  },
  ownerColumn: "userId",
})
// -> post.list, post.get, post.create,
//    post.update, post.delete
Auth and permissions, built in

.auth() mounts Better Auth at /api/auth/** and accepts every Better Auth option directly. Resource permissions — public, authenticated, owner, admin — are declared per-procedure, with owner checks and user-id injection handled automatically.

.auth({ secret: process.env.AUTH_SECRET! })
.middleware(async ({ context, next }) => {
  const session = await context.auth.api
    .getSession({ headers: context.headers });
  return next({ context: { user: session?.user } });
})
Realtime without extra infrastructure

Stream updates over SSE with oRPC's event iterators and EventPublisher — an async generator in a .procedure() handler is all it takes to fan events out to subscribers, with resumable delivery via withEventMeta.

.procedure("post.live", (base) =>
  base
    .output(eventIterator(z.object({ id: z.number() })))
    .handler(async function* ({ signal }) {
      for await (const row of postEvents.subscribe(
        "created",
        { signal },
      )) {
        yield withEventMeta(row, { id: String(row.id) });
      }
    }),
)
Deploy to a VPS, or go serverless

pglite() ships a zero-infra embedded Postgres that writes to local disk — perfect for a VPS or Coolify box. Need serverless or edge? Swap in any Kysely Dialect (Neon, D1, Durable Objects) with the same builder chain — templates/cloudflare and templates/vercel-neon ship with a ready deploy script.

$ npm run deploy

# templates/cloudflare   -> wrangler deploy
# templates/vercel-neon  -> vercel deploy --prod

Your backend, your data, your infrastructure.

No hosted control plane, no proprietary APIs, no data leaving your own servers. Install the package, write your schema, and ship.