# Run a full stack

App, database and twins together, for browser and end-to-end work.

For a complete browser application with login, sessions and signed callbacks, start with [test an app in the browser](./test-in-the-browser.md) and its [example source](../../cookbook/browser-signup/README.md). The HTTP script below explains the service mechanics; it does not drive a browser. The previous recording is historical and is not evidence for these current instructions.

A world can own more than twins. Your app itself, a real local Postgres, a Redis: anything the
app needs running is a service in `world.json`, brought up and torn down together, with each
service's connection details in the env the next service sees.

## The app

A server whose `/signup` creates a Stripe customer the way production code would, and an
end-to-end script that drives the server, not the twin.

```json file=package.json
{ "name": "acme-web", "private": true, "dependencies": { "stripe": "17.7.0" }, "devDependencies": { "@volter/world": "3.0.63", "@volter/twin-stripe": "3.0.1" } }
```

```text file=.env.example
STRIPE_SECRET_KEY=
```

```js file=server.mjs
import { createServer } from 'node:http';
import Stripe from 'stripe';

const stripe = new Stripe(process.env.STRIPE_SECRET_KEY);

createServer(async (req, res) => {
  if (req.url === '/health') { res.end('ok'); return; }
  if (req.url === '/signup') {
    const customer = await stripe.customers.create({ email: 'ada@example.com', name: 'Ada' });
    res.setHeader('content-type', 'application/json');
    res.end(JSON.stringify({ customerId: customer.id }));
    return;
  }
  res.statusCode = 404; res.end('not found');
}).listen(Number(process.env.PORT), '127.0.0.1');
```

```js file=e2e.mjs
const res = await fetch(`${process.env.APP_URL}/signup`);
const body = await res.json();
if (!/^cus_/.test(body.customerId)) { console.error(body); process.exit(1); }
console.log(`signed up ${body.customerId}`);
```

```bash
npm install
npx volter world init --name acme-web --twins stripe
```

## The app as a service

`init` wrote the stripe twin into `.volter/world.json`. Add the app as a `process` service:
what to run, the env name its URL is exported under, and how the world knows it is ready.

```json file=.volter/world.json
{
  "id": "acme-web",
  "env": { "STRIPE_SECRET_KEY": "$issue:stripe" },
  "services": [
    { "id": "stripe", "type": "twin", "package": "@volter/twin-stripe", "version": "3.0.1", "port": "auto", "injectEnv": "STRIPE_TWIN_URL" },
    { "id": "app", "type": "process", "command": "node", "args": ["server.mjs"], "port": "auto",
      "injectEnv": "APP_URL", "portArg": false, "rootArg": false, "ready": { "httpUrl": "${url}/health" } }
  ]
}
```

Services start in order, and each one receives the env the earlier ones produced, so the app
starts after its twin is ready and inherits `STRIPE_TWIN_URL`. A twin declared after co-located
twins starts as soon as their addresses are known, since a twin calls nothing while it starts. With a readiness probe, `up` waits until
the app answers, not merely until it binds.

```bash
npx volter world up
```

```text
acme-web  1 twin up, story loaded
  stripe: http://127.0.0.1:56314
  app: http://127.0.0.1:56320
```

## Drive the app end to end

```bash
npx volter world run -- node e2e.mjs
```

```text
signed up cus_twin_1
```

The script talked only to the app. The app talked to Stripe, and the write is in the log:

```bash
npx volter world log
```

```text
stripe customer.create customer:cus_twin_1
```

```bash
npx volter world down
```

## A real database

A real local tool is an `external` service: the world runs its `up`, `status` and `down`, waits
for it to be ready, and reads its connection details into the env:

```json
{
  "id": "db", "type": "external",
  "external": {
    "up":     ["./dev/db", "up"],
    "status": ["./dev/db", "status", "--json"],
    "down":   ["./dev/db", "down"],
    "readyWhen": { "command": "./dev/db", "args": ["ready"] },
    "discover": [{ "as": "DATABASE_URL", "jsonPath": "url" }]
  }
}
```

`npx volter world init` emits this shape for a Postgres, MySQL, Redis or MongoDB it detects in your env
names, with a definition the world manages. The world serves a Redis without a container: the redis twin speaks
Redis's own protocol on the declared port, so `ioredis`, `node-redis` and BullMQ connect unmodified, and
its keys are the world's state, branched and reset with it. A Postgres runs in a container, or,
where there is no container runtime, as the machine's own PostgreSQL (a `postgres` server on PATH,
or `VOLTER_WORLD_POSTGRES_BIN`), else through PGlite. MongoDB runs its own `mongod` server from
PATH, loopback only, with its data kept under the world's state directory. The Supabase twin hands
the application that managed Postgres connection. Your migrations and native Postgres client use
the same database. Supabase's Management API, PostgREST, Storage and Auth are unsupported;
installing the twin does not make Supabase SDK calls work. Apply your application's schema through
its migrations; the twin does not install Supabase's Auth or Storage schemas.

