Skip to main content

Command Palette

Search for a command to run...

Express.js + Prisma v7 Setup (TypeScript + pnpm)

Updated
5 min read
Express.js + Prisma v7 Setup (TypeScript + pnpm)

Express.js + Prisma v7 Setup (TypeScript + pnpm)

Preface

Prisma has introduced a major update with Prisma v7, bringing notable structural changes to how Prisma Client is generated and managed.

Key changes include:

  • A new prisma.config.ts file

  • A separate output directory for generated clients

  • The Prisma Client and generated types are no longer stored inside node_modules

Instead, when running prisma generate, Prisma now requires an explicit output path to be defined in your project’s schema.prisma. For example:

output = "../src/generated/prisma"

This guide walks through a clean and modern setup for building an Express.js + Prisma v7 application using TypeScript and the pnpm package manager.


Project Setup

Initialize the Node.js project

pnpm init

This command creates a package.json file and initializes your Node.js project.

Ensure that pnpm is installed globally before continuing.


Initialize TypeScript

tsc --init

This creates a tsconfig.json file with default compiler settings, which we will customize later.


Update package.json

Add the following scripts:

"scripts": {
  "build": "tsc -b",
  "start": "node dist/src/index.js",
  "dev": "pnpm run build && pnpm run start"
}

Also add:

"type": "module"

Why "type": "module" is required

By default, Node.js treats .js files as CommonJS modules (require() syntax).

However, modern Prisma versions and recent tooling use ES Modules (ESM) with import/export.

Adding "type": "module" tells Node.js:

“Treat all .js files in this project as ES Modules.”

Without this, Node.js will attempt to execute your compiled files as CommonJS and fail when encountering ESM syntax or Prisma’s generated ESM client.


Configure tsconfig.json

Replace the contents of tsconfig.json with the following:

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "strict": true,
    "outDir": "dist"
  },
  "include": ["src/**/*"]
}

Understanding the Key Compiler Options

Think of tsconfig.json as instructions for the TypeScript compiler:

What language to output, how strict to be, and where files should go.


1. Runtime Target (target)

  • Setting: ES2022

  • Purpose: Defines the JavaScript version emitted by TypeScript

  • Why it matters:

    Modern Node.js versions support ES2022 natively. This avoids unnecessary polyfills and keeps the compiled output clean and performant.


2. Module System (module & moduleResolution)

  • Setting: NodeNext

  • Purpose: Enables native ES Module behavior

Important note:

import { x } from "./file.js";

When using NodeNext, file extensions are mandatory in imports. This is a common source of confusion, but it aligns with how modern Node.js resolves modules.


3. Type Safety & Build Stability

  • strict: true

    Enables comprehensive type safety checks—essential for production-grade code.

  • skipLibCheck: true

    Prevents build failures caused by type errors in third-party libraries.


4. Output Structure

  • include limits compilation to the src directory

  • outDir ensures compiled files are emitted to dist

This keeps source and build artifacts clearly separated.


At this point, the base application setup is complete.


Prisma Setup

Install Dependencies

pnpm add prisma @types/node @types/pg --save-dev
pnpm add @prisma/client @prisma/adapter-pg pg dotenv

Package Breakdown

  • prisma – Prisma CLI for migrations and generation

  • @prisma/client – Type-safe database client

  • @prisma/adapter-pg – PostgreSQL driver adapter

  • pg – PostgreSQL driver

  • @types/pg – Type definitions for pg

  • dotenv – Loads environment variables from .env


Initialize Prisma

pnpm prisma init

This creates:

  • prisma.config.ts

  • prisma/schema.prisma


Configure the Prisma Client Generator

Update schema.prisma:

generator client {
  provider = "prisma-client"
  output   = "../generated/prisma"
}

datasource db {
  provider = "postgresql"
}

This ensures the Prisma Client is generated outside node_modules.

📌 Add the following to .gitignore:

/generated/prisma
dist

Configure Database URL

Update the DATABASE_URL in your .env file.

DATABASE_URL="postgresql://username:password@localhost:5432/mydatabase"

You may use a local PostgreSQL instance or a hosted provider such as Neon.


Define the Data Model

Edit prisma/schema.prisma:

model User {
  id    Int     @id @default(autoincrement())
  email String  @unique
  name  String?
  posts Post[]
}

model Post {
  id        Int     @id @default(autoincrement())
  title     String
  content   String?
  published Boolean @default(false)
  author    User    @relation(fields: [authorId], references: [id])
  authorId  Int
}

Run Migrations

pnpm prisma migrate dev --name init

This creates database tables based on your schema.


Generate Prisma Client

pnpm dlx prisma generate

A new generated/prisma directory will be created with the client and types.


Instantiate Prisma Client

Create src/lib/prisma.ts:

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

const connectionString = process.env.DATABASE_URL!;
const adapter = new PrismaPg({ connectionString });

const prisma = new PrismaClient({ adapter });

export { prisma };

💡 In Development, consider implementing a singleton Prisma Client to avoid connection exhaustion.


Express Setup

Install Express

pnpm add express @types/express

Create the Application Entry Point

Create src/index.ts:

import express from "express";
import { prisma } from "./lib/prisma.js";

const app = express();
const port = 3009;

app.get("/test", async (_req, res) => {
  const user = await prisma.user.create({
    data: {
      name: "Alice",
      email: "alice@prisma.io",
      posts: {
        create: {
          title: "Hello World",
          content: "This is my first post!",
          published: true,
        },
      },
    },
    include: { posts: true },
  });

  const allUsers = await prisma.user.findMany({
    include: { posts: true },
  });

  res.json({ users: allUsers });
});

app.listen(port, () => {
  console.log(`Server running on port ${port}`);
});

⚠️ This approach is not recommended for production, but it is sufficient for demonstration and verification purposes.

Test The Endpoint


Conclusion

By following this guide step by step, you will have a fully working Express.js + Prisma v7 application using:

  • TypeScript

  • pnpm

  • PostgreSQL

  • Prisma Client outside node_modules

  • Native ES Modules

This setup aligns with modern Node.js and Prisma best practices and serves as a solid foundation for scalable backend applications.d types are no longer stored inside node_modules


If this guide helped you understand how to set up Express.js with Prisma v7 using TypeScript and pnpm, consider giving it a ❤️ like and sharing it with other developers who might be struggling with Prisma’s new client generation changes.

Have questions, edge cases, or improvements in mind?
Drop them in the comments—I actively read and respond.

Follow me on Hashnode for more deep-dive backend guides, Prisma internals, and modern Node.js workflows 🚀