Deployment

BuiltOuter.handle(request: Request): Promise<Response> is a plain Fetch API handler, so Outer mounts as the server entry for any framework that speaks fetch — Nitro, Hono, H3, Next.js API Routes, etc.

The zero-infra default: pglite()

For persistent-hosting deployments (VPS, Coolify), import the pglite() helper from the /pglite subpath:

import { Outer } from "@outerjs/server";
import { pglite } from "@outerjs/server/pglite";

new Outer({ db: pglite() }); // or pglite({ dataDir: "..." }), defaults to <cwd>/.outer/pglite

PGlite is real Postgres, running embedded and writing to local disk — no external infra to run. This is the path to reach for first; it's what makes Outer deployable to a VPS/Coolify box with nothing else to provision. Splitting it into a subpath keeps PGlite's WASM out of deploy bundles for platforms where it's dead weight (Cloudflare Workers, Vercel Functions).

Because PGlite writes to local disk, the host needs a persistent, writable filesystem across requests. This works on a VPS, Coolify, or any long-lived Node process — it does not work on serverless/edge platforms unless you swap in a different dialect.

Custom dialects for serverless/edge

For platforms without a persistent filesystem (Vercel Functions, Cloudflare Workers), or to point at an existing database, pass any Kysely Dialect directly:

import { D1Dialect } from "kysely-d1"; // or PostgresDialect, MysqlDialect, kysely-durable-objects, etc.

new Outer({
  db: { dialect: new D1Dialect({ database: env.DB }), kind: "sqlite" },
});

kind tells Outer which SQL dialect family to generate DDL for (column types like serial/jsonb/uuid don't exist in SQLite, so they're remapped) and which constraint-error codes to recognize when turning DB errors into CONFLICT/BAD_REQUEST responses.

Currently supported:

  • "postgres" — PGlite, or any network Postgres via PostgresDialect
  • "sqlite" — Cloudflare D1, Durable Objects via kysely-durable-objects, libSQL/Turso, etc.

Kysely also ships mysql/mssql dialects, and Better Auth supports them — but Outer doesn't generate correct DDL for them yet, so kind is deliberately typed to just the two verified options.

Verified templates

  • templates/cloudflare — Cloudflare Workers with Durable Objects
  • templates/vercel-neon — Vercel Functions with Neon Postgres

Both ship with a deploy script:

$ npm run deploy

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

Embedding in a host framework

The handler itself is host-agnostic. Export whatever shape the host expects and delegate to outer.handle:

// e.g. Nitro server entry (see templates/ilha)
export default { fetch: (req: Request) => outer.handle(req) };

Use .middleware() to pull the host runtime's own utilities into context, alongside context.db/context.auth, so they're available in every .procedure():

import { useStorage } from "nitro/storage";
import { runTask } from "nitro/task";

const outer = new Outer(...)
  .schema(v1_0)
  .auth({ secret: useRuntimeConfig().authSecret })
  .middleware(async ({ context, next }) => {
    const kv = useStorage();
    return next({ context: { kv, runTask } });
  })
  .procedure("foo", (base) =>
    base.handler(async ({ context }) => {
      await context.kv.setItem("foo", "bar");
      return { foo: await context.kv.getItem("foo") };
    }),
  )
  .build();

This is the same pattern regardless of host — swap nitro/storage/nitro/task for whatever the framework provides (Cloudflare bindings, Next.js headers(), etc.).

HTTP routes

MethodPathHandler
GET/openapi.jsonOpenAPI 3.x spec (only mounted when .openapi({ enabled: true }) was called)
ALL/api/auth/**Better Auth handler (only mounted when .auth() was called)
ALL/rpc/**oRPC handler (prefix /rpc)

Roadmap

Alpha focuses on persistent-hosting deployments (VPS, Coolify) with pglite() as the recommended default. One thing is planned next:

  • Admin dashboard/UI — comparable to PocketBase's dashboard or Supabase Studio. Should expose: table data browser with CRUD, user/session management, and migration status. Planned as a separate outer-admin package served at /admin when enabled.

Serverless/edge support is no longer blocked — db: { dialect, kind } lets you swap PGlite for a network-attached Postgres or a "sqlite"-family dialect, with working, verified templates for both. mysql/mssql kinds still aren't implemented (Kysely ships dialects for both, but Outer's DDL generation and error mapping don't cover them).