🎩 You're Invited:Meet the Socket team at Black Hat in Las Vegas, August 3-6.RSVP
Sign In

@prisma/dev

Package Overview
Dependencies
Maintainers
4
Versions
842
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@prisma/dev

A local Prisma Postgres server for development and testing

latest
npmnpm
Version
0.25.0
Version published
Weekly downloads
5.9M
7.77%
Maintainers
4
Weekly downloads
 
Created
Source

@prisma/dev

A local Prisma Postgres server for development and testing.

It runs a real PostgreSQL database in your own Node process — no Docker, no separate service to install — and exposes it over both a plain TCP connection string and the prisma+postgres:// HTTP protocol that deployed Prisma Postgres uses.

Not for production. This is a development and testing tool.

There are two ways to use it:

  • @prisma/dev/vite — a Vite plugin that runs a database for the lifetime of your dev server.
  • @prisma/dev — start and stop a server from your own code.

Install

npm install --save-dev @prisma/dev

Vite plugin

Add the plugin and your app gets a database whenever vite dev runs.

import { prismaDev } from "@prisma/dev/vite";
import { defineConfig } from "vite";

export default defineConfig({
  plugins: [prismaDev({ name: "my-app" })],
});

That sets two environment variables before any of your code runs:

VariableValue
DATABASE_URLTCP connection string for the main database
SHADOW_DATABASE_URLTCP connection string for the shadow database, which prisma migrate dev requires

Set name per project. It decides where the database files are persisted, so two projects left on the default name would share data.

Production builds are untouched. See When the plugin does nothing.

Applying migrations and seeding

A fresh database has no schema. You could migrate from application code, but then migration logic ships in your production artifact for the sake of local development. onDatabaseReady keeps it in the dev-only config instead.

import { execFile } from "node:child_process";
import { promisify } from "node:util";

import { prismaDev } from "@prisma/dev/vite";
import { defineConfig } from "vite";

const execFileAsync = promisify(execFile);

export default defineConfig({
  plugins: [
    prismaDev({
      name: "my-app",
      async onDatabaseReady({ env }) {
        await execFileAsync("npx", ["prisma", "migrate", "deploy"], { env: { ...process.env, ...env } });
      },
    }),
  ],
});

The hook runs before any application or framework code executes, so nothing can observe an unprepared database. vite dev waits for it, so a slow migration delays startup.

The plugin does this work in Vite's configureServer hook. That is the earliest hook which only runs for a dev server that is actually going to serve. Anything earlier also runs for config resolutions that serve nothing — a bare resolveConfig call, or the child compiler a framework builds from your config file — and starting a database for those is wrong. See Frameworks that build a child compiler.

One hook is registered, but it can do as much as you need. Sequence with plain await:

async onDatabaseReady(database) {
  await migrate(database);
  await seed(database);
}

It receives:

PropertyDescription
databaseUrlTCP connection string for the main database
shadowDatabaseUrlTCP connection string for the shadow database
prismaPostgresUrlThe prisma+postgres:// connection string. Carries an API key
envThe variables this plugin manages, mapped to the local database
nameThe server name
ownedfalse when the plugin attached to a database another process started

Pass database.env when spawning a subprocess

Spread env explicitly, as the example above does.

It names the local database directly, so a migration cannot pick up a variable this plugin does not manage. That matters if you disabled one with env: { databaseUrl: false }: the ambient process.env still holds your own value, while database.env holds the local one.

The hook runs once per database

Not once per dev server. Two dev servers sharing one database in one process — see Frameworks that build a child compiler — run it once. A config restart that does not change the plugin's own options keeps the running database, so it does not re-run the hook either.

It does run when the plugin attached to a database that prisma dev was already running. Make it idempotent — prisma migrate deploy already is.

If the hook fails

The plugin shuts the database down and Vite fails to start. It will not serve your app against a half-prepared database.

Throwaway databases

By default the database is persisted under name and survives dev server restarts. Set persistenceMode: "stateless" to get a clean, in-memory database on every start instead.

prismaDev({
  name: "my-app",
  test: { name: "my-tests", persistenceMode: "stateless" },
});

The clearest use is integration tests, where every run should start from a known state. See Databases for Vitest runs.

