Own your backend. Outer is an open-source alternative to Supabase, PocketBase, and Firebase where you own 100% of the solution and the data — no hosted control plane, no per-project pricing, nothing leaving your box. One TypeScript builder chain gives you a real Postgres database, auth, typed RPC, auto-generated CRUD with row-level permissions, migrations, realtime, and OpenAPI — compiled into a single fetch-compatible handler you can deploy to a $5 VPS, Coolify, Cloudflare Workers, or Vercel.
Built on proven pieces — Kysely, oRPC, Better Auth, and PGlite — instead of reinventing them.
npx giget@latest gh:ilhajs/outer/templates/minimal my-outer-appOr from scratch — this is a complete backend:
import { Outer, schema } from "@outerjs/server";
import { pglite } from "@outerjs/server/pglite";
import { serve } from "srvx";
const v1_0 = schema("1.0.0")
.table("post", (t) => ({
id: t.serial().primaryKey(),
title: t.text(),
body: t.text().nullable(),
userId: t.text(),
}))
.build();
const outer = new Outer({ name: "My API", baseUrl: "http://localhost:3000", db: pglite() })
.schema(v1_0)
.auth({ secret: process.env.AUTH_SECRET! })
.openapi()
.resource("post", {
permissions: { create: "authenticated", update: "owner", delete: "owner" },
ownerColumn: "userId",
})
.procedure("hello", (base) => base.handler(() => "world"))
.build();
await outer.migrator.migrateToLatest();
serve({ fetch: (req) => outer.handle(req) });With zero extra setup, that's:
- A local Postgres database (PGlite — real embedded Postgres, not SQLite pretending), schema-driven migrations, and a typed
context.db(Kysely + a Prisma-style read API) - Auth — sign-up, sign-in, sessions, social providers — via Better Auth at
/api/auth/** - Auto-generated CRUD procedures per table via
.resource(), with per-action permissions:public/authenticated/admin/owner/ your own function - Type-safe RPC at
/rpc/**, plus opt-in OpenAPI (/openapi.json) with a spec-accurate plain-JSON surface at/rest/** - Realtime streaming (SSE) via oRPC event iterators — no extra infrastructure
And because outer.handle(request) is a plain Fetch handler, it mounts unchanged into Bun, Node, srvx, Nitro, Hono, H3, or Next.js API Routes.
Pair it with @outerjs/sdk on the client for a fully typed RPC + auth client in one call — every .procedure()'s input and output flows to your frontend:
import { createClient } from "@outerjs/sdk";
import type { InferRouter } from "@outerjs/server";
import type { outer } from "./server";
type Router = InferRouter<typeof outer>;
export const client = createClient<Router>({
baseUrl: "http://localhost:3000",
})
.auth()
.build();
await client.hello(); // "world" — typedThe pglite() default writes to local disk, which makes persistent hosts (VPS, Coolify, any long-lived process) a zero-infra deploy. On serverless/edge, swap in any Kysely dialect — the templates show both paths:
| Template | Stack | Command |
|---|---|---|
minimal |
Bare Outer server behind srvx | npx giget@latest gh:ilhajs/outer/templates/minimal my-app |
ilha |
Full-stack: Outer in a Nitro entry + Ilha frontend | npx giget@latest gh:ilhajs/outer/templates/ilha my-app |
cloudflare |
Cloudflare Workers, backed by a Durable Object's SQLite storage | npx giget@latest gh:ilhajs/outer/templates/cloudflare my-app |
vercel-neon |
Vercel serverless functions, backed by Neon Postgres | npx giget@latest gh:ilhajs/outer/templates/vercel-neon my-app |
Heavy or platform-specific dependencies are optional peers, so a Workers deploy never downloads PGlite's WASM and a server that skips .openapi() never installs the OpenAPI toolchain.
- SPEC.md — the full API reference: builder chain, schema and migrations, resource permissions, the Sola query API, realtime, type extraction
- Guides on the website: getting started and deployment (
apps/website)
This is a Bun workspace monorepo:
| Path | Description |
|---|---|
packages/server |
@outerjs/server — Outer's core |
packages/sdk |
@outerjs/sdk — type-safe client (oRPC + Better Auth) |
templates/* |
Deployable starters (see table above) |
apps/website |
Documentation website |
bun install
bun run build # builds every package
bun run test # runs every package's test suite
bun run lint # oxlint
bun run fmt # oxfmtMIT