Skip to content

Getting started

Create an Outer server, define a schema, enable auth, add a procedure, and start with automatic migrations.

Updated View as Markdown

Outer is a TypeScript backend framework built on Kysely, oRPC, and Better Auth. You describe your database, auth, and API in one builder chain, and get back a fetch-compatible handler you can mount anywhere.

By default it runs on PGlite — real Postgres, embedded, writing to local disk — so you need no external services to start.

Before you begin

You need Node.js 20 or later and a package manager. The template installs everything else.

Create a server

Scaffold a project and start it:

npx giget@latest gh:ilhajs/outer/templates/minimal my-outer-app
cd my-outer-app
npm install
npm run dev

The server listens on http://localhost:3000. Call POST /rpc/user/me to check that it responds.

Pick a different template

Template What you get Command
minimal The smallest Outer server — new Outer(...).start() behind srvx, no frontend. npx giget@latest gh:ilhajs/outer/templates/minimal
ilha Full-stack starter: Outer as a Nitro server entry, Ilha + Vite on the frontend. npx giget@latest gh:ilhajs/outer/templates/ilha
cloudflare Outer on Cloudflare Workers — a Durable Object’s SQLite for data, R2 for uploads, instead of PGlite. npx giget@latest gh:ilhajs/outer/templates/cloudflare
vercel-neon Outer on Vercel Functions — Neon Postgres for data, Vercel Blob for uploads, instead of PGlite. npx giget@latest gh:ilhajs/outer/templates/vercel-neon

Write your first server

Every Outer app is one chain. Read it top to bottom: a schema version, auth, then the procedures your clients call. .start() builds the server and applies migrations in one step.

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

const v1_0 = schema("1.0.0")
  .auth() // Better Auth tables: user, session, account, verification
  .table("post", (t) => ({
    id: t.serial().primaryKey(),
    title: t.text(),
    userId: t.text().references("user", "id"),
  }))
  .build();

const server = await new Outer({ name: "My API", baseUrl: "http://localhost:3000", db: pglite() })
  .schema(v1_0)
  .auth({ secret: process.env.AUTH_SECRET! })
  .procedure("user.me", (base) => base.handler(({ context }) => context.user), {
    permission: "authenticated",
  })
  .start();

serve({ fetch: (req) => server.handle(req) });

This server exposes POST /rpc/user/me plus the Better Auth routes under /api/auth/**. .auth() resolves the session once per request, so context.user is already there — you never write a getSession middleware.

Order the chain correctly

Call the methods in this order: .schema().middleware().procedure().start() (or .build()). Each call types the next one, so a procedure only sees the tables and context fields declared above it.

.auth(), .openapi(), .mcp(), .admin(), .files(), and .use() can appear anywhere before .start() / .build().

Run migrations

.start() calls build() then migrateToLatest() and throws if migration fails. Prefer it for self-hosted apps:

const server = await new Outer({ db: pglite() }).schema(v1_0).start();

Use .build() when you need manual control — for example a deploy-time script, or logging the migration result:

const server = new Outer({ db: pglite() }).schema(v1_0).build();
const { error, results } = await server.migrator.migrateToLatest();
if (error) throw error;

On serverless, run migrations from a deploy script instead of the request path — see Deployment.

Project structure

my-outer-app/
├── src/
│   ├── schema.ts   # schema("1.0.0")...build() versions
│   └── index.ts    # await new Outer(...).schema(...).auth(...).procedure(...).start()
└── package.json

Keep schema versions in their own file. You add versions over time and never edit old ones — or derive the next version with .extend().

Next steps

  • Schema — define tables, columns, and relations
  • Migrations — register versions and apply them
  • Resources — generate CRUD for a table
  • Auth — Better Auth, sessions, and API keys
  • Permissions — guard procedures and resources
  • Procedures — write the endpoints CRUD does not cover
  • Plugins — package procedures, routes, and middleware
  • Database — query with Kysely and the Sola ORM
  • Deployment — pick a database and a host
Navigation

Type to search…

↑↓ navigate↵ selectEsc close