Part 3.1 — Project Setup & Prisma Schema
If your frontend is the face of your application, your backend is the backbone.
Part 3 begins your journey into a real, production-quality backend using NestJS + Prisma + PostgreSQL, the stack we'll reuse consistently for the rest of the series.
This post lays the foundation: project setup, environment config, Prisma installation, PostgreSQL connection, schema modeling, migrations, and initial folder structure.
1. Why NestJS + Prisma + PostgreSQL?
This stack gives you:
NestJS: structured, modular TypeScript backend with decorators, DI, pipes, filters, interceptors
Prisma: type-safe ORM that lets you query PostgreSQL without raw SQL footguns
PostgreSQL: powerful, ACID-compliant RDBMS ideal for production workloads
Everything we build moving forward uses this stack — no alternatives.
2. Create the Backend Project
Inside your monorepo (or root workspace):
pnpm dlx @nestjs/cli new backendChoose TypeScript.
Move into the project:
cd backendInstall dependencies:
pnpm installYour project now has:
backend/
src/
test/
package.json
tsconfig.jsonThis is your home for all backend code.
3. Install Prisma & Initialize It
From inside backend/:
pnpm add -D prisma
pnpm add @prisma/clientInitialize:
pnpm prisma initThis creates:
prisma/
schema.prisma
.envWe’ll configure PostgreSQL next.
4. Configure PostgreSQL Connection
Open your .env:
DATABASE_URL="postgresql://postgres:password@localhost:5432/fullstack_db"If you’re using Docker for PostgreSQL (recommended):
# docker-compose.yml
services:
db:
image: postgres:15
ports:
- "5432:5432"
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: password
POSTGRES_DB: fullstack_dbStart:
docker compose up -dYour backend now has a clean PostgreSQL instance.
5. Define the Initial Prisma Schema
Open prisma/schema.prisma:
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
model User {
id Int @id @default(autoincrement())
name String
email String @unique
createdAt DateTime @default(now())
posts Post[]
}
model Post {
id Int @id @default(autoincrement())
title String
body String
createdAt DateTime @default(now())
authorId Int
author User @relation(fields: [authorId], references: [id])
}Why this schema?
Shows a 1-to-many relation (User → Posts)
Matches typical CRUD operations
Will integrate easily with your earlier React CRUD UI
6. Generate the Client & Run the Migration
Generate Prisma Client:
pnpm prisma generateRun the first migration:
pnpm prisma migrate dev --name initPrisma will create:
the tables
indexes
foreign keys
relation integrity
Your PostgreSQL database now has a working schema.
7. Add PrismaService to NestJS
Create a Prisma module:
pnpm nest g module prisma
pnpm nest g service prismaEdit 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();
}
}Add it to the module:
import { Module } from '@nestjs/common';
import { PrismaService } from './prisma.service';
@Module({
providers: [PrismaService],
exports: [PrismaService],
})
export class PrismaModule {}This gives your entire backend access to PrismaService.
8. Folder Structure Going Forward
We’ll follow a clear module-based layout:
src/
prisma/
users/
users.module.ts
users.service.ts
users.controller.ts
dto/
posts/
posts.module.ts
posts.service.ts
posts.controller.ts
dto/
common/
exceptions/
filters/
pipes/
interceptors/This structure supports scalability without the chaos of sprawling files.
9. Quick Smoke Test Query
Edit app.service.ts temporarily:
constructor(private prisma: PrismaService) {}
getHello() {
return this.prisma.user.findMany();
}Run:
pnpm start:devHit:http://localhost:3000/
You should get [] — an empty users array — confirming your stack works:
React → REST client → NestJS → Prisma → PostgreSQL (coming soon in Part 5)
10. What’s Next?
In Part 3.2, you’ll create:
DTO validation
REST controllers
REST routes
Services with Prisma queries
Production-ready request validation
This is where your backend truly becomes usable by your frontend.
Related
Part 4.6 — Building a Maintainable Testing Architecture
Writing tests is easy. Maintaining hundreds of them is hard. This post shows how to structure your NestJS backend tests so they scale without pain.
Part 4.4 — Testing Auth Guards, JWT Strategy & Protected Routes
Authentication logic is critical—and fragile if untested. In this post, you’ll learn how to test JWT auth guards and protected REST routes with confidence.
Part 4.3 — Integration Testing with Supertest: REST Endpoints
Integration tests verify the entire request pipeline—controllers, DTOs, pipes, filters, interceptors, and database behavior. This post shows how to test your REST API like a professional.
Comments