For interactive vite dev it is worth understanding what you give up:

  • Editing vite.config.ts or an .env file restarts the dev server, which discards the data. Anything you seeded or entered by hand is gone.
  • onDatabaseReady gets an empty database on every start, so it has to do the full migrate and seed each time.
  • The database is invisible to prisma dev ls, stop, and rm, and cannot be shared with a running prisma dev.
  • Ports are chosen from whatever is free rather than the defaults below, unless you set them explicitly. That matters if you want to psql in at a predictable address.

Sharing a database with prisma dev

If a prisma dev server is already running under the same name, the plugin uses it instead of starting its own, and leaves it running when Vite exits. database.owned is false in that case.

This applies to the default stateful mode only. A stateless plugin always starts its own throwaway database, so asking for one never hands you a persistent database by surprise.

So this works, with both terminals on one database:

# terminal 1
npx prisma dev --name my-app

# terminal 2 — vite.config.ts uses prismaDev({ name: "my-app" })
npx vite dev

Frameworks that build a child compiler

Some frameworks compile part of your app with a second, internal Vite server built from the same config file. React Router does this, and so the plugin has to cope with being instantiated twice in one process.

The plugin does. Two acquisitions of one name converge on one database: the second joins the first rather than restarting it, the database stays up until the last of them lets go, and onDatabaseReady runs once. Nothing needs configuring for this.

Two consequences are worth knowing:

  • A child compiler runs without a database. React Router builds its child compiler before any dev server is configured, and drops the hook the plugin uses. Nothing in a child compiler queries a database, but code that runs there cannot expect DATABASE_URL to be set.
  • Commands that only resolve the config start nothing. react-router typegen and react-router build load vite.config.ts without serving anything, so no database starts and no migration runs. A typecheck is a typecheck.

Reading a database's address from another process

process.env reaches the Vite process and its children. It does not reach a psql session, a migration you run by hand, or anything else started from a second terminal.

Look the server up by name instead:

import { getPrismaDevServerConnection } from "@prisma/dev";

const connection = await getPrismaDevServerConnection({ name: "my-app" });

console.log(connection?.databaseUrl); // postgres://...

It resolves to null when no server of that name is running. Only stateful servers are visible — a stateless one writes no state and can only be addressed through the process that started it.

This is why hardcoding a connection string into an .env file is not necessary, and why pinning ports is not either. See Ports and stable addresses.

Connection strings never reach the browser

The plugin writes to process.env and nothing else. It deliberately does not use Vite's define or import.meta.env.

Those inline values into browser assets. Both the TCP connection string and the prisma+postgres:// URL carry credentials, so inlining either would hand database access to every visitor of your app.

Server-side code, SSR loaders, and Prisma Client all read process.env, which is the surface that actually needs these values.

The database's lifetime

The database runs inside the Vite process, so it cannot outlive it. There is no background daemon and nothing to clean up by hand.

Quitting Vite normally, or with Ctrl-C, shuts the database down cleanly. A kill -9 takes the database down with the process; the next start recovers on its own, though it may pause briefly to do so.

Editing vite.config.ts or an .env file restarts the Vite dev server. The database is only restarted along with it if the edit changed this plugin's own options, in which case the new options have to win. Otherwise the running database is carried over untouched. Either way your data is persisted, unless you opted into a throwaway database. Editing application code does not touch the database.

When the plugin does nothing

No database is started when:

  • the command is vite build
  • the command is vite preview
  • the config was loaded by Vitest, which reads the same vite.config.ts
  • the config was only resolved, and no dev server was configured — see Frameworks that build a child compiler

See Databases for Vitest runs to allow one under Vitest.

Databases for Vitest runs

Vitest reads the same vite.config.ts, so by default the plugin starts nothing there. A plain vitest run should not boot a database as a config side effect.

Opt in with the test option:

prismaDev({
  name: "my-app",
  test: { name: "my-app-test", persistenceMode: "stateless" },
});

Each entry under test replaces the corresponding option above it. Everything you do not name is inherited. Here the test run gets its own throwaway database and inherits the env mapping, while vite dev keeps its persistent my-app one.

The port options are inherited too, but this database is stateless, so it takes whatever ports are free rather than the ones it inherited — see Throwaway databases. Set the ports under test if a run needs a predictable address. Note that pinning them collides with a vite dev on the same ports if both run at once.

test: {} opts in with the surrounding options unchanged, so vite dev and vitest run share one database.

Setting test has no effect outside Vitest, so there is one config file and no branching in it.

Keeping your own value for a variable

The plugin owns every variable it writes. If your environment already points one somewhere else, the local database wins and the plugin logs a warning naming what it displaced.

