Skip to content

Use Prisma

Replace the in-memory user repository with Prisma and PostgreSQL.

This guide continues from Get started and replaces the in-memory user repository with Prisma and PostgreSQL.

By the end of this guide, users created through /auth/sign-up will be stored in a persistent database instead of memory.

  • A project completed from Get started
  • A PostgreSQL database

Install Prisma, Prisma Client, the PostgreSQL driver adapter, and pg:

Terminal window
pnpm add -D prisma
pnpm add @prisma/client @prisma/adapter-pg pg

Run the Prisma init command from the project root:

Terminal window
pnpm dlx prisma init --output ../generated/prisma

This creates a prisma/schema.prisma file and configures the generated Prisma Client output path.

Add DATABASE_URL to your existing .env file:

.env
DATABASE_URL="postgresql://USER:PASSWORD@localhost:5432/DB_NAME?schema=public"

Replace USER, PASSWORD, and DB_NAME with your PostgreSQL username, password, and database name.

Update prisma/schema.prisma:

prisma/schema.prisma
generator client {
provider = "prisma-client"
output = "../generated/prisma"
moduleFormat = "cjs"
}
datasource db {
provider = "postgresql"
}
model User {
id Int @id @default(autoincrement())
name String
email String @unique
password String
}

The User model matches the example user shape from Get started. brkpt-auth does not require this exact model. Your adapters decide which fields are used for JWT payloads, password validation, and safe user responses.

Create and apply the initial migration:

Terminal window
pnpm dlx prisma migrate dev --name init

Then generate Prisma Client:

Terminal window
pnpm dlx prisma generate

Create the following files:

  • Directorysrc/
    • Directoryprisma/
      • prisma.module.ts
      • prisma.service.ts

Create PrismaService

Create prisma.service.ts and wrap the generated PrismaClient:

src/prisma/prisma.service.ts
import { Injectable } from '@nestjs/common';
import { PrismaPg } from '@prisma/adapter-pg';
import { PrismaClient } from '../../generated/prisma/client';
@Injectable()
export class PrismaService extends PrismaClient {
constructor() {
const adapter = new PrismaPg({
connectionString: process.env.DATABASE_URL as string,
});
super({ adapter });
}
}

Create PrismaModule

Create prisma.module.ts and export PrismaService so the adapters can inject it:

src/prisma/prisma.module.ts
import { Module } from '@nestjs/common';
import { PrismaService } from './prisma.service';
@Module({
providers: [PrismaService],
exports: [PrismaService],
})
export class PrismaModule {}

In Get started, BrkptAuthModule imported UserModule so the adapters could inject MemoryUserRepository.

Now replace UserModule with PrismaModule:

src/brkpt-auth/brkpt-auth.module.ts
import { UserModule } from '../user/user.module';
import { PrismaModule } from '../prisma/prisma.module';
@Module({
imports: [UserModule],
imports: [PrismaModule],
controllers: [...features.flatMap((f) => f.controllers)],
providers: [...features.flatMap((f) => f.providers)],
})
export class BrkptAuthModule {
// ...
}

The brkpt-auth features do not need to change. Only the adapters need to use PrismaService instead of MemoryUserRepository.

Update CoreAdapter

Update CoreAdapter to use PrismaService instead of MemoryUserRepository:

src/brkpt-auth/adapters/core.adapter.ts
import { Injectable } from '@nestjs/common';
import { User } from '../../../generated/prisma/client';
import { PrismaService } from '../../prisma/prisma.service';
import { CorePort } from '../features/core/core.port';
import { AuthJwtPayload } from './types';
@Injectable()
export class CoreAdapter implements CorePort<User> {
constructor(private readonly prisma: PrismaService) {}
mapUserToJwtPayload(user: User): AuthJwtPayload {
return {
sub: user.id,
email: user.email,
};
}
shrinkJwtPayload(payload: AuthJwtPayload): Record<string, unknown> {
return {
sub: payload.sub,
};
}
findUserByJwtPayload(payload: AuthJwtPayload): Promise<User | null> {
return this.prisma.user.findUnique({
where: {
id: payload.sub,
},
});
}
toSafeUser(user: User): Record<string, unknown> {
const { password: _password, ...safe } = user;
return safe;
}
extractUserIdFromJwtPayload(payload: AuthJwtPayload): number {
return payload.sub;
}
}

Update CredentialsAdapter

Update CredentialsAdapter to use PrismaService instead of MemoryUserRepository:

src/brkpt-auth/adapters/credentials.adapter.ts
import { Injectable } from '@nestjs/common';
import * as bcrypt from 'bcrypt';
import { User } from '../../../generated/prisma/client';
import { PrismaService } from '../../prisma/prisma.service';
import { CredentialsPort } from '../features/credentials/credentials.port';
import { SignInDto } from '../features/credentials/dto/sign-in.dto';
import { SignUpDto } from '../features/credentials/dto/sign-up.dto';
@Injectable()
export class CredentialsAdapter implements CredentialsPort<User> {
constructor(private readonly prisma: PrismaService) {}
findUserByDto(dto: SignInDto | SignUpDto): Promise<User | null> {
return this.prisma.user.findUnique({
where: {
email: dto.email,
},
});
}
validatePassword(user: User, dto: SignInDto): Promise<boolean> {
return bcrypt.compare(dto.password, user.password);
}
async createUser(dto: SignUpDto): Promise<User> {
const password = await bcrypt.hash(dto.password, 10);
return this.prisma.user.create({
data: {
name: dto.name,
email: dto.email,
password,
},
});
}
extractUserIdFromUser(user: User): number {
return user.id;
}
}

You can now remove the src/user/ folder created in Get started:

  • Directorysrc/
    • Directoryuser/
      • Directoryrepositories/
        • memory-user.repository.ts
      • user.entity.ts
      • user.module.ts

That folder only existed to provide MemoryUserRepository for the in-memory example. After switching the adapters to PrismaService, it is no longer used.

Start the application:

Terminal window
pnpm start:dev

Create a user through /auth/sign-up, restart the server, then sign in with the same account again.

The sign-in request should still succeed because the user is now stored in PostgreSQL.