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 aspire start failed 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.1-alpha.12 — 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 http://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-api(:8094) and the streams service (:4437) join the graph.
Step 3 — Run the migration workflow
Open a second terminal at the workspace root (keep aspire start going in the first).
Now Postgres is reachable, run the four-command database 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 http://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.