Prisma Setup
Prisma is a type-safe ORM that generates a fully typed query client from a declarative schema, making it a natural fit for NestJS and TypeScript. Unlike decorator-based ORMs, Prisma keeps your data model in a single schema.prisma file and produces a PrismaClient whose methods know the exact shape of every table. The idiomatic way to use it in Nest is to wrap that client in an injectable PrismaService that opens the connection during bootstrap, then expose it through a PrismaModule so any provider can inject it. This page walks through installing Prisma, defining the schema, generating the client, and wiring it into the DI container.
Installing Prisma
You need the Prisma CLI as a dev dependency and the @prisma/client runtime package. Initialize Prisma to scaffold the schema file and a .env entry for your connection string.
npm install prisma --save-dev
npm install @prisma/client
npx prisma init --datasource-provider postgresql
prisma init creates a prisma/ directory with a starter schema.prisma and adds a DATABASE_URL placeholder to your .env.
.env
prisma/schema.prisma
Keep the
DATABASE_URLin.envand never commit it. Prisma reads this variable throughenv("DATABASE_URL")in the schema, so the same code runs against local, staging, and production databases.
Defining the schema
The schema has three parts: a generator that emits the client, a datasource that points at your database, and one or more model blocks that describe your tables. Models are the source of truth — Prisma derives both the SQL schema (via migrations) and the TypeScript types from them.
// prisma/schema.prisma
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
model User {
id Int @id @default(autoincrement())
email String @unique
name String?
posts Post[]
createdAt DateTime @default(now())
}
model Post {
id Int @id @default(autoincrement())
title String
published Boolean @default(false)
author User @relation(fields: [authorId], references: [id])
authorId Int
}
Generating the client
Run prisma generate to produce the typed client into node_modules/@prisma/client. To push the schema to a fresh database during development, prisma db push creates the tables without a migration history; for tracked changes use prisma migrate dev instead.
npx prisma generate
npx prisma db push
Output:
✔ Generated Prisma Client (v5.x) to ./node_modules/@prisma/client in 84ms
🚀 Your database is now in sync with your Prisma schema. Done in 312ms
Add
prisma generateto apostinstallscript inpackage.json. The generated client lives innode_modules, so it is wiped on a cleannpm installand must be regenerated in CI and Docker builds.
Building the PrismaService
PrismaClient already manages a connection pool, so the cleanest integration is to extend it with an @Injectable() service and let Nest’s lifecycle hooks control the connection. Calling $connect() in onModuleInit makes Nest establish the connection during bootstrap rather than lazily on the first query, which surfaces credential errors immediately.
import { Injectable, OnModuleInit, OnModuleDestroy } from '@nestjs/common';
import { PrismaClient } from '@prisma/client';
@Injectable()
export class PrismaService
extends PrismaClient
implements OnModuleInit, OnModuleDestroy
{
async onModuleInit(): Promise<void> {
await this.$connect();
}
async onModuleDestroy(): Promise<void> {
await this.$disconnect();
}
}
Because the service is a PrismaClient, every model accessor (prisma.user, prisma.post) and method (findMany, create, $transaction) is available directly on the injected instance, fully typed.
Creating the PrismaModule
Wrap the service in a module and mark it @Global() so you only import it once in the root module instead of in every feature module. Export the service so other providers can inject it.
import { Global, Module } from '@nestjs/common';
import { PrismaService } from './prisma.service';
@Global()
@Module({
providers: [PrismaService],
exports: [PrismaService],
})
export class PrismaModule {}
Then import PrismaModule once in AppModule:
import { Module } from '@nestjs/common';
import { PrismaModule } from './prisma/prisma.module';
import { UsersModule } from './users/users.module';
@Module({
imports: [PrismaModule, UsersModule],
})
export class AppModule {}
Injecting and using the service
Inject PrismaService into any provider through the constructor and call the typed query methods. The return types are inferred from your schema, so the editor autocompletes fields and flags typos at compile time.
import { Injectable } from '@nestjs/common';
import { PrismaService } from '../prisma/prisma.service';
import { User } from '@prisma/client';
@Injectable()
export class UsersService {
constructor(private readonly prisma: PrismaService) {}
findAll(): Promise<User[]> {
return this.prisma.user.findMany({ include: { posts: true } });
}
create(email: string, name?: string): Promise<User> {
return this.prisma.user.create({ data: { email, name } });
}
}
When you start the app, the connection is established before any request is served.
npm run start:dev
Output:
[Nest] 5120 - 06/14/2026, 9:11:02 AM LOG [InstanceLoader] PrismaModule dependencies initialized +2ms
[Nest] 5120 - 06/14/2026, 9:11:02 AM LOG [NestApplication] Nest application successfully started +5ms
Sync vs. push vs. migrate
Three commands move your schema into the database, each suited to a different stage.
| Command | What it does | When to use |
|---|---|---|
prisma generate | Regenerates the typed client from the schema | After any schema edit |
prisma db push | Syncs the database to the schema with no history | Early prototyping, throwaway dev DBs |
prisma migrate dev | Creates a versioned SQL migration and applies it | Tracked schema changes, teams, production |
Best Practices
- Extend
PrismaClientin a singlePrismaServiceand connect viaonModuleInitso failures surface at boot. - Make
PrismaModuleglobal and import it once inAppModule; exportPrismaServicefor injection. - Add
prisma generateto apostinstallscript so the client is rebuilt in CI and Docker images. - Use
prisma migrate devfor tracked schema changes and reservedb pushfor disposable development databases. - Keep
DATABASE_URLin.envand out of version control; reference it throughenv()in the schema. - Always call
$disconnect()inonModuleDestroyto release pooled connections on graceful shutdown.