NestJS with Prisma for Beginners: How to Persist Data in a Real Database

So far in this series, we have built a NestJS API that can:

Very nice.

But there is still one big problem:

the data is probably living in memory

Which is totally fine for learning.

And completely terrible for real applications.

Because the moment your app restarts:

This is where Prisma comes in.

Prisma is an ORM and database toolkit that gives you type-safe access to databases like PostgreSQL, MySQL, SQLite, and others. Prisma’s current docs describe it as a type-safe ORM for databases including Postgres, MySQL, and SQLite, and Nest has an official Prisma recipe specifically for integrating it into NestJS.

In this article, we are going to use Prisma as the bridge between:

By the end, you will understand:

Let’s make your API remember things.


Why This Chapter Matters So Much

A backend without persistence is useful for learning structure.

But once you want a real app, you need:

That is exactly the kind of workflow Prisma is built for.

Prisma’s docs describe the Prisma schema as the main configuration point for your ORM setup, containing your database connection and your data model. Prisma Migrate is then used to keep the database schema in sync with your Prisma schema as it evolves.

So this chapter is not just “add one more tool.”

It is the moment your NestJS API starts becoming an actual application.


What Prisma Is in Plain English

Prisma gives you a structured way to:

The current Prisma docs describe the CLI as the tool for initializing Prisma, generating Prisma Client, and managing migrations, while the generated Prisma Client is the type-safe query interface tailored to your schema.

So instead of manually writing raw SQL for every simple operation, Prisma lets you do things like:

in a way that feels very TypeScript-friendly.

That makes it a very natural fit for NestJS.


Why Prisma Fits Well with NestJS

NestJS likes:

Prisma also likes:

That is why they pair so well.

Nest’s official Prisma recipe shows this exact pattern:

So Prisma does not replace Nest structure.

It plugs into it very cleanly.


Step 1: Install Prisma

The Nest Prisma recipe and Prisma’s Nest guide both start by installing Prisma as a dev dependency and then installing @prisma/client for runtime use. The current Prisma Nest guide also shows database-driver-specific packages for some setups, such as PostgreSQL driver adapters, but the core learning flow still begins with prisma and @prisma/client.

Start with:

npm install prisma --save-dev
npm install @prisma/client

That gives you:

Very important pair.


Step 2: Initialize Prisma

Prisma’s docs still use npx prisma init as the standard initialization command. The Nest Prisma guide also shows an initialization flow that creates a prisma directory, a schema.prisma file, a prisma.config.ts, and a .env file. (prisma.io)

Run:

npx prisma init

Or, if you want to generate the client into a custom location from the start, Prisma’s Nest guide shows:

npx prisma init --output ../src/generated/prisma

(prisma.io)

This setup gives you some important files:

These are the core pieces of your Prisma setup. (prisma.io)


What schema.prisma Actually Is

The Prisma schema is the main configuration file for Prisma ORM. Prisma’s docs say it typically contains three main kinds of things:

That means this file usually defines:

A simple example:

generator client {
  provider = "prisma-client"
  output   = "../src/generated/prisma"
}

datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}

model User {
  id    Int     @id @default(autoincrement())
  email String  @unique
  name  String?
  posts Post[]
}

model Post {
  id        Int      @id @default(autoincrement())
  title     String
  content   String?
  published Boolean  @default(false)
  authorId  Int
  author    User     @relation(fields: [authorId], references: [id])
}

This structure follows the current Prisma Nest guide closely: generator block, datasource block, then models like User and Post. (prisma.io)


A Very Important Current Detail: moduleFormat = "cjs"

This is one of the easiest things to miss if you follow older Prisma tutorials.

Nest’s current Prisma recipe explicitly notes that Prisma v7 ships as ESM by default, while standard Nest setups are commonly CommonJS, so you need to set moduleFormat = "cjs" in the Prisma generator configuration for this combo to work cleanly.

So in the current Nest + Prisma setup, your generator block should look like this:

generator client {
  provider     = "prisma-client"
  output       = "../src/generated/prisma"
  moduleFormat = "cjs"
}

That small line saves a lot of confusion.

Very much worth knowing early.


Step 3: Configure DATABASE_URL

