Use a second database
Goal: add a second database — a second Postgres, or a MySQL/SQL Server instance — to a NetScript workspace that already has its primary datasource, so each datasource gets its own Prisma schema, migrations, and generated client.
This recipe assumes the primary datasource is Postgres (the recommended default — every tutorial
scaffolds with --db postgres), but the primary engine is itself chosen at scaffold time: pass
--db mysql, --db mssql, or --db sqlite to netscript init for a MySQL, SQL Server, or
file-backed SQLite primary instead. Everything below applies regardless of which engine your
primary uses; the examples simply show the common Postgres-primary case.
NetScript's default scaffold gives you one primary datasource that every plugin
aggregates its .prisma models into (see Database & migration).
A second database is the opposite shape: a separate Prisma schema workspace with its own
generate output and its own migration history. It never merges into the primary aggregation.
The second datasource is polyglot the same way the primary is: netscript db add <engine>
accepts the same four engines as netscript init — postgres, mysql, mssql, and sqlite.
The container-mode engines (postgres/mysql/mssql) are provisioned as an Aspire container
resource (addPostgres / addMySql / addSqlServer); sqlite is file-backed and has no
Aspire container resource, so the Aspire/Docker prerequisites below apply only to the
container engines.
There are two ways to add one, and they answer different needs:
- Scaffold it (
netscript db add) — when you want NetScript to own the second datasource: its own schema dir, migrations, seed scripts, and an Aspire-provisioned container. This is the recommended path and the bulk of this recipe. - Wire an adapter by hand — when the second database is external (a managed MySQL, a reporting warehouse) that you only read/write from application code, with no NetScript-managed migrations. Covered in Connect an external database by hand.
Before you start
| Name | Type | Description |
|---|---|---|
A scaffolded workspace |
with a primary db |
An existing NetScript project whose primary datasource is already wired (Postgres by default, or whichever engine you passed to netscript init via --db) — ideally migrated once via the Database & migration recipe so you know the single-datasource loop. |
netscript CLI |
on PATH |
deno install --global --allow-all --name netscript jsr:@netscript/cli. netscript db add --help should print. |
Aspire CLI + Docker |
for container mode |
The scaffolded second database is provisioned as a container by Aspire (addMySql / addPostgres / addSqlServer). Docker or Podman must be running. Skip only for an external/hand-wired database. |
Deno |
2.x |
Prisma client generation runs under the Deno runtime (the generated schema sets runtime="deno"). |
Step 1 — Scaffold the second database
From the workspace root, run netscript db add <engine>. The engine is one of
postgres, mysql, mssql, or sqlite. The --name flag sets the config key the
datasource is registered under (it defaults to the engine name):
# Add a MySQL datasource. Registered under NetScript.Databases.mysql,
# scaffolded into database/mysql/, provisioned by Aspire's addMySql (image 8.4).
netscript db add mysql
# A second Postgres for an isolated domain. --name gives it a distinct config
# key (and workspace), so it does not collide with the primary 'postgres'.
netscript db add postgres --name analytics
# SQL Server via Aspire's addSqlServer. Same shape — its own schema + migrations.
netscript db add mssql --name reporting
What netscript db add does, in one pass:
- Scaffolds a workspace at
database/<engine>/(formysql,database/mysql/) — its ownschema/schema.prisma,prisma.config.ts,scripts/, and (after generate)schema/.generated/. - Registers the datasource in
appsettings.jsonunderNetScript.Databases.<configKey>with the engine, mode, and a generatedDatabaseName— the same appsettings-driven model the primary Postgres uses. - Adds the workspace as a member of the project so tooling discovers it.
- Regenerates the Aspire config and AppHost helper files so the new container (for example
the MySQL resource via
addMySql) joins the resource graph the next time you runaspire start.
Step 2 — Bring the new container up with Aspire
Because db add regenerated the Aspire config, the new database becomes a container in the
resource graph. Start (or restart) the AppHost so it provisions:
# database/migration recipe covers this in full — restart so Aspire picks up
# the regenerated config and provisions the new container (e.g. the mysql resource).
cd aspire
aspire start
Open the Aspire dashboard at http://localhost:18888 (the access
token is printed by aspire start) and confirm the new resource — mysql, analytics, or
whatever your config key is — goes green alongside the existing postgres and redis.
Step 3 — Migrate and generate the second datasource
The netscript db operations are multi-database aware: every one takes a --db <target>
flag, where the target is a config key, a database name, or all. With the second database
registered under NetScript.Databases.mysql, point each command at it with --db mysql. Run
these from the workspace root, with aspire start up in another terminal:
# Create + apply the first migration for the SECOND datasource only.
# --db selects the config key; --name labels the migration directory.
netscript db init --db mysql --name init
# Generate the Deno-runtime Prisma client + zod schemas into
# database/mysql/schema/.generated for the mysql datasource.
netscript db generate --db mysql
# Run that datasource's seed scripts (database/mysql/scripts/seed.ts).
netscript db seed --db mysql
# Confirm the mysql datasource is migrated and in sync — the authoritative check.
netscript db status --db mysql
# Operate on EVERY registered datasource at once (primary + second).
netscript db migrate --db all --name add_reports
netscript db generate --db all
Each datasource keeps its own migration history under database/<engine>/migrations/
and its own generated client. Editing the second schema and re-running
netscript db migrate --db mysql never touches the primary Postgres, and vice versa.
Step 4 — Query the second client from application code
After db generate --db mysql, the second datasource has its own typed client at
database/mysql/schema/.generated/client.server.ts. Import it exactly like the primary —
just from the new path. The two clients are independent PrismaClient instances, so a service
can read from both:
// services/reporting/src/db.ts
// Each datasource generates its OWN client. Import the second one from its path.
import { PrismaClient as ReportingPrisma } from '../database/mysql/schema/.generated/client.server.ts';
export const reporting = new ReportingPrisma();
// Fully typed off the SECOND schema's models — separate from the primary client.
const rows = await reporting.report.findMany({ take: 20 });
console.log(rows.length);
// services/reporting/src/sync.ts
// A service can hold both clients side by side — one per datasource.
import { PrismaClient as AppPrisma } from '../database/postgres/schema/.generated/client.server.ts';
import { PrismaClient as ReportingPrisma } from '../database/mysql/schema/.generated/client.server.ts';
const app = new AppPrisma();
const reporting = new ReportingPrisma();
// Copy a record from the primary Postgres into the MySQL reporting datasource.
const order = await app.exampleRecord.findFirstOrThrow();
await reporting.report.create({ data: { sourceId: order.id, name: order.name } });
Connect an external database by hand
If the second database is external — a managed MySQL you do not want NetScript to migrate
or provision — skip db add and wire a driver adapter in application code. NetScript wraps each
Prisma 7 driver in a small DatabaseAdapter with a uniform lifecycle
(getDriverAdapter → setClient → connect/healthCheck/getStatus). The MySQL and SQL Server
adapters are sub-exports (not in the @netscript/database/adapters barrel), so a Postgres-only
app never pulls in their drivers:
| Name | Type | Description |
|---|---|---|
createMysqlAdapter(opts) |
@netscript/database/adapters/mysql |
MySQL 8.x / MariaDB via the native-Deno @netscript/prisma-adapter-mysql driver. MysqlConnectionOptions adds charset, timezone, connectionLimit, multipleStatements over the shared parts. |
createMssqlAdapter(opts) |
@netscript/database/adapters/mssql |
SQL Server via @prisma/adapter-mssql. MssqlConnectionOptions adds instanceName, encrypt, trustServerCertificate, integratedSecurity, connectTimeout, requestTimeout. |
createPostgresAdapter(opts) |
@netscript/database/adapters |
PostgreSQL via @prisma/adapter-pg. The only adapter in the barrel; PostgresConnectionOptions adds schema and applicationName. |
The shared options come from DatabaseConnectionOptions (@netscript/database/ports): pass a
connectionString, or the structured host / port / database / username / password /
ssl / poolSize / timeout parts.
// services/reporting/src/external-db.ts
// Sub-export — import from /adapters/mysql, NOT the barrel.
import { createMysqlAdapter } from '@netscript/database/adapters/mysql';
import { PrismaClient } from '../database/mysql/schema/.generated/client.server.ts';
// 1) Build the adapter from structured parts (or pass { connectionString }).
const adapter = createMysqlAdapter({
host: Deno.env.get('MYSQL_HOST') ?? 'localhost',
port: 3306,
database: 'reporting',
username: Deno.env.get('MYSQL_USER') ?? 'root',
password: Deno.env.get('MYSQL_PASSWORD'),
ssl: false,
});
// 2) Pass the driver adapter into Prisma, then hand the client BACK to the adapter.
export const reporting = new PrismaClient({ adapter: adapter.getDriverAdapter() });
adapter.setClient(reporting);
// 3) Lifecycle + health now run off the same client.
await adapter.connect();
const ok = await adapter.healthCheck(); // SELECT 1
console.log('mysql healthy:', ok, await adapter.getStatus());
// Prefer reading config from the environment? getMysqlConfig() reads structured
// MYSQL_HOST / MYSQL_PORT / MYSQL_DATABASE / MYSQL_USER / MYSQL_PASSWORD vars and
// falls back to a connection-string env var (MYSQLDB_URI, then DATABASE_URL).
import { createMysqlAdapter, getMysqlConfig } from '@netscript/database/adapters/mysql';
const cfg = getMysqlConfig(); // → MysqlAdapterConfig, or throws if nothing is set
const adapter = createMysqlAdapter({
host: cfg.hostname,
port: cfg.port,
database: cfg.db,
username: cfg.username,
password: cfg.password,
});
// (getMssqlConfig has the same shape for SQL Server, reading MSSQL_* / MSSQLDB_URI.)
// services/reporting/src/external-db.ts — SQL Server (same lifecycle)
import { createMssqlAdapter } from '@netscript/database/adapters/mssql';
import { PrismaClient } from '../database/mssql/schema/.generated/client.server.ts';
const adapter = createMssqlAdapter({
host: Deno.env.get('MSSQL_SERVER') ?? 'localhost',
port: 1433,
database: 'reporting',
username: 'sa',
password: Deno.env.get('MSSQL_PASSWORD'),
encrypt: true, // local-dev TLS knobs; tighten for production
trustServerCertificate: true,
});
export const reporting = new PrismaClient({ adapter: adapter.getDriverAdapter() });
adapter.setClient(reporting);
await adapter.connect();
In-production pitfalls
See also
Database & Prisma — capability hub ·
Database & migration — the primary-datasource loop ·
@netscript/database reference ·