Step-by-Step: Setting Up NestJS with Prisma and PostgreSQL
NestJS combined with Prisma and PostgreSQL forms a powerful stack for building scalable backend applications. NestJS provides a structured, opinionated architecture inspired by Angular, while Prisma offers a type-safe database layer that eliminates the friction of traditional ORMs.
Prerequisites
- Node.js 20+ installed on your machine
- PostgreSQL 15+ running locally or via Docker
- A package manager (npm, yarn, or pnpm)
- Basic familiarity with TypeScript
Step 1: Scaffold the NestJS Project
Start by installing the NestJS CLI and generating a new project. The CLI handles the boilerplate: TypeScript configuration, the main module, and the entry point.
npm install -g @nestjs/cli
nest new my-backend
cd my-backend
npm run start:devThis creates a project with a clean module-based structure. NestJS uses modules, controllers, and services to organize code — a pattern that scales well as your application grows.
Step 2: Install and Configure Prisma
With the NestJS project running, install Prisma as a development dependency and initialize it. Prisma will create a schema file where you define your data models.
npm install @prisma/client
npm install -D prisma
npx prisma initThe init command creates two files: prisma/schema.prisma (your data model) and a .env file with the database connection string. Open the .env file and update DATABASE_URL with your PostgreSQL credentials.
# .env
database_url="postgresql://user:password@localhost:5432/my_backend?schema=public"Step 3: Define Your Data Model
Open prisma/schema.prisma and define your models. For this tutorial, we'll create a User model and a Post model to demonstrate relations.
// prisma/schema.prisma
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("database_url")
}
model User {
id String @id @default(cuid())
email String @unique
name String?
posts Post[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model Post {
id String @id @default(cuid())
title String
content String?
published Boolean @default(false)
author User @relation(fields: [authorId], references: [id])
authorId String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}Run the migration to create the tables in your PostgreSQL database. Prisma generates a SQL migration file and applies it.
npx prisma migrate dev --name init
npx prisma generateStep 4: Create a Prisma Module
Instead of importing PrismaClient everywhere, create a dedicated module that instantiates it as a singleton. This follows NestJS's dependency injection pattern and ensures efficient database connections.
// src/prisma/prisma.service.ts
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();
}
}// src/prisma/prisma.module.ts
import { Global, Module } from '@nestjs/common';
import { PrismaService } from './prisma.service';
@Global()
@Module({
providers: [PrismaService],
exports: [PrismaService],
})
export class PrismaModule {}Step 5: Build Your First CRUD Endpoint
Now create a Users module with a service and controller. The service uses dependency injection to access PrismaService, and the controller exposes REST endpoints.
// src/users/users.service.ts
import { Injectable } from '@nestjs/common';
import { PrismaService } from '../prisma/prisma.service';
@Injectable()
export class UsersService {
constructor(private prisma: PrismaService) {}
async findAll() {
return this.prisma.user.findMany({
include: { posts: true },
});
}
async findOne(id: string) {
return this.prisma.user.findUnique({
where: { id },
include: { posts: true },
});
}
async create(data: { email: string; name?: string }) {
return this.prisma.user.create({ data });
}
}Step 6: Connect Everything in the App Module
// src/app.module.ts
import { Module } from '@nestjs/common';
import { PrismaModule } from './prisma/prisma.module';
import { UsersModule } from './users/users.module';
@Module({
imports: [PrismaModule, UsersModule],
})
export class AppModule {}With this setup, your NestJS application automatically connects to PostgreSQL on startup, provides Prisma as a singleton across all modules, and exposes type-safe database queries. The pattern extends naturally to any number of models and modules, making it suitable for projects of any scale.