With no container runtime and no PostgreSQL installed, the world serves that Postgres with real
Postgres compiled to WASM (PGlite), behind a wire-protocol listener on the same port, with the same env. MySQL needs a
container runtime and is refused without one.

MongoDB needs its own server binary, not a container or an extra twin package. The
[MongoDB example](../../cookbook/mongodb-backing/README.md) gives a pinned installation and an executed
world lifecycle. The `mongodb` driver and mongoose use the injected connection URL unchanged.
The server supplies its own queries, indexes and stored data. It starts as one standalone node;
add `command: ["mongod", "--replSet", "world"]` to the definition's MongoDB service when the application
needs transactions or change streams. The world initializes that one member and waits for its
primary. Authentication, TLS, multiple members, failover, sharding and Atlas services are not
configured. Database time follows the machine. A branch copies a stopped world's current data;
reset starts fresh data. Database writes have no world log, checkpoint, push or deploy.

What the PGlite Postgres does and does not do:

- **Extensions.** Every contrib extension PGlite ships and pgvector are available, so migrations'
  `CREATE EXTENSION IF NOT EXISTS pgcrypto | citext | "uuid-ossp" | unaccent | pg_trgm |
  btree_gist | hstore | ltree | fuzzystrmatch | vector | …` work. Others (postgis, pg_cron,
  timescaledb) fail with Postgres's "is not available" error.
- **One declared database and login.** Use the database name, username and throwaway password
  configured in the World's database definition. An undeclared database is refused with `3D000`,
  and incorrect credentials with `28P01`. `current_database()` names the declared database. `CREATE DATABASE` always fails:
  for the database your connection named it answers `42P04` ("already exists", which is true, so
  `rails db:create` proceeds), and for any other name `0A000` (not supported). Nothing can make a
  second, separate database, so a Prisma shadow database (`prisma migrate dev`) or a test runner's
  `test_<name>` database needs a container runtime; `prisma migrate deploy` does not.
- **No bulk load over COPY.** `COPY … FROM STDIN` (`psql \copy`, `pg_restore` data, copy
  streams) is refused with `0A000`; load rows with `INSERT`. `COPY … TO STDOUT` works.
- **One serialized session.** Connections take turns, and an open transaction blocks the others
  until it ends. They share one session: `SET`, temp tables and SQL `PREPARE`d statements leak
  between connections, session advisory locks do not exclude each other, and `LISTEN`/`NOTIFY` does
  not reach across connections. A statement a driver prepares over the protocol stays its
  connection's own, even when two connections pick the same name (Prisma's `s0`), and its own SQL
  `EXECUTE` or `DEALLOCATE` of that name reaches it.

## Browser tests

The browser is not a Node process, so the injector does not reach it. Two ways in:

- **Server-side calls.** Most apps call vendors from the server. The server runs inside the world
  and is redirected; the browser talks only to your app, as the end-to-end script above did.
- **Browser-side calls.** Put the browser proxy shipped with the kernel in front of the app, so
  the browser's SDK calls share the same twins. Configure the browser with the World's proxy
  (`VOLTER_WORLD_PROXY`) and trust its session CA (`VOLTER_WORLD_CA`); a Node injector alone
  does not redirect requests made inside a browser. [Load a named browser](./seed-and-reset.md#load-a-named-browser-locally)
  shows how to use the World's saved browser state and proxy. This needs no platform source checkout.

Then run the browser suite inside the world: `npx volter world run -- npx playwright test`.

## What is real here

The Clerk twin issues RS256 tokens against a real JWKS, and the S3 twin verifies SigV4 signatures.
Native SQL queries run in the World's Postgres. These mechanisms do not establish support for every
vendor authentication or permission flow; check the selected release's limitations. Generative
twins return labeled deterministic stubs; assert on the calls and state changes, not the prose.

## Runnable examples

The [cookbook](../../cookbook/README.md) holds complete stacks you can copy: an on-call agent across GitHub, Jira,
Slack and OpenAI, and open-source applications (Postiz, Twenty) served whole in a World.

<!-- Fenced tutorial executor: packages/cli/src/journeys/tutorial.ts. Execution scope is recorded separately. -->
