Skip to main content
Alpha

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 initpostgres, 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

Prerequisites
NameTypeDescription
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>/ (for mysql, database/mysql/) — its own schema/schema.prisma, prisma.config.ts, scripts/, and (after generate) schema/.generated/.
  • Registers the datasource in appsettings.json under NetScript.Databases.<configKey> with the engine, mode, and a generated DatabaseName — 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 run aspire 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 (getDriverAdaptersetClientconnect/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:

Second-database adapter factories (each implements DatabaseAdapter)
NameTypeDescription
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 ·

@netscript/prisma-adapter-mysql reference ·

Orchestration with Aspire ·

KV, queues & cron — the Postgres queue backend