PHASE 09 / BOOK 10
Hands-On Lab & Phase Exit
Install, configure, integrate, migrate and explain the system
NestJS
PrismaService
Prisma Client
PrismaPg driver adapter
Supabase PostgreSQL
Figure - The database access stack introduced in Phase 09
Course rule: Prisma CLI, @prisma/client and @prisma/adapter-pg are locked to 7.9.1. Newer Prisma releases are not
substituted halfway through the course.
Phase 09 Hands-On Lab
The lab intentionally walks from installation to a working migration and injectable PrismaService. Perform the steps in
order and stop on the first failed checkpoint.
Level 1 - Follow
A. Verify the API workspace
From: taskflow\apps\api
Get-Location
node -v
pnpm -v
B. Install exact dependencies
# From: taskflow\apps\api
pnpm add -D prisma@7.9.1 @types/pg
pnpm add @prisma/client@7.9.1 @prisma/adapter-pg@7.9.1 pg dotenv @nestjs/config
C. Verify exact versions
# From: taskflow\apps\api
pnpm exec prisma version
D. Initialize and inspect
# From: taskflow\apps\api
pnpm exec prisma init
Identify .env, prisma.config.ts and prisma/schema.prisma and explain one responsibility for each.
Level 2 - Configure
A. Add safe environment placeholders
.env.example
# Runtime application connection (Phase 08 recommends a session-capable pooled URL for local NestJS).
DATABASE_URL=
# Prisma CLI / migration connection. Prefer a direct URL when reachable.
DIRECT_URL=
# Optional only if migrate dev cannot create an automatic shadow database.
# NEVER point this at the same database as DIRECT_URL.
SHADOW_DATABASE_URL=
B. Configure Prisma CLI
prisma.config.ts
import "dotenv/config";
import { defineConfig, env } from "prisma/config";
export default defineConfig({
schema: "prisma/schema.prisma",
migrations: {
path: "prisma/migrations",
},
datasource: {
// CLI / migrations: direct or session-capable connection.
url: env("DIRECT_URL"),
},
});
C. Define the minimal model
prisma/schema.prisma
generator client {
provider = "prisma-client"
output = "../src/generated/prisma"
moduleFormat = "cjs"
}
datasource db {
provider = "postgresql"
}
model Todo {
id String @id @default(cuid())
title String
completed Boolean @default(false)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
D. Validate and generate
pnpm exec prisma validate
pnpm exec prisma generate
Level 3 - Integrate
A. Add PrismaService
src/prisma/prisma.service.ts
import { Injectable, OnModuleDestroy, OnModuleInit } from "@nestjs/common";
import { ConfigService } from "@nestjs/config";
import { PrismaPg } from "@prisma/adapter-pg";
import { PrismaClient } from "../generated/prisma/client";
@Injectable()
export class PrismaService
extends PrismaClient
implements OnModuleInit, OnModuleDestroy
{
constructor(config: ConfigService) {
const connectionString = config.getOrThrow<string>("DATABASE_URL");
const adapter = new PrismaPg({ connectionString });
super({ adapter });
}
async onModuleInit() {
await this.$connect();
}
async onModuleDestroy() {
await this.$disconnect();
}
}
B. Add PrismaModule
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 {}
C. Register configuration and PrismaModule
src/app.module.ts
import { Module } from "@nestjs/common";
import { ConfigModule } from "@nestjs/config";
import { HealthModule } from "./health/health.module";
import { TodosModule } from "./todos/todos.module";
import { PrismaModule } from "./prisma/prisma.module";
@Module({
imports: [
ConfigModule.forRoot({ isGlobal: true }),
PrismaModule,
HealthModule,
TodosModule,
],
})
export class AppModule {}
D. Build
pnpm run build
Level 4 - Engineer
Create the first migration only after verifying that DIRECT_URL targets your development database and that
shadow-database behavior is safe.
pnpm exec prisma migrate dev --name init_todo
pnpm exec prisma generate
pnpm exec prisma migrate status
Engineer questions
• Why does NestJS runtime use DATABASE_URL while Prisma CLI can use DIRECT_URL?
• What failure would you expect if the generated client were ESM but NestJS output remained CommonJS?
• Why is migration.sql worth reviewing if Prisma generated it?
• Why is the shadow database dangerous when pointed at the same database as DIRECT_URL?
• What changes in production: migrate dev or migrate deploy? Why?
Phase Exit Checklist
• [ ] prisma, @prisma/client and @prisma/adapter-pg are all 7.9.1.
• [ ] pnpm exec prisma version confirms local versions.
• [ ] schema.prisma uses prisma-client, a custom output, and moduleFormat = cjs.
• [ ] prisma.config.ts reads the CLI datasource from DIRECT_URL.
• [ ] Real secrets are absent from Git.
• [ ] prisma validate passes.
• [ ] prisma generate succeeds.
• [ ] PrismaService uses PrismaPg and one shared PrismaClient instance.
• [ ] NestJS production build passes.
• [ ] migrate dev was run only against the intended development database.
• [ ] migration.sql was inspected.
• [ ] migrate status is clean.
What You Should Understand Before Phase 10
Prisma is now infrastructure you understand. Phase 10 will no longer ask "how do I connect Prisma?" It will ask the
more important question: "what should the relational model actually be?" That moves us from tooling into data design.
Working Prisma infrastructure
Phase 10 relational model design
Projects / tasks / relationships
Phase 11 persistent CRUD API
Figure - The database curriculum continues from infrastructure into modeling and behavior