Prisma uses the DATABASE_URL environment variable to connect to your database. The current Nest guide says .env is typically used to store database credentials, and the datasource block reads from env("DATABASE_URL"). (prisma.io)

For PostgreSQL, a typical value looks like:

DATABASE_URL="postgresql://user:password@localhost:5432/mydb?schema=public"

Prisma’s PostgreSQL guide documents that general URL format directly. (prisma.io)

If you want to keep the very first local setup simpler, many tutorials use SQLite, but for most real backend practice, PostgreSQL is the better long-term mental model. Prisma supports both. (prisma.io)


Step 4: Create and Run a Migration

Once your models are in schema.prisma, you need to create the actual database tables.

This is where Prisma Migrate comes in.

Prisma’s Migrate docs say prisma migrate dev is the normal development command for creating and applying migrations from your Prisma schema. It creates a migration history and keeps your database schema in sync with the data model. (prisma.io)

Run:

npx prisma migrate dev --name init

This does the important work of:

That is the moment your data model stops being theoretical.

Now it is real.


Step 5: Generate Prisma Client

Prisma’s CLI docs say prisma generate generates assets like Prisma Client based on your schema and the generator block, writing the client to the configured output path. (prisma.io)

Run:

npx prisma generate

Now Prisma creates a type-safe client that matches your models.

That means when you use prisma.user.findMany() in TypeScript later, it is based on the actual schema you defined.

That is one of Prisma’s nicest strengths.


Step 6: Create a PrismaService in NestJS

Nest’s official Prisma recipe and Prisma’s Nest guide both show creating a dedicated PrismaService so the Prisma client can be injected like any other Nest provider.

A beginner-friendly version looks like this:

import { Injectable, OnModuleInit } from '@nestjs/common';
import { PrismaClient } from '../generated/prisma/client';

@Injectable()
export class PrismaService extends PrismaClient implements OnModuleInit {
  async onModuleInit() {
    await this.$connect();
  }
}

This is a very Nest-friendly pattern because:

Very nice fit.


Step 7: Put PrismaService in a Module

Now register it the Nest way.

Example:

import { Global, Module } from '@nestjs/common';
import { PrismaService } from './prisma.service';

@Global()
@Module({
  providers: [PrismaService],
  exports: [PrismaService],
})
export class PrismaModule {}

Then import PrismaModule into your app or feature modules as needed.

This follows normal Nest module behavior: modules organize providers, and exported providers become available to importing modules. Nest’s modules docs still define modules this way.


Step 8: Use Prisma Inside a Nest Service

This is where it starts feeling real.

Instead of an in-memory array, your feature service now uses Prisma.

Example users.service.ts:

import { Injectable } from '@nestjs/common';
import { PrismaService } from '../prisma/prisma.service';
import { CreateUserDto } from './dto/create-user.dto';

@Injectable()
export class UsersService {
  constructor(private readonly prisma: PrismaService) {}

  findAll() {
    return this.prisma.user.findMany();
  }

  findOne(id: number) {
    return this.prisma.user.findUnique({
      where: { id },
    });
  }

  create(data: CreateUserDto) {
    return this.prisma.user.create({
      data,
    });
  }
}

This matches the current Prisma + Nest learning flow: generate the client, wrap it in a Nest service, and then call model methods like findMany, findUnique, and create from your feature services. (prisma.io)

Now your service is no longer pretending to persist data.

It actually does.


Step 9: Expose It Through a Controller

At this point, the controller stays mostly normal.

Example:

import { Body, Controller, Get, Param, ParseIntPipe, Post } from '@nestjs/common';
import { UsersService } from './users.service';
import { CreateUserDto } from './dto/create-user.dto';

@Controller('users')
export class UsersController {
  constructor(private readonly usersService: UsersService) {}

  @Get()
  findAll() {
    return this.usersService.findAll();
  }

  @Get(':id')
  findOne(@Param('id', ParseIntPipe) id: number) {
    return this.usersService.findOne(id);
  }

  @Post()
  create(@Body() body: CreateUserDto) {
    return this.usersService.create(body);
  }
}

That is one of the nice things about adding Prisma to Nest:
your controllers do not suddenly become weird.

The main difference is that the service now talks to a real database.


Why This Pattern Is So Clean

This is the real architectural win.

That is a very healthy stack of responsibilities.

