Database & migration
Goal: take a freshly scaffolded NetScript workspace and stand up its database — bring up the
AppHost, create the first migration, generate the typed Prisma client, seed it, and confirm the
schema is applied — using only the public netscript db commands.
This recipe uses Postgres, the recommended default and the engine every tutorial builds on. The
database is polyglot, though: NetScript scaffolds a Prisma-backed database for four engines, chosen
at scaffold time with --db — netscript init my-app --db postgres|mysql|mssql|sqlite. Postgres,
MySQL, and MSSQL come up as Aspire container resources; SQLite is file-backed and needs no
container. The migration loop below is identical on every engine; only the provisioning differs.
This is a task-oriented recipe. The single most important fact, and the one most people trip over:
netscript db commands provision and talk to Postgres through Aspire. Aspire is step 2, not
an afterthought. You bring the database up first with cd aspire && aspire start, then the db
commands have something to connect to. Skip that ordering and db init fails with a
resident-AppHost diagnostic before it ever reaches Prisma. Everything below assumes the default
Aspire layout; the --no-aspire escape hatch is called out where it differs.
What you'll end up with
By the end of this recipe you will have a running Postgres container, a first migration on disk and
applied, a generated Prisma client (with matching zod schemas) under
database/postgres/schema/.generated/, seeded baseline rows, and a green netscript db status. The
whole loop is four commands, run once, against a database that Aspire is keeping alive in a second
terminal.
| Name | Type | Description |
|---|---|---|
aspire start |
from aspire/ |
Provisions the Postgres + Redis containers. Must be up first and stay running. |
netscript db init --name init |
from workspace root |
Initializes tooling, creates the first migration from schema.prisma, applies it to Postgres. |
netscript db generate |
from workspace root |
Generates the Deno-runtime Prisma client + zod schemas into .generated. Re-run after every schema edit. |
netscript db seed |
from workspace root |
Runs database/postgres/scripts/seed.ts to populate baseline rows. |
netscript db status |
from workspace root |
Reports which migrations are applied and whether the database is in sync — the authoritative verification. |
Before you start
| Name | Type | Description |
|---|---|---|
NetScript workspace |
netscript init … |
A workspace scaffolded with a database. This recipe uses --db postgres (the recommended default), which lays down database/postgres/ with a Prisma schema, prisma.config.ts, and seed scripts. Swap --db postgres for mysql, mssql, or sqlite for a different engine — the workflow is the same, only the database/ |
netscript CLI |
on PATH |
Install with deno install --global --allow-all --name netscript jsr:@netscript/cli@0.0.7 — then netscript --help should print. |
Aspire CLI |
aspire |
Used to provision Postgres and Redis as containers via Docker. Confirm with aspire --version. Docker/Podman must be running. |
Deno |
2.x |
deno --version. Prisma generation runs under the Deno runtime (the schema sets runtime="deno"). |
If you scaffolded with --no-aspire, you are responsible for pointing the workspace at your own
Postgres via POSTGRES_URI / DATABASE_URL (see Deploy);
there is no AppHost to start, so you skip Step 2, but the migration commands in Step 3 are otherwise
identical and talk to whatever database those variables resolve to.
Run every command from your workspace root unless a step says otherwise. To target a workspace other
than the current directory, pass --project-root <path> — useful in CI or when scripting against a
checkout you are not cd-ed into.
Step 1 — Confirm the database wiring
A scaffolded workspace already contains the database workspace at database/postgres/. You do not
author any of this by hand; the point of this step is to recognize the moving parts so the later
commands make sense:
my-app/
├── appsettings.json # NetScript.Databases.postgres — Engine/Mode/DatabaseName
├── netscript.config.ts # databases.config is intentionally empty here
└── database/postgres/
├── schema/
│ ├── schema.prisma # root: generator client (runtime="deno"), generator zod, datasource db
│ ├── plugins/ # per-plugin .prisma models aggregated here
│ │ ├── workers/workers.prisma
│ │ ├── sagas/sagas.prisma
│ │ └── triggers/triggers.prisma
│ └── .generated/ # created by `db generate`: Prisma client + zod
├── prisma.config.ts # schema dir + migrations path + datasource url
├── migrations/ # created by `db init`: timestamped migration dirs
└── scripts/ # migrate.ts, seed.ts, generate-zod.ts, …
Two things worth internalizing before you run anything:
- The datasource is Postgres, driven by
appsettings.json— not bynetscript.config.ts. In the scaffold,netscript.config.tshasdatabases: { config: [] }(empty). The real connection details live inappsettings.jsonunderNetScript.Databases.postgres(Engine: "Postgres",Mode: "Container", a generatedDatabaseName, and a persistentDataPathof.data/postgres). Aspire reads that block to provision the container, andprisma.config.tsresolves its datasource URL from the same source. If you ever wonder "where does the connection string come from?", the answer is this block, not the config file. - Plugin schemas are split out and aggregated. Each first-party plugin ships its own
database/<plugin>.prisma; those are aggregated underdatabase/postgres/schema/plugins/<plugin>/. So after adding theworkers,sagas, andtriggersplugins you'll see their tables (e.g.model JobDefinitionfrom workers) join the rootExampleRecordmodel in the same Postgres datasource. One migration covers them all.
Step 2 — Bring up Postgres with Aspire (do this first)
From the workspace root, change into the isolated Aspire folder and start the AppHost. The
TypeScript AppHost (aspire/apphost.mts) lives in aspire/ to keep its Node graph separate from
the Deno workspace, so the commands run from there:
cd aspire
aspire restore # one-time: downloads the AppHost SDK modules
aspire start # provisions Postgres + Redis containers and starts the resource graph
Leave aspire start running in this terminal. It provisions both the Postgres database and the
Redis cache (your KV/queue backend) as Docker containers — no manual docker run and no local
Postgres install required. When it settles you'll have:
- The Aspire dashboard at https://localhost:18888 — the access token
is printed in the
aspire startoutput. Open it and confirm thepostgresandredisresources are green. - Resources named
postgres,redis, and the per-capability services/processors (workers-api,workers,sagas-api,sagas,triggers-api,triggers). If you also scaffolded auth and streams, you'll seeauth-apiand the streams service join the graph on their assigned ports.
Step 3 — Run the migration workflow
Open a second terminal at the workspace root (keep aspire start going in the first). These
commands address the already-running Postgres through an explicit resident AppHost resource; they do
not create a parallel AppHost or database container. Run the workflow in order:
# Create the first migration from the current Prisma schema and apply it.
# --name labels the migration directory (here: a migration called "init").
netscript db init --name init
# Generate the Deno-runtime Prisma client (and the zod schemas) into
# database/postgres/schema/.generated so your code can import typed models.
netscript db generate
# Run the workspace seed scripts (database/postgres/scripts/seed.ts)
# to populate baseline rows.
netscript db seed
# Show migration / tooling status: which migrations are applied and
# whether the database is in sync with the schema.
netscript db status
What each step does, in plain terms:
netscript db init --name init— initializes database tooling and creates the first migration fromdatabase/postgres/schema/schema.prisma, writing it to the migrations path declared inprisma.config.ts(migrations: { path: 'migrations' }) and applying it to the running Postgres container. The--namevalue names the migration directory; pick something descriptive for later migrations (add-orders,index-email), not justinit.netscript db generate— runs database code generation: the Prisma client (generator clientwithruntime="deno", output./.generated) plus the zod schemas (generator zod→./.generated/zod). Re-run this whenever the schema changes, because the generated types are how your application code stays in lockstep with the database.netscript db seed— executes the seed scripts underdatabase/postgres/scripts/. Seeds are ordinary Deno scripts, so they can insert baseline rows, reference data, or a first admin record using the freshly generated client.netscript db status— reports migration/tooling status so you can confirm the schema is applied and in sync. This is the command you'll reach for most often once you start iterating.
Step 4 — Verify
Confirm the database is migrated and seeded three ways, from most to least authoritative:
- CLI status.
netscript db statusreports theinitmigration as applied and the schema in sync. This is the authoritative check — if it is green, the rest is corroboration. - Generated client exists.
database/postgres/schema/.generated/now contains the Prisma client and thezodschemas — proofdb generatesucceeded and your typed models are importable from application code. - Aspire dashboard. The
postgresresource at https://localhost:18888 is green and shows recent activity from the migration and seed runs. Use it to eyeball that the container is healthy.
If all three line up, your database is provisioned, migrated, and seeded — you can now import the generated client in a service or worker and read the rows the seed wrote.
Where the data goes next
Once the schema is live, the generated client is what every other capability reads and writes
through. Queue jobs persist their message_queue rows (the
PostgreSQL queue backend shares this same datasource),
durable sagas persist runtime state when configured with the Prisma store backend, and your services
query their models directly. The migration loop you just ran is the foundation the rest of the
workspace stands on.
See also
Reference — databaseThe full generated @netscript/database API: schema helpers, the Prisma adapter surface, and migration tooling.
Capability — Database & PrismaHow NetScript wires Prisma 7 + Postgres, per-plugin schema aggregation, and the appsettings-driven datasource.
Orchestration with AspireWhy the AppHost (aspire/apphost.mts) provisions Postgres and Redis, and how the resource graph fits together.
Queue / KV / cronThe next recipe: use the KV and queue backends — including the PostgreSQL queue provider that shares this datasource.
Deploy without AspirePoint the workspace at your own Postgres via POSTGRES_URI / DATABASE_URL using the --no-aspire escape hatch.
For the MySQL adapter surface, see prisma-adapter-mysql.