跪拜 Guibai
← Back to the summary

Swapping TypeORM for Prisma in NestJS Cuts Boilerplate and Makes Relational Queries a One-Liner

It Just Works! The NestJS + Prisma Combo Boosts Backend Development Efficiency by 200%

Used Prisma before but forgot how? Don't worry, this article will get you back up to speed in 10 minutes!

As a Node.js backend developer, the thing I'm most grateful for in the last two years is replacing TypeORM with Prisma in my projects.

If you're writing backends with NestJS and still manually writing SQL or getting bogged down by the complex entity configurations of traditional ORMs, then Prisma is definitely your "soulmate." Although I've used Prisma before, my skills got rusty after not touching it for a while (and I'm sure I'm not the only one).

Today, I'll walk you through picking up the core usage of Prisma in NestJS from scratch. The article covers configuration, CRUD, relational queries, transactions, and easily overlooked pitfalls.


1. Why Prisma? (Pain Points of Traditional ORMs)

Before diving into the code, let's align our understanding. NestJS officially recommends several ORMs, but Prisma stands out for three reasons:

  1. Type Safety is a Game-Changer: Prisma Client automatically generates TypeScript types based on your data model. When you use this.prisma.user.findUnique(), your IDE intelligently suggests all fields. A misspelled field name will immediately show a red error at compile time, completely eliminating the nightmare of raw SQL strings.
  2. Declarative Modeling: No need to write cumbersome @Entity() decorators. A single, clear schema.prisma file defines all tables and relationships.
  3. Smooth Relational Queries: Native support for include and nested create makes writing complex queries as smooth as writing JSON.

2. Quick Setup: Integrating into NestJS

1. Install Dependencies

Run this in your NestJS project root directory:

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

After execution, a .env file and a prisma/schema.prisma file will be automatically generated.

2. Configure Database Connection (.env)

Modify the DATABASE_URL in .env (using MySQL as an example):

DATABASE_URL="mysql://root:123456@localhost:3306/my_nest_db"

3. Define the Data Model (schema.prisma)

Let's use a simple User-Post model to demonstrate a one-to-many relationship:

generator client {
  provider = "prisma-client-js"
}

datasource db {
  provider = "mysql"
  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?
  userId    Int
  user      User     @relation(fields: [userId], references: [id])
  createdAt DateTime @default(now())
}

4. Run Migration (Generate Database Tables)

This step generates the SQL, syncs it to the database, and generates the type-safe Prisma Client:

npx prisma migrate dev --name init

3. Elegant Encapsulation in NestJS (Key Point)

Don't just write PrismaClient directly in your Controller. We need to leverage NestJS's DI (Dependency Injection) mechanism to encapsulate it as a globally shared Service.

Create src/prisma/prisma.service.ts:

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

@Injectable()
export class PrismaService extends PrismaClient implements OnModuleInit, OnModuleDestroy {
  constructor() {
    super({
      // Print SQL logs during development for easy debugging
      log: process.env.NODE_ENV === 'development' ? ['query', 'info', 'warn', 'error'] : ['error'],
    });
  }

  async onModuleInit() {
    await this.$connect(); // Automatically connect on application startup
  }

  async onModuleDestroy() {
    await this.$disconnect(); // Disconnect on application shutdown
  }
}

Remember to register this Service in the AppModule so we can use it anywhere via @Inject().


4. Practical CRUD (Picking Back Up What You Forgot)

Now let's write a UserService to refresh our memory on CRUD operations.

1. Read (Find)

// Query a single user (by unique key)
async findOne(id: number) {
  return this.prisma.user.findUnique({
    where: { id },
  });
}

// Query a list (with pagination and fuzzy search)
async findAll(name?: string) {
  return this.prisma.user.findMany({
    where: {
      name: { contains: name, mode: 'insensitive' }, // Case-insensitive fuzzy query
    },
    skip: 0,
    take: 10,
    orderBy: { id: 'desc' },
  });
}

2. Create & Update

// Create a new user
async create(data: { email: string; name: string }) {
  return this.prisma.user.create({
    data,
  });
}

// Update a user
async update(id: number, data: { name?: string }) {
  return this.prisma.user.update({
    where: { id },
    data,
  });
}

3. Delete

// Physical delete (use with caution; soft deletes are more recommended in real business logic)
async remove(id: number) {
  return this.prisma.user.delete({
    where: { id },
  });
}

5. Prisma's "Killer Feature": Relational Operations

This is the most satisfying part of Prisma—no need to write complex left join statements.

Scenario 1: Query a user and fetch all their posts at the same time

async findUserWithPosts(id: number) {
  return this.prisma.user.findUnique({
    where: { id },
    include: {
      posts: true, // Automatic relational query!
    },
  });
}

Scenario 2: Nested Create (Create a user and simultaneously create a post for them) This often requires two save operations in traditional ORMs, but Prisma does it in one step:

async createUserAndPost() {
  return this.prisma.user.create({
    data: {
      email: '[email protected]',
      name: 'Tom',
      posts: {
        create: { title: 'My First Prisma Article', content: 'It just works!' },
      },
    },
    include: { posts: true }, // Return the related results after creation
  });
}

6. Advanced: Transactions and Raw SQL (Fallback Options)

1. Transactions

For scenarios like bank transfers or inventory deductions, you must guarantee all-or-nothing execution.

async transfer() {
  return this.prisma.$transaction([
    this.prisma.user.update({ where: { id: 1 }, data: { name: 'A' } }),
    this.prisma.user.update({ where: { id: 2 }, data: { name: 'B' } }),
  ]);
}

2. Raw SQL (When Prisma's syntax doesn't support a complex query)

Although Prisma is powerful, writing raw SQL is more flexible for extremely complex report queries.

await this.prisma.$queryRaw`SELECT * FROM User WHERE email LIKE ${'%gmail%'}`;

7. Pitfall Guide for Beginners (Lessons Learned the Hard Way)

  1. Must Migrate After Modifying the Model: After changing schema.prisma, you must run npx prisma migrate dev. Otherwise, the database structure won't change, and your code will throw errors.
  2. Restart the Nest Service After Changing the Model: Since the Prisma Client is generated inside node_modules, Nest's Hot Module Replacement (HMR) sometimes doesn't detect the change. If you find that type hints aren't updating, it's recommended to Ctrl+C and re-run npm run start:dev.
  3. findUnique vs findFirst: The where condition for findUnique must contain a unique field (like id or a field marked with @unique), otherwise it will throw an error. If you want to query by a non-unique field, use findFirst.
  4. Implementing Soft Deletes: Prisma has no built-in @SoftDelete. You need to manually add a deletedAt DateTime? field to your model and globally filter by deletedAt: null in your findMany queries.

8. Summary

Integrating Prisma with NestJS is essentially about replacing the Model layer in the MVC architecture with a "sharp blade." The Controller receives RESTful requests, and the Service calls Prisma to operate the database. The entire process is type-safe and full of development enjoyment.

If you're still working overtime because of complex SQL join queries, try introducing Prisma into your next project. Trust me, once you use it, you'll never go back.

If this article helped you regain your Prisma skills, feel free to like, bookmark, and share! If you encounter errors in practice, leave a comment, and we'll troubleshoot together. 😊