And it fits Nest’s style perfectly.


Prisma Migrations Are a Huge Part of Why This Feels Better

A lot of beginners first think:
“I just need database queries.”

But what you really need is:

That is exactly what Prisma Migrate is for.

Prisma’s docs explicitly describe Prisma Migrate as the tool that keeps your database schema in sync with your Prisma schema and generates a history of SQL migration files. (prisma.io)

That means when your schema changes later, you are not just manually improvising on the database.

You have a workflow.

That is a very big upgrade.


A Simple Example Model Set

If you want a good beginner-first setup, use two related models like:

That lets you learn:

And Prisma’s Nest guide uses that exact kind of relationship-oriented example for getting started. (prisma.io)

This is enough to build:

Very good practice surface.


What You Should Put in .env

Keep the database connection string in .env, not hardcoded in source code.

That is both the current Prisma flow and the current Nest config-friendly habit. Prisma’s Nest guide explicitly uses .env for the connection string, and Nest’s configuration docs recommend centralizing environment-based configuration. (prisma.io)

Example:

DATABASE_URL="postgresql://postgres:postgres@localhost:5432/codewithziye?schema=public"

This becomes especially important later when you have:

Different environments should not mean editing source code every time.


Common Beginner Mistakes with Prisma in NestJS

Let’s save a few future headaches.

1. Forgetting to run migrations

Changing schema.prisma alone does not magically update your real database.

You still need prisma migrate dev in development. Prisma’s migration docs are very explicit about this workflow. (prisma.io)

2. Forgetting to generate the client

If the schema changes, the generated client needs to match.

That is why prisma generate matters. (prisma.io)

3. Not setting moduleFormat = "cjs" in the current Nest recipe

This is one of the most current gotchas. Nest’s Prisma recipe explicitly calls it out for Prisma v7 with CommonJS Nest projects.

4. Putting Prisma logic directly in controllers

You can, but it is not a great pattern.

Keep database access in services.

5. Treating DTOs and Prisma models as the same thing

They are related, but they are not the same responsibility.

That separation matters.

6. Hardcoding DB credentials

Please do not.

Your future self and your deployment pipeline will both be annoyed.


A Good Mental Model to Remember

Here is the simplest way to carry Prisma in your head inside a Nest project:

That is the pattern.

And once it clicks, database persistence feels much less intimidating.


Why Prisma Is a Great Choice for This Series

For this beginner series, Prisma is a strong fit because it teaches the right things in a clear order:

  1. define your data model
  2. generate a client
  3. migrate the schema
  4. use the client through a Nest service
  5. connect it to real endpoints

That is very teachable.

And very practical.

Also, Prisma’s type-safe client fits really nicely with the Nest + TypeScript mindset.

That makes the learning curve feel cleaner than many older “magic ORM” workflows.


Final Thoughts

This is the chapter where your NestJS API starts behaving like a real backend.

Nest’s current Prisma recipe and Prisma’s current docs support a very clean path:

That is a huge step forward.

Because now your app is no longer working with temporary in-memory data.

It can actually persist records in a real database.

And that changes everything.

In the next article, we’ll make that API feel even more production-ready with better validation, versioning, and Swagger documentation.


Real Interview Questions

What is Prisma in a NestJS app?

Prisma is an ORM and database toolkit that gives your NestJS application type-safe access to databases like PostgreSQL, MySQL, and SQLite. Nest also has an official Prisma recipe for integrating it into Nest applications.

How do I start Prisma in a NestJS project?

The standard starting point is to install Prisma, then run npx prisma init, which creates the Prisma setup files such as schema.prisma and .env.

What does schema.prisma do?

It is Prisma’s main configuration file. It defines your datasource, client generator, and data models.

Why do I need prisma migrate dev?

Because migrations are how Prisma applies your schema changes to the real database in development and keeps the database schema in sync with your Prisma schema.

Why do current NestJS Prisma setups need moduleFormat = "cjs"?

Nest’s official Prisma recipe notes that Prisma v7 defaults to ESM, while common Nest projects still use CommonJS, so moduleFormat = "cjs" is required in that setup.

Should I use Prisma directly in a controller?

It is better to wrap Prisma in a PrismaService and use it inside feature services, which matches Nest’s architecture and the official Nest Prisma recipe.