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 8.6 — When (and When Not) to Use GraphQL in Real Systems
GraphQL is powerful—but expensive. This post helps you decide, based on real constraints, whether GraphQL is worth adopting or whether REST will serve you better.
Part 8.5 — Backend Architecture: Controllers vs “Resolvers” in NestJS
If you squint a little, a NestJS controller method already is a resolver. This post shows how to embrace that idea without introducing GraphQL complexity.
Part 8.4 — Client-Driven Data with REST: Avoiding Over-Fetching
Over-fetching is a client problem as much as a server problem. This post shows how React apps can drive data needs intentionally—without turning REST APIs into guesswork.
Comments