{"id":52050044,"url":"https://github.com/ilhajs/outer","last_synced_at":"2026-08-02T12:01:25.864Z","repository":{"id":368658024,"uuid":"1286037744","full_name":"ilhajs/outer","owner":"ilhajs","description":"The tiny backend you never knew you needed","archived":false,"fork":false,"pushed_at":"2026-07-24T08:54:25.000Z","size":1584,"stargazers_count":3,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-07-24T09:12:58.978Z","etag":null,"topics":["authentication","backend","better-auth","kysely","orm","orpc","pglite","postgresql","typescript","vanilla-typescript"],"latest_commit_sha":null,"homepage":"https://outer.now/","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ilhajs.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":"ROADMAP.md","authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2026-07-01T11:35:48.000Z","updated_at":"2026-07-24T08:54:50.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/ilhajs/outer","commit_stats":null,"previous_names":["ilhajs/outer"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/ilhajs/outer","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ilhajs%2Fouter","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ilhajs%2Fouter/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ilhajs%2Fouter/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ilhajs%2Fouter/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ilhajs","download_url":"https://codeload.github.com/ilhajs/outer/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ilhajs%2Fouter/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":36192499,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-07-20T02:08:10.276Z","status":"online","status_checked_at":"2026-08-02T02:00:06.915Z","response_time":58,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["authentication","backend","better-auth","kysely","orm","orpc","pglite","postgresql","typescript","vanilla-typescript"],"created_at":"2026-08-02T12:01:25.062Z","updated_at":"2026-08-02T12:01:25.849Z","avatar_url":"https://github.com/ilhajs.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cdiv align=\"center\"\u003e\n\n# Outer\n\n### The open-source backend for the agentic internet.\n\n**Define a procedure once — serve it as typed RPC, REST, an OpenAPI spec, and an MCP tool your agents can call.** Real Postgres with pgvector, running on the same box as your app. No hosted control plane, no per-project pricing, nothing leaving your machine.\n\n`Self-hosted` · `MIT licensed` · `Alpha`\n\n\u003c/div\u003e\n\nOuter is an open-source alternative to Supabase, PocketBase, and Firebase where **you own 100% of the solution and the data**. One TypeScript builder chain gives you a Postgres database, auth, typed RPC, auto-generated CRUD with row-level permissions, file uploads, migrations, realtime, OpenAPI, and an MCP server — compiled into a single fetch-compatible handler you can drop on a $5 VPS, Coolify, Cloudflare Workers, or Vercel.\n\nIt's built on pieces you already trust — [Kysely](https://kysely.dev), [oRPC](https://orpc.unnoq.com), [Better Auth](https://better-auth.com), and [PGlite](https://pglite.dev) — instead of reinventing them. `.outer/pglite` is a folder you own; there is no dashboard between you and your data.\n\n```bash\nnpx giget@latest gh:ilhajs/outer/templates/minimal my-outer-app\n```\n\n## A complete backend, from one file\n\n```ts\nimport { Outer } from \"@outerjs/server\";\nimport { pglite } from \"@outerjs/server/pglite\";\nimport { schema } from \"@outerjs/server/schema\";\nimport { fromSchema } from \"@outerjs/server/secrets\";\nimport { fromUnstorage } from \"@outerjs/server/storage\";\nimport { serve } from \"srvx\";\nimport { createStorage } from \"unstorage\";\nimport fsLite from \"unstorage/drivers/fs-lite\";\nimport { z } from \"zod\";\n\n// Validate env once; read typed values via `context.secrets` anywhere — no more `process.env.X!`\nconst secrets = fromSchema(\n  z.object({\n    AUTH_SECRET: z.string(),\n    BASE_URL: z.string().default(\"http://localhost:3000\"),\n  }),\n  process.env,\n);\n\n// Versioned schema — drives migrations, query types, and endpoint validation\nconst v1_0 = schema(\"1.0.0\")\n  .auth() // Better Auth tables (user, session, account, verification) + admin fields\n  .table(\"post\", (t) =\u003e ({\n    id: t.serial().primaryKey(),\n    title: t.text(),\n    body: t.text().nullable(),\n    userId: t.text().references(\"user\", \"id\"),\n  }))\n  .files({ attachTo: [\"post\"] }) // `file` metadata table + a `post_file` pivot\n  // Relations power `include` — e.g. context.db.query.user.findMany({ include: { post: true } })\n  .relation(\"user\", (rel) =\u003e rel.hasMany(\"post\", { from: \"id\", to: \"userId\" }))\n  .relation(\"post\", (rel) =\u003e rel.belongsTo(\"user\", { from: \"userId\", to: \"id\" }))\n  .build();\n\nconst outer = new Outer({\n  name: \"My API\",\n  baseUrl: secrets.get(\"BASE_URL\"),\n  db: pglite(), // embedded Postgres + pgvector; swap for any Kysely Dialect\n  cors: { origins: [\"https://app.example.com\"], credentials: true },\n  storage: fromUnstorage(createStorage({ driver: fsLite({ base: \".outer/files\" }) })),\n  kv: createStorage(), // context.kv — any unstorage driver (Redis, Cloudflare KV, Vercel Runtime Cache, …)\n  secrets, // surfaced as context.secrets\n  rateLimit: { max: 100, windowMs: 60_000 }, // per-caller on /rpc + /rest\n})\n  .schema(v1_0)\n  .auth({ secret: secrets.require(\"AUTH_SECRET\") }) // sign-up, sessions, social — /api/auth/**\n  .openapi() // GET /openapi.json + a plain-JSON REST surface at /rest/**\n  .admin() // schema introspection + table CRUD at /rpc/_admin/**, admin-gated\n  .files() // upload / download / attach + GET /files/:id, private to the uploader\n  .resource(\"post\", {\n    // six typed CRUD endpoints with row-level permissions\n    permissions: { list: \"public\", create: \"authenticated\", update: \"owner\", delete: \"owner\" },\n    ownerColumn: \"userId\", // auto-filled on create, enforced on owner checks\n  })\n  .procedure(\"post.search\", (base) =\u003e\n    // your own typed RPC — Zod-validated input, Prisma-style reads on context.db\n    base\n      .input(z.object({ q: z.string() }))\n      .handler(({ input, context }) =\u003e\n        context.db.query.post.findMany({ where: { title: { contains: input.q } }, take: 20 }),\n      ),\n  )\n  .build();\n\nawait outer.migrator.migrateToLatest();\nserve({ fetch: (req) =\u003e outer.handle(req) }); // outer.handle is a plain Fetch handler\n```\n\nThat's validated env secrets, auth, six CRUD endpoints for `post` with ownership enforced, file uploads, an admin API, an OpenAPI spec, per-caller rate limiting, a custom search procedure over the Prisma-style query API, and versioned migrations — backed by embedded Postgres that writes to local disk, with **zero infrastructure to run**.\n\n## Why developers pick Outer\n\n|                 | A hosted BaaS                                                               | Outer                                                                                                       |\n| :-------------- | :-------------------------------------------------------------------------- | :---------------------------------------------------------------------------------------------------------- |\n| **Your data**   | Lives in someone else's database, behind their dashboard and their billing. | Lives in your Postgres, on your infra. `.outer/pglite` is a folder you own.                                 |\n| **Your auth**   | A black box you configure through a settings UI.                            | [Better Auth](https://better-auth.com) — real code you read, extend, and call directly from `context.auth`. |\n| **Your client** | Generated after the fact, and quietly drifts from your schema.              | Inferred straight from your server. If it compiles, it matches.                                             |\n| **Scaling**     | Means picking a new pricing plan past the free tier.                        | Means giving the box you already pay for more CPU and RAM.                                                  |\n\n## One router, four surfaces\n\nDefine a procedure once. Outer serves it four ways, with no second definition to keep in sync:\n\n- **Typed RPC** at `/rpc/**` — the wire protocol `@outerjs/sdk` speaks, end to end.\n- **REST + OpenAPI** — a spec-accurate plain-JSON surface at `/rest/**`, plus `GET /openapi.json`.\n- **MCP tools** at `/mcp` — `.mcp()` hands agents your _shipped business logic_, not raw database access, inheriting the exact permissions your app already enforces. (`post.search` becomes the `post_search` tool.)\n\nAdd `.admin()` for a self-describing admin API — schema introspection, migration status, and table CRUD — ready for a dashboard to drive.\n\n## What one chain gives you\n\nWith zero extra setup:\n\n- **Real Postgres, embedded** — [PGlite](https://pglite.dev) is actual Postgres in your process (not SQLite pretending), with **pgvector bundled in** for vector search on a $4 box. Prefer Neon, Durable Objects, or network Postgres? Pass any Kysely `Dialect` and the whole chain is unchanged.\n- **A typed `context.db`** — Kysely for writes, a Prisma-style read API (`findMany`, `where` operators, `include`, cursor `paginate`) via `context.db.query`, and `context.db.transact()` for transactions that span both.\n- **Auto-generated CRUD** per table via `.resource()`, with per-action permissions — `public` / `authenticated` / `admin` / `owner` / your own function — plus field-level write control (`writable` / `readonly`) so a client can never spoof a server-managed column.\n- **File uploads** via `.files()` — typed `file.upload` and a `GET /files/:id` route, private to the uploader by default (a 404, never a 403), bytes in unstorage / S3·R2 / Vercel Blob and only metadata in Postgres. Downloads are hardened against stored XSS out of the box.\n- **Realtime, no broker** — an async generator in a `.procedure()` streams over SSE with resumable delivery, and `context.db.query.\u003ctable\u003e.live()` turns any read into a reactive stream on PGlite.\n- **Schema-driven migrations** — versioned, diffed, and applied from your `schema()`.\n\n## Schema to SSR, no HTTP hop\n\n`outer.client()` calls your procedures in-process during server rendering — same types, no serialization, no localhost round-trip:\n\n```ts\n// In a Server Component / server function, in the same process as Outer:\nconst api = outer.client(() =\u003e headers()); // sees the caller's session, runs permission checks\nconst posts = await api.post.list();\n```\n\nOn the client, `@outerjs/sdk` gives you a fully typed RPC + auth client in one call — every `.procedure()`'s input and output flows to your frontend, with **no codegen step and no SDK to regenerate**:\n\n```ts\nimport { createClient } from \"@outerjs/sdk\";\nimport type { InferRouter } from \"@outerjs/server\";\nimport type { outer } from \"./server\";\n\nexport const client = createClient\u003cInferRouter\u003ctypeof outer\u003e\u003e({\n  baseUrl: \"http://localhost:3000\",\n})\n  .auth()\n  .build();\n\nawait client.hello(); // \"world\" — typed. Rename it on the server and this turns red.\n```\n\nBecause `outer.handle(request)` is a plain `(Request) =\u003e Promise\u003cResponse\u003e`, it mounts unchanged into Bun, Node, srvx, Nitro, Hono, H3, or Next.js API Routes.\n\n## Deploy anywhere\n\nThe `pglite()` default writes to local disk, so any persistent host (VPS, Coolify, a long-lived process) is a zero-infra deploy. On serverless/edge, swap in a Kysely dialect — the templates show both paths, and heavy or platform-specific pieces are optional peers, so a Workers deploy never downloads PGlite's WASM.\n\n| Template      | Stack                                                                                           | Scaffold                                                        |\n| ------------- | ----------------------------------------------------------------------------------------------- | --------------------------------------------------------------- |\n| `minimal`     | Bare Outer server behind [srvx](https://srvx.h3.dev)                                            | `npx giget@latest gh:ilhajs/outer/templates/minimal my-app`     |\n| `ilha`        | Full-stack: Outer in a [Nitro](https://nitro.build) entry + [Ilha](https://ilha.build) frontend | `npx giget@latest gh:ilhajs/outer/templates/ilha my-app`        |\n| `cloudflare`  | Cloudflare Workers — Durable Object SQLite for data, R2 for uploads                             | `npx giget@latest gh:ilhajs/outer/templates/cloudflare my-app`  |\n| `vercel-neon` | Vercel functions — [Neon](https://neon.tech) Postgres for data, Vercel Blob for uploads         | `npx giget@latest gh:ilhajs/outer/templates/vercel-neon my-app` |\n\n## Documentation\n\n- [SPEC.md](./SPEC.md) — the full API reference: builder chain, schema and migrations, resource permissions, the Sola query API, realtime, MCP, type extraction.\n- [ROADMAP.md](./ROADMAP.md) — what's shipped in Outer and [Outer Hub](https://hub.outer.now), and what's coming next.\n- Guides and API reference on the website (`apps/website`).\n\n## Repo layout\n\nA Bun workspace monorepo:\n\n| Path              | Description                                                                                              |\n| ----------------- | -------------------------------------------------------------------------------------------------------- |\n| `packages/server` | `@outerjs/server` — Outer's core                                                                         |\n| `packages/sdk`    | `@outerjs/sdk` — type-safe client (oRPC + Better Auth)                                                   |\n| `templates/*`     | Deployable starters (see table above)                                                                    |\n| `apps/hub`        | `@outerjs/hub` — admin dashboard ([hub.outer.now](https://hub.outer.now)) that drives the `.admin()` API |\n| `apps/website`    | Documentation website                                                                                    |\n\n## Development\n\n```bash\nbun install\nbun run build   # builds every package\nbun run test    # runs every package's test suite\nbun run lint    # oxlint\nbun run fmt     # oxfmt\n```\n\n## License\n\nMIT — no telemetry, nothing phoning home. The whole thing runs on hardware you already pay for.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Filhajs%2Fouter","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Filhajs%2Fouter","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Filhajs%2Fouter/lists"}