This is deliberate. Many frameworks load .env into process.env before the plugin runs, so deferring to the existing value would leave a project whose .env already names DATABASE_URL with a running database that nothing connects to.

To keep your own value, disable that variable:

prismaDev({
  env: { databaseUrl: false },
  name: "my-app",
});

The plugin then never writes DATABASE_URL, and reports no conflict. database.env in onDatabaseReady still carries the local connection string.

A variable that already holds exactly the value the plugin was going to write is not warned about. There is no conflict to report.

Using the prisma+postgres:// URL

Off by default, because it embeds an API key. Opt in by naming a variable:

prismaDev({
  env: { prismaPostgresUrl: "PRISMA_DATABASE_URL" },
  name: "my-app",
});

Options

OptionDefaultDescription
name"default"Server name, which decides where data is persisted
onDatabaseReadyAsync hook to migrate and seed before Vite starts
persistenceMode"stateful""stateless" for a clean in-memory database on every start
env.databaseUrl"DATABASE_URL"Variable for the main TCP connection string. false to skip
env.shadowDatabaseUrl"SHADOW_DATABASE_URL"Variable for the shadow TCP connection string. false to skip
env.prismaPostgresUrlfalseVariable for the prisma+postgres:// URL. Name it to opt in
testfalseOptions for Vitest runs. An object opts in, false stays off
port51213HTTP server port
databasePort51214Main database port
shadowDatabasePort51215Shadow database port
streamsPort51216Colocated Prisma Streams server port
debugfalseLog the server's debug output

Ports and stable addresses

A named stateful server records the ports it settled on and reuses them the next time it starts. So name alone is enough for a stable address.

To find out which ports a server actually landed on, look it up by name. That is also the recommended way to reach the database from outside the Vite process.

Passing a port explicitly changes the failure mode, deliberately:

  • A default port that is taken moves. The plugin falls back to another free port rather than refusing to start.
  • A port you named is bound or the start fails. It is never silently moved. A caller who names a port has something outside this process depending on it, and pointing that something at a port nothing is listening on is worse than a clear error.

A stateless database records nothing, so it takes any free port on every start unless you name one.

Programmatic server

For test harnesses, scripts, and integrations outside Vite:

import { startPrismaDevServer } from "@prisma/dev";

const server = await startPrismaDevServer({ name: "my-tests" });

console.log(server.database.prismaORMConnectionString); // postgres://...
console.log(server.ppg.url); // prisma+postgres://...

await server.close();

The resolved server exposes:

PropertyDescription
databaseconnectionString, prismaORMConnectionString, and a psql terminalCommand
shadowDatabaseThe same shape, for prisma migrate
ppg.urlThe prisma+postgres:// connection string, API key included
http.urlBase URL of the HTTP server
nameThe server name
close()Shuts everything down

Persistence

startPrismaDevServer() defaults to persistenceMode: "stateless", which discards all data when the server closes. That suits tests.

Pass persistenceMode: "stateful" to persist data under the given name, matching prisma dev and the Vite plugin's default.

const server = await startPrismaDevServer({ name: "my-app", persistenceMode: "stateful" });

Ports

Defaults are 51213 for HTTP, 51214 for the database, 51215 for the shadow database, and 51216 for the Prisma Streams server. Override any of them with port, databasePort, shadowDatabasePort, and streamsPort.

A default that is taken falls back to another free port. A port you pass explicitly is bound or the call rejects — with PortNotAvailableError when nothing can bind it, and PortBelongsToAnotherServerError when another Prisma Dev server already holds it.

Starting a second server under a name that is already running rejects with ServerAlreadyRunningError.

Looking up a running server

import { getPrismaDevServerConnection } from "@prisma/dev";

const connection = await getPrismaDevServerConnection({ name: "my-app" });

Resolves the databaseUrl, shadowDatabaseUrl, and prismaPostgresUrl of a running stateful server by name, or null when none is running. Use it from a process that did not start the database and therefore cannot read its process.env.

Experimental

server.experimental exposes change-data-capture events, query insights, and the colocated Prisma Streams endpoint. These are experimental and may change without notice.

Requirements

The Vite plugin supports Vite 5, 6, 7, and 8. vite is an optional peer dependency, so installing @prisma/dev without Vite is fine — the plugin module references Vite for types only.

License

ISC

Keywords

prisma

FAQs

Package last updated on 27 Jul 2026

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts