NestJS + Prisma/TypeORM
Integrate Prisma or TypeORM as the data layer in NestJS 11 with proper DI, lifecycle, and testing patterns.
Recipe
Quick-reference recipe card - copy-paste ready.
Prisma:
import { Injectable, OnModuleInit, OnModuleDestroy } from "@nestjs/common";
import { PrismaClient } from "@prisma/client";
@Injectable()
export class PrismaService extends PrismaClient implements OnModuleInit, OnModuleDestroy {
async onModuleInit() { await this.$connect(); }
async onModuleDestroy() { await this.$disconnect(); }
}
@Injectable()
export class UsersService {
constructor(private prisma: PrismaService) {}
findAll() { return this.prisma.user.findMany(); }
}When to reach for this: Any NestJS API that needs a database. Prisma for DX and migrations; TypeORM for decorator-driven entities.
Working Example
Prisma Module
// prisma.module.ts
import { Global, Module } from "@nestjs/common";
import { PrismaService } from "./prisma.service.js";
@Global()
@Module({
providers: [PrismaService],
exports: [PrismaService],
})
export class PrismaModule {}
// users.service.ts
import { Injectable } from "@nestjs/common";
import { PrismaService } from "../prisma/prisma.service.js";
@Injectable()
export class UsersService {
constructor(private prisma: PrismaService) {}
async findById(id: string) {
return this.prisma.user.findUnique({ where: { id } });
}
async create(data: { name: string; email: string }) {
return this.prisma.user.create({ data });
}
}
// Test override
const mockPrisma = {
user: {
findUnique: async () => ({ id: "1", name: "Test", email: "t@t.com" }),
create: async (args: { data: { name: string; email: string } }) => ({ id: "1", ...args.data }),
},
};TypeORM Module
import { Module } from "@nestjs/common";
import { TypeOrmModule } from "@nestjs/typeorm";
import { User } from "./user.entity.js";
import { UsersService } from "./users.service.js";
@Module({
imports: [TypeOrmModule.forFeature([User])],
providers: [UsersService],
exports: [UsersService],
})
export class UsersModule {}
@Injectable()
export class UsersService {
constructor(
@InjectRepository(User) private repo: Repository<User>,
) {}
findAll() { return this.repo.find(); }
}What this demonstrates:
- PrismaService with connect/disconnect lifecycle
- Global PrismaModule for app-wide injection
- TypeORM
forFeaturefor entity-scoped repositories - Mock Prisma client for unit tests
Deep Dive
Prisma vs TypeORM in NestJS
| Factor | Prisma | TypeORM |
|---|---|---|
| Schema definition | schema.prisma file | Decorator entities |
| Migrations | prisma migrate | TypeORM migrations |
| Query style | Generated client API | Repository / QueryBuilder |
| NestJS integration | Manual service wrapper | @nestjs/typeorm module |
| Raw SQL | $queryRaw | QueryBuilder raw |
| Team preference | DX-focused teams | Decorator/Spring-like teams |
Transaction Pattern (Prisma)
async transfer(fromId: string, toId: string, amount: number) {
return this.prisma.$transaction(async (tx) => {
await tx.account.update({ where: { id: fromId }, data: { balance: { decrement: amount } } });
await tx.account.update({ where: { id: toId }, data: { balance: { increment: amount } } });
});
}Repository Pattern
Keep controllers thin; services call Prisma or repositories. See Repository Pattern.
Gotchas
- PrismaService not disconnected - connection pool leaks on shutdown. Fix: implement
onModuleDestroy. - Prisma in request scope - unnecessary overhead. Fix: singleton PrismaService, pass tenant ID as parameter.
- TypeORM
synchronize: truein production - auto-alters schema dangerously. Fix: use migrations only. - N+1 queries in services - Prisma
includeforgotten. Fix: useinclude/selector DataLoader. - Testing against real database - slow, flaky tests. Fix: mock PrismaService or use Testcontainers.
- Global module for everything - hides dependencies. Fix: global only for Prisma/Config; feature modules explicit.
Alternatives
| Alternative | Use When | Don't Use When |
|---|---|---|
| Prisma | Type-safe client, easy migrations | Heavy raw SQL, existing TypeORM codebase |
| TypeORM | Decorator entities, NestJS native module | Want Prisma-level DX |
| Drizzle ORM | Lightweight, SQL-first | Need mature NestJS integration |
| Raw pg driver | Maximum control | Standard CRUD API |
FAQs
Should PrismaModule be global?
Common pattern for singleton PrismaService. Alternative: import PrismaModule in each feature module for explicit deps.
How do I handle Prisma in serverless?
Connection pooling via Prisma Accelerate or PgBouncer. Avoid per-invocation $connect without pooling.
Can I use both Prisma and TypeORM?
Technically yes, practically avoid it. Pick one ORM per service.
How do I test services with Prisma?
Mock PrismaService with overrideProvider, or use a test database with prisma migrate reset.
How does this work with NestJS microservices?
Same PrismaService injected into message handlers. One DB connection pool per process.
Should entities be in the same module as controllers?
Separate entities/ or prisma/ from controllers. Services bridge the gap.
How do I handle database migrations in CI?
Run prisma migrate deploy or TypeORM migrations in deploy pipeline before starting the app.
What about read replicas?
Prisma supports read replicas via extension. TypeORM supports multiple connections in config.
Related
- NestJS Basics - module structure
- Dependency Injection - service wiring
- Repository Pattern - data access layer
- Databases - database fundamentals
- NestJS Best Practices - section checklist
Stack versions: This page was written for Node.js 24.18.0 (Active LTS), npm 10+, TypeScript 5.6+, Express 5, Fastify 5, and NestJS 11.