Home / Articles

Setting Up Prisma 7 with PostgreSQL in a TypeScript Node.js Project

Fix the common Prisma 7 setup errors in TypeScript, from string or undefined URLs to rootDir complaints, and wire up PostgreSQL with the pg driver adapter.

Publisher: REACTAPP.TOP7 min read1666 words

This article is published in English.

Sources: I Wasted 4 Hours Setting Up Prisma — Here’s How You Should Set Up Prisma v7

Following the Prisma quickstart in a plain TypeScript Node.js project often ends in a wall of compiler errors before a single query runs. The problems are not in your models; they come from how the TypeScript compiler, Prisma's new config file and the generated client interact with a conventional src/dist layout. This guide explains the three errors you are most likely to hit, why each one happens, and a step-by-step setup with PostgreSQL that compiles cleanly. Prisma 7 changed several defaults, so check the current Prisma documentation for anything that may have moved since this was written.

The three errors that derail a fresh setup

A connection URL typed as string or undefined

In prisma.config.ts, the database URL is usually read with process.env.DATABASE_URL. TypeScript types every environment variable as string | undefined, because nothing guarantees it is set at runtime. The config expects a plain string, so the compiler rejects the assignment.

prisma.config.ts outside the root directory

A typical TypeScript project sets rootDir to src and outDir to dist. Prisma creates prisma.config.ts at the project root, and by default the compiler includes every .ts file it finds. It then complains that a file outside rootDir is part of the program. Moving the config into src would silence the error, but that is the wrong fix: src is for application code such as routes, controllers and data access, not tooling configuration.

A generated client outside the root directory

The same thing happens with the generated client. If Prisma writes it to generated/prisma at the project root, the compiler again finds TypeScript files outside rootDir. Moving generated code into src is also a poor choice, because it is build output from prisma generate, not source you maintain.

Left unresolved, these errors stop the project from starting. A side effect is just as annoying: compiled JavaScript for prisma.config.ts can end up outside dist, cluttering the repository. Patching errors one by one tends to create new ones, so it is worth fixing the setup as a whole.

Step 1: create the project and install dependencies

Start with an empty TypeScript project. Install the compiler, tsx for running TypeScript directly, and Node's type definitions, then generate a tsconfig.json:

npm init
npm install typescript tsx @types/node --save-dev
npx tsc --init

Next, install Prisma. The CLI and the pg type definitions are development dependencies; the client, the PostgreSQL driver adapter, the pg driver and dotenv are runtime dependencies:

npm install prisma @types/pg --save-dev
npm install @prisma/client @prisma/adapter-pg pg dotenv

The @prisma/adapter-pg package matters here. In Prisma 7 the client talks to the database through a JavaScript driver adapter, so you pass it a configured pg connection instead of relying only on a URL in the schema.

Step 2: configure ES modules and the compiler layout

Mark the package as an ES module in package.json:

{
  "type": "module"
}

Then set the source and output directories in tsconfig.json, and create the src folder:

"rootDir": "./src",
"outDir": "./dist",

Step 3: initialize Prisma

Run the CLI once (on its own, this simply prints the available commands and confirms the installation works):

npx prisma

Then initialize Prisma for PostgreSQL:

npx prisma init --datasource-provider postgresql --output ../generated/prisma

This creates prisma.config.ts, a prisma folder containing schema.prisma, and a .env file for the database URL. The --output flag sets where the client will be generated; Step 4 revisits that choice. Right after this step is when the errors described above appear.

Step 4: fix the configuration

Three targeted changes resolve all three errors.

Read the URL with Prisma's env helper

Replace the contents of prisma.config.ts with the following. This file is TypeScript, even though the snippet is labelled otherwise.

import "dotenv/config";
import type { PrismaConfig } from "prisma";
import { env } from "prisma/config";

export default {
  schema: "prisma/schema.prisma",
  migrations: {
    path: "prisma/migrations",
    seed: "tsx prisma/seed.ts",
  },
  datasource: {
    url: env("DATABASE_URL"),
  },
} satisfies PrismaConfig;

What to notice:

  • import "dotenv/config" loads .env before anything reads it. Prisma 7 no longer loads it for you automatically in this setup.
  • env("DATABASE_URL") from prisma/config returns a string and fails loudly if the variable is missing, which removes the string | undefined error.
  • satisfies PrismaConfig type-checks the object without widening it.
  • The config also declares where the schema and migrations live and which command seeds the database.

Limit what the compiler includes

Add an include entry at the end of tsconfig.json:

"include": ["src"],

This tells the compiler to build only files under src. The Prisma CLI still reads prisma.config.ts on its own, but tsc stops treating it as part of your program, so the rootDir error disappears and no stray JavaScript is emitted outside dist.

Generate the client into node_modules

Finally, edit schema.prisma so the generator has no custom output path:

generator client {
  provider = "prisma-client-js"
}

datasource db {
  provider = "postgresql"

Without an output, the prisma-client-js generator writes the client into node_modules, so nothing generated sits at the project root and the last rootDir error goes away. The snippet ends after the datasource provider; close the block with a brace. In Prisma 7 the connection URL lives in prisma.config.ts, not in the schema.

Be aware that this is a trade-off. Prisma's newer prisma-client generator is designed around an explicit output directory, and prisma-client-js is the older generator. If you adopt the newer one, keep its output outside src and exclude it from tsc through include, as above.

Step 5: add the database URL and migrate

Put your connection string in .env. The value below is a placeholder; use your own database, and never commit real values:

DATABASE_URL="postgresql://johndoe:randompassword@localhost:5432/mydb?schema=public"

Define your models in schema.prisma, then create and apply the first migration:

npx prisma migrate dev --name init

Step 6: generate the client

Generate the typed client from the schema:

npx prisma generate

Run this again whenever the schema changes, so the types stay in sync with your models.

Step 7: create a shared Prisma instance

Create src/lib/prisma.ts to hold one client for the whole application. It builds a PostgreSQL adapter from the connection string and passes it to PrismaClient. This code is TypeScript as well:

import "dotenv/config";
import { PrismaPg } from "@prisma/adapter-pg";
import { PrismaClient } from "../../node_modules/@prisma/client/index.js";

const connectionString = `${process.env.DATABASE_URL}`;

const adapter = new PrismaPg({ connectionString });
const prisma = new PrismaClient({ adapter });

export default prisma;

The client is imported through an explicit relative path into node_modules:

import { PrismaClient } from "../../node_modules/@prisma/client/index.js";

That path works because the client was generated into node_modules in Step 4. In most setups the bare @prisma/client specifier resolves to the same package, so try that first; fall back to the explicit path only if your module resolution settings require it.

One pitfall in this snippet: wrapping process.env.DATABASE_URL in a template string turns a missing value into the literal text "undefined", which produces a confusing connection error later. A short check that throws when the variable is absent makes failures obvious.

Now import this instance wherever you need database access, such as controllers or services, and run your CRUD queries through it. Keeping one shared client avoids opening a new connection pool per file.

Key takeaways

  • Most Prisma 7 setup errors in TypeScript come from the compiler's rootDir rules, not from Prisma itself.
  • Use env() from prisma/config plus dotenv/config for a correctly typed, explicitly loaded database URL.
  • Restrict tsc with "include": ["src"] so tooling files and generated code stay out of your build.
  • Keep generated clients out of src, either in node_modules or an excluded output folder.
  • Create one shared PrismaClient with the PrismaPg adapter and validate the connection string at startup.

If you are using Next.js rather than a plain Node.js service, our guide to setting up Prisma 7 in a Next.js 16 TypeScript project covers the framework-specific details.