---
title: "Introduction"
description: "Create an Outer server, define a schema, enable auth, add a procedure, and start with automatic migrations."
---

> Documentation Index
> Fetch the complete documentation index at: https://outer.now/llms.txt
> Use this file to discover all available pages before exploring further.

# Introduction

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](https://pglite.dev) — 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:

```bash
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](https://srvx.h3.dev), no frontend.                        | `npx giget@latest gh:ilhajs/outer/templates/minimal`     |
| `ilha`        | Full-stack starter: Outer as a [Nitro](https://nitro.build) server entry, [Ilha](https://ilha.build) + 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](https://neon.tech) 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.

```ts
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"),
  }))
  .relation("user", (rel) => rel.hasMany("post", { from: "id", to: "userId" }))
  .relation("post", (rel) => rel.belongsTo("user", { from: "userId", to: "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:

```ts
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:

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

| Call       | What it does                                                 | Prefer when                                   |
| ---------- | ------------------------------------------------------------ | --------------------------------------------- |
| `.start()` | `build()` + `migrateToLatest()`, throws on migration failure | Self-hosted boot                              |
| `.build()` | Finalizes the chain only — you run migrations yourself       | Deploy scripts, logging `results`, serverless |

On serverless, run migrations from a deploy script instead of the request path — see [Deployment](/outer/deployment).

## Call it from a client

After the server is up, use [`@outerjs/sdk`](/getting-started/client) in the browser, or `server.client(headers)` during SSR — both share `InferRouter` types.

## Project structure

```txt
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()`](/schema/defining-schema#extend-a-previous-version).

## Next steps

- [Client](/getting-started/client) — typed `@outerjs/sdk` and `BuiltOuter.client()`
- [`new Outer()`](/outer/server) — constructor options and what `.start()` returns
- [`schema()`](/schema/defining-schema) — define tables, columns, and relations
- [Migrations](/schema/migrations) — register versions and apply them
- [`.resource()`](/outer/resource) — generate CRUD for a table
- [`.auth()`](/outer/auth) — Better Auth, sessions, and API keys
- [Permissions](/outer/permissions) — guard procedures and resources
- [`.procedure()`](/outer/procedure) — write the endpoints CRUD does not cover
- [`.use()`](/outer/use) — package procedures, routes, and middleware
- [Database](/outer/database) — query with Kysely and the Sola ORM
- [`new Outer()`](/outer/server) — CORS, rate limits, health, `onError`
- [Deployment](/outer/deployment) — pick a database and a host

Source: https://outer.now/getting-started/introduction/index.mdx
