NestJS + Prisma: āļŠāđāļ•āļāđāļšāđ‡āļāđ€āļ­āļ™āļ”āđŒāļŠāļĄāļąāļĒāđƒāļŦāļĄāđˆāļŠāļģāļŦāļĢāļąāļš Node.js

āļ„āļđāđˆāļĄāļ·āļ­āļ‰āļšāļąāļšāļŠāļĄāļšāļđāļĢāļ“āđŒāđƒāļ™āļāļēāļĢāļŠāļĢāđ‰āļēāļ‡ API āđāļšāđ‡āļāđ€āļ­āļ™āļ”āđŒāļŠāļĄāļąāļĒāđƒāļŦāļĄāđˆāļ”āđ‰āļ§āļĒ NestJS āđāļĨāļ° Prisma āļ„āļĢāļ­āļšāļ„āļĨāļļāļĄāļāļēāļĢāļ•āļąāđ‰āļ‡āļ„āđˆāļē āđ‚āļĄāđ€āļ”āļĨ āđ€āļ‹āļ­āļĢāđŒāļ§āļīāļŠ āļ—āļĢāļēāļ™āđāļ‹āļāļŠāļąāļ™ āđāļĨāļ°āđāļ™āļ§āļ›āļāļīāļšāļąāļ•āļīāļ—āļĩāđˆāļ”āļĩ

NestJS āđāļĨāļ° Prisma āļŠāļģāļŦāļĢāļąāļšāļāļēāļĢāļŠāļĢāđ‰āļēāļ‡āļŠāđāļ•āļāđāļšāđ‡āļāđ€āļ­āļ™āļ”āđŒāļŠāļĄāļąāļĒāđƒāļŦāļĄāđˆ

NestJS āđāļĨāļ° Prisma āđ€āļ›āđ‡āļ™āļāļēāļĢāļœāļŠāļĄāļœāļŠāļēāļ™āļ—āļĩāđˆāļ—āļĢāļ‡āļžāļĨāļąāļ‡āļŠāļģāļŦāļĢāļąāļšāļāļēāļĢāļžāļąāļ’āļ™āļēāđāļšāđ‡āļāđ€āļ­āļ™āļ”āđŒāļŠāļĄāļąāļĒāđƒāļŦāļĄāđˆ NestJS āļĄāļ­āļšāļŠāļ–āļēāļ›āļąāļ•āļĒāļāļĢāļĢāļĄāđāļšāļšāđ‚āļĄāļ”āļđāļĨāđāļĨāļ°āļāļēāļĢāļ‰āļĩāļ”āļ”āļĩāđ€āļžāļ™āđ€āļ”āļ™āļ‹āļĩ āđƒāļ™āļ‚āļ“āļ°āļ—āļĩāđˆ Prisma āļĄāļ­āļš ORM āļ—āļĩāđˆāļ›āļĨāļ­āļ”āļ āļąāļĒāļ”āđ‰āļēāļ™āļŠāļ™āļīāļ”āļžāļĢāđ‰āļ­āļĄāļ›āļĢāļ°āļŠāļšāļāļēāļĢāļ“āđŒāļ™āļąāļāļžāļąāļ’āļ™āļēāļ—āļĩāđˆāļĒāļ­āļ”āđ€āļĒāļĩāđˆāļĒāļĄ āļŠāđāļ•āļāļ™āļĩāđ‰āļŠāđˆāļ§āļĒāđƒāļŦāđ‰āļŠāļĢāđ‰āļēāļ‡ API āļ—āļĩāđˆāđāļ‚āđ‡āļ‡āđāļĢāļ‡āđāļĨāļ°āļšāļģāļĢāļļāļ‡āļĢāļąāļāļĐāļēāļ‡āđˆāļēāļĒāđ„āļ”āđ‰

āđ€āļŦāļ•āļļāļœāļĨāļ‚āļ­āļ‡āļāļēāļĢāļˆāļąāļšāļ„āļđāđˆāļ™āļĩāđ‰

Prisma āļŠāļĢāđ‰āļēāļ‡āđ„āļ„āļĨāđ€āļ­āļ™āļ•āđŒ TypeScript āļ—āļĩāđˆāļĄāļĩāļŠāļ™āļīāļ”āļ‚āđ‰āļ­āļĄāļđāļĨāđ‚āļ”āļĒāļ­āļąāļ•āđ‚āļ™āļĄāļąāļ•āļīāļˆāļēāļāļŠāļ„āļĩāļĄāļēāļāļēāļ™āļ‚āđ‰āļ­āļĄāļđāļĨ āđ€āļĄāļ·āđˆāļ­āļĢāļ§āļĄāļāļąāļš NestJS āđāļĨāļ°āļĢāļ°āļšāļšāđ‚āļĄāļ”āļđāļĨ āđ‚āļ„āđ‰āļ”āļˆāļ°āļ­āļ˜āļīāļšāļēāļĒāļ•āļąāļ§āđ€āļ­āļ‡āđ„āļ”āđ‰āđāļĨāļ°āļ‚āđ‰āļ­āļœāļīāļ”āļžāļĨāļēāļ”āļ”āđ‰āļēāļ™āļŠāļ™āļīāļ”āļˆāļ°āļ–āļđāļāļ•āļĢāļ§āļˆāļžāļšāļ•āļąāđ‰āļ‡āđāļ•āđˆāļ•āļ­āļ™āļ„āļ­āļĄāđ„āļžāļĨāđŒ

āļāļēāļĢāļ•āļąāđ‰āļ‡āļ„āđˆāļēāđ‚āļ›āļĢāđ€āļˆāļāļ•āđŒāđ€āļĢāļīāđˆāļĄāļ•āđ‰āļ™

āļāļēāļĢāļĢāļ§āļĄ Prisma āđ€āļ‚āđ‰āļēāļāļąāļšāđ‚āļ›āļĢāđ€āļˆāļāļ•āđŒ NestJS āļ•āđ‰āļ­āļ‡āļœāđˆāļēāļ™āļ‚āļąāđ‰āļ™āļ•āļ­āļ™āļāļēāļĢāļ•āļąāđ‰āļ‡āļ„āđˆāļēāđ€āļžāļĩāļĒāļ‡āđ„āļĄāđˆāļāļĩāđˆāļ‚āļąāđ‰āļ™āļ•āļ­āļ™ āļāļĢāļ°āļšāļ§āļ™āļāļēāļĢāđ€āļ›āđ‡āļ™āļĄāļēāļ•āļĢāļāļēāļ™āđāļĨāļ°āļĄāļĩāđ€āļ­āļāļŠāļēāļĢāļ„āļĢāļšāļ–āđ‰āļ§āļ™

bash
# terminal
# Create a new NestJS project
nest new my-backend-api
cd my-backend-api

# Install Prisma as a dev dependency
npm install prisma --save-dev

# Install the Prisma client
npm install @prisma/client

# Initialize Prisma with PostgreSQL
npx prisma init --datasource-provider postgresql

āļ„āļģāļŠāļąāđˆāļ‡āļ™āļĩāđ‰āļˆāļ°āļŠāļĢāđ‰āļēāļ‡āđ‚āļŸāļĨāđ€āļ”āļ­āļĢāđŒ prisma/ āļžāļĢāđ‰āļ­āļĄāđ„āļŸāļĨāđŒ schema.prisma āđāļĨāļ°āđ„āļŸāļĨāđŒ .env āļŠāļģāļŦāļĢāļąāļšāļ•āļąāļ§āđāļ›āļĢāļŠāļ āļēāļžāđāļ§āļ”āļĨāđ‰āļ­āļĄ

src/prisma/prisma.service.tstypescript
import { Injectable, OnModuleInit, OnModuleDestroy, Logger } from '@nestjs/common';
import { PrismaClient } from '@prisma/client';

@Injectable()
export class PrismaService extends PrismaClient implements OnModuleInit, OnModuleDestroy {
  // Dedicated logger for Prisma operations
  private readonly logger = new Logger(PrismaService.name);

  constructor() {
    // Configure client with logging in development
    super({
      log: process.env.NODE_ENV === 'development'
        ? ['query', 'info', 'warn', 'error']
        : ['error'],
    });
  }

  // Automatic connection on module startup
  async onModuleInit() {
    await this.$connect();
    this.logger.log('Prisma connection established');
  }

  // Clean disconnection on application shutdown
  async onModuleDestroy() {
    await this.$disconnect();
    this.logger.log('Prisma connection closed');
  }
}

āđ€āļ‹āļ­āļĢāđŒāļ§āļīāļŠ Prisma āļžāļĢāđ‰āļ­āļĄāļ—āļĩāđˆāļˆāļ°āļ–āļđāļāļ‰āļĩāļ”āđ€āļ‚āđ‰āļēāđ„āļ›āļĒāļąāļ‡āđ€āļ‹āļ­āļĢāđŒāļ§āļīāļŠāļ­āļ·āđˆāļ™ āđ† āļ‚āļ­āļ‡āđāļ­āļ›āļžāļĨāļīāđ€āļ„āļŠāļąāļ™āđāļĨāđ‰āļ§

āļāļēāļĢāļŠāļĢāđ‰āļēāļ‡āđ‚āļĄāļ”āļđāļĨ Prisma āđāļšāļš Global

āđ€āļžāļ·āđˆāļ­āļ—āļģāđƒāļŦāđ‰āđ€āļ‹āļ­āļĢāđŒāļ§āļīāļŠ Prisma āđƒāļŠāđ‰āļ‡āļēāļ™āđ„āļ”āđ‰āļ—āļąāđˆāļ§āļ—āļąāđ‰āļ‡āđāļ­āļ›āļžāļĨāļīāđ€āļ„āļŠāļąāļ™āđ‚āļ”āļĒāđ„āļĄāđˆāļ•āđ‰āļ­āļ‡āļ™āļģāđ€āļ‚āđ‰āļēāļ­āļĒāđˆāļēāļ‡āļŠāļąāļ”āđ€āļˆāļ™āđƒāļ™āđāļ•āđˆāļĨāļ°āđ‚āļĄāļ”āļđāļĨ āļˆāļķāļ‡āđƒāļŠāđ‰āđ€āļ”āļ„āļ­āđ€āļĢāđ€āļ•āļ­āļĢāđŒ @Global()

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

// The @Global decorator makes this module available everywhere
@Global()
@Module({
  providers: [PrismaService],
  exports: [PrismaService],
})
export class PrismaModule {}

āļāļēāļĢāļ™āļģāđ€āļ‚āđ‰āļēāđƒāļ™āđ‚āļĄāļ”āļđāļĨāļĢāļēāļāļ—āļģāđƒāļŦāđ‰āđ€āļ‹āļ­āļĢāđŒāļ§āļīāļŠāļžāļĢāđ‰āļ­āļĄāđƒāļŠāđ‰āļ‡āļēāļ™āđ‚āļ”āļĒāļ­āļąāļ•āđ‚āļ™āļĄāļąāļ•āļī

src/app.module.tstypescript
import { Module } from '@nestjs/common';
import { PrismaModule } from './prisma/prisma.module';
import { UsersModule } from './users/users.module';
import { PostsModule } from './posts/posts.module';

@Module({
  imports: [
    PrismaModule,   // Single declaration is sufficient
    UsersModule,
    PostsModule,
  ],
})
export class AppModule {}

āļ•āļ­āļ™āļ™āļĩāđ‰āđ€āļ‹āļ­āļĢāđŒāļ§āļīāļŠāđƒāļ”āļāđ‡āļŠāļēāļĄāļēāļĢāļ–āļ‰āļĩāļ” PrismaService āđ„āļ”āđ‰āđ‚āļ”āļĒāđ„āļĄāđˆāļ•āđ‰āļ­āļ‡āļ™āļģāđ€āļ‚āđ‰āļēāđ€āļžāļīāđˆāļĄāđ€āļ•āļīāļĄ

āđ‚āļĄāļ”āļđāļĨ Global āđ€āļ—āļĩāļĒāļšāļāļąāļšāļāļēāļĢāļ™āļģāđ€āļ‚āđ‰āļēāđāļšāļšāļŠāļąāļ”āđ€āļˆāļ™

āđ‚āļĄāļ”āļđāļĨ Global āļŠāđˆāļ§āļĒāļĨāļ”āļ„āļ§āļēāļĄāļ‹āļąāļšāļ‹āđ‰āļ­āļ™āļ‚āļ­āļ‡āļŠāļ–āļēāļ›āļąāļ•āļĒāļāļĢāļĢāļĄ āđāļ•āđˆāļ—āļģāđƒāļŦāđ‰āļ”āļĩāđ€āļžāļ™āđ€āļ”āļ™āļ‹āļĩāļāļĨāļēāļĒāđ€āļ›āđ‡āļ™āđāļšāļšāļ‹āđˆāļ­āļ™ āļŠāļģāļŦāļĢāļąāļšāđāļ­āļ›āļžāļĨāļīāđ€āļ„āļŠāļąāļ™āļ‚āļ™āļēāļ”āđ€āļĨāđ‡āļāļāđ‡āļĒāļ­āļĄāļĢāļąāļšāđ„āļ”āđ‰ āđƒāļ™āđ‚āļ›āļĢāđ€āļˆāļāļ•āđŒāļ‚āļ™āļēāļ”āđƒāļŦāļāđˆ āļāļēāļĢāļ™āļģāđ€āļ‚āđ‰āļēāđāļšāļšāļŠāļąāļ”āđ€āļˆāļ™āļŠāđˆāļ§āļĒāđƒāļŦāđ‰āļ•āļīāļ”āļ•āļēāļĄāļ”āļĩāđ€āļžāļ™āđ€āļ”āļ™āļ‹āļĩāđ„āļ”āđ‰āļ‡āđˆāļēāļĒāļ‚āļķāđ‰āļ™

āļāļēāļĢāļāļģāļŦāļ™āļ”āļŠāļ„āļĩāļĄāļē Prisma

āđ„āļŸāļĨāđŒ schema.prisma āļāļģāļŦāļ™āļ”āđ‚āļĄāđ€āļ”āļĨāļ‚āđ‰āļ­āļĄāļđāļĨ āļ„āļ§āļēāļĄāļŠāļąāļĄāļžāļąāļ™āļ˜āđŒ āđāļĨāļ°āļ•āļąāļ§āđ€āļĨāļ·āļ­āļāļ‚āļ­āļ‡āļāļēāļ™āļ‚āđ‰āļ­āļĄāļđāļĨ Prisma āđƒāļŠāđ‰āļ āļēāļĐāļēāļ™āļīāļĒāļēāļĄāļŠāļ„āļĩāļĄāļēāļ‚āļ­āļ‡āļ•āļąāļ§āđ€āļ­āļ‡ (PSL)

prisma/schema.prismaprisma
generator client {
  provider = "prisma-client-js"
  // Enable types for advanced filtering queries
  previewFeatures = ["fullTextSearch"]
}

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

// User model with all relations
model User {
  id        String   @id @default(cuid())
  email     String   @unique
  password  String
  name      String
  role      Role     @default(USER)
  createdAt DateTime @default(now()) @map("created_at")
  updatedAt DateTime @updatedAt @map("updated_at")

  // One-to-many relations
  posts     Post[]
  comments  Comment[]
  profile   Profile?

  // Index for frequent searches
  @@index([email])
  @@map("users")
}

// Enum for user roles
enum Role {
  USER
  ADMIN
  MODERATOR
}

// One-to-one relation with User
model Profile {
  id       String  @id @default(cuid())
  bio      String?
  avatar   String?
  website  String?
  userId   String  @unique @map("user_id")

  user     User    @relation(fields: [userId], references: [id], onDelete: Cascade)

  @@map("profiles")
}

// Posts with many-to-one relation to User
model Post {
  id          String    @id @default(cuid())
  title       String
  slug        String    @unique
  content     String
  excerpt     String?
  published   Boolean   @default(false)
  publishedAt DateTime? @map("published_at")
  authorId    String    @map("author_id")
  createdAt   DateTime  @default(now()) @map("created_at")
  updatedAt   DateTime  @updatedAt @map("updated_at")

  // Relations
  author      User      @relation(fields: [authorId], references: [id], onDelete: Cascade)
  comments    Comment[]
  categories  CategoriesOnPosts[]

  @@index([authorId])
  @@index([slug])
  @@map("posts")
}

// Comments with dual relation
model Comment {
  id        String   @id @default(cuid())
  content   String
  postId    String   @map("post_id")
  authorId  String   @map("author_id")
  createdAt DateTime @default(now()) @map("created_at")

  post      Post     @relation(fields: [postId], references: [id], onDelete: Cascade)
  author    User     @relation(fields: [authorId], references: [id], onDelete: Cascade)

  @@index([postId])
  @@map("comments")
}

// Categories for posts
model Category {
  id    String @id @default(cuid())
  name  String @unique
  slug  String @unique

  posts CategoriesOnPosts[]

  @@map("categories")
}

// Pivot table for many-to-many relation
model CategoriesOnPosts {
  postId     String   @map("post_id")
  categoryId String   @map("category_id")
  assignedAt DateTime @default(now()) @map("assigned_at")

  post       Post     @relation(fields: [postId], references: [id], onDelete: Cascade)
  category   Category @relation(fields: [categoryId], references: [id], onDelete: Cascade)

  @@id([postId, categoryId])
  @@map("categories_on_posts")
}

āļŦāļĨāļąāļ‡āļˆāļēāļāđāļāđ‰āđ„āļ‚āļŠāļ„āļĩāļĄāļēāđāļĨāđ‰āļ§ āļĄāļēāļĒāđ€āļāļĢāļŠāļąāļ™āļˆāļ°āļ™āļģāļāļēāļĢāđ€āļ›āļĨāļĩāđˆāļĒāļ™āđāļ›āļĨāļ‡āđ„āļ›āđƒāļŠāđ‰āļāļąāļšāļāļēāļ™āļ‚āđ‰āļ­āļĄāļđāļĨ

bash
# terminal
# Create a migration with descriptive name
npx prisma migrate dev --name init_schema

# Generate Prisma client (automatic after migrate dev)
npx prisma generate

# View schema in browser
npx prisma studio

āļāļēāļĢāļžāļąāļ’āļ™āļēāđ€āļ‹āļ­āļĢāđŒāļ§āļīāļŠāļœāļđāđ‰āđƒāļŠāđ‰āļ”āđ‰āļ§āļĒ Prisma

āđ€āļ‹āļ­āļĢāđŒāļ§āļīāļŠ Users āđāļŠāļ”āļ‡āđƒāļŦāđ‰āđ€āļŦāđ‡āļ™āļ–āļķāļ‡āļāļēāļĢāļ”āļģāđ€āļ™āļīāļ™āļāļēāļĢ CRUD āļ—āļąāđˆāļ§āđ„āļ›āļāļąāļš Prisma āļāļēāļĢāļāļģāļŦāļ™āļ”āļŠāļ™āļīāļ”āļ­āļąāļ•āđ‚āļ™āļĄāļąāļ•āļīāļŠāđˆāļ§āļĒāđƒāļŦāđ‰āđ‚āļ„āđ‰āļ”āđāļĨāļ°āļāļēāļ™āļ‚āđ‰āļ­āļĄāļđāļĨāļŠāļ­āļ”āļ„āļĨāđ‰āļ­āļ‡āļāļąāļ™

src/users/users.service.tstypescript
import {
  Injectable,
  NotFoundException,
  ConflictException,
  BadRequestException,
} from '@nestjs/common';
import { PrismaService } from '../prisma/prisma.service';
import { CreateUserDto } from './dto/create-user.dto';
import { UpdateUserDto } from './dto/update-user.dto';
import { User, Prisma } from '@prisma/client';
import * as bcrypt from 'bcrypt';

// Type for results without password
type SafeUser = Omit<User, 'password'>;

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

  // Default selection without password
  private readonly safeSelect: Prisma.UserSelect = {
    id: true,
    email: true,
    name: true,
    role: true,
    createdAt: true,
    updatedAt: true,
    profile: true,
  };

  async create(createUserDto: CreateUserDto): Promise<SafeUser> {
    // Check email uniqueness
    const existingUser = await this.prisma.user.findUnique({
      where: { email: createUserDto.email },
    });

    if (existingUser) {
      throw new ConflictException('This email is already in use');
    }

    // Secure password hashing
    const hashedPassword = await bcrypt.hash(createUserDto.password, 12);

    // Creation with optional profile
    return this.prisma.user.create({
      data: {
        email: createUserDto.email,
        password: hashedPassword,
        name: createUserDto.name,
        // Nested profile creation if provided
        profile: createUserDto.bio ? {
          create: {
            bio: createUserDto.bio,
          },
        } : undefined,
      },
      select: this.safeSelect,
    });
  }

  async findAll(params: {
    page?: number;
    limit?: number;
    search?: string;
  }): Promise<{ data: SafeUser[]; total: number; pages: number }> {
    const { page = 1, limit = 10, search } = params;
    const skip = (page - 1) * limit;

    // Optional search condition
    const where: Prisma.UserWhereInput = search
      ? {
          OR: [
            { email: { contains: search, mode: 'insensitive' } },
            { name: { contains: search, mode: 'insensitive' } },
          ],
        }
      : {};

    // Parallel execution for performance
    const [data, total] = await this.prisma.$transaction([
      this.prisma.user.findMany({
        where,
        skip,
        take: limit,
        orderBy: { createdAt: 'desc' },
        select: this.safeSelect,
      }),
      this.prisma.user.count({ where }),
    ]);

    return {
      data,
      total,
      pages: Math.ceil(total / limit),
    };
  }

  async findOne(id: string): Promise<SafeUser> {
    const user = await this.prisma.user.findUnique({
      where: { id },
      select: {
        ...this.safeSelect,
        // Include recent posts
        posts: {
          take: 5,
          orderBy: { createdAt: 'desc' },
          select: {
            id: true,
            title: true,
            slug: true,
            published: true,
          },
        },
      },
    });

    if (!user) {
      throw new NotFoundException(`User with ID ${id} not found`);
    }

    return user;
  }

  async update(id: string, updateUserDto: UpdateUserDto): Promise<SafeUser> {
    // Check existence
    await this.findOne(id);

    // Update with nested profile handling
    return this.prisma.user.update({
      where: { id },
      data: {
        name: updateUserDto.name,
        // Update or create profile
        profile: updateUserDto.bio ? {
          upsert: {
            create: { bio: updateUserDto.bio },
            update: { bio: updateUserDto.bio },
          },
        } : undefined,
      },
      select: this.safeSelect,
    });
  }

  async remove(id: string): Promise<void> {
    await this.findOne(id);
    // Deletion cascades to profile and posts
    await this.prisma.user.delete({ where: { id } });
  }
}

āļāļēāļĢāļāļģāļŦāļ™āļ”āļŠāļ™āļīāļ”āļ‚āļ­āļ‡ Prisma āļŠāđˆāļ§āļĒāļĢāļąāļšāļ›āļĢāļ°āļāļąāļ™āļ§āđˆāļēāļ—āļļāļāļ„āļļāļ“āļŠāļĄāļšāļąāļ•āļīāļ—āļĩāđˆāđƒāļŠāđ‰āļĄāļĩāļ­āļĒāļđāđˆāđƒāļ™āļŠāļ„āļĩāļĄāļē

āļžāļĢāđ‰āļ­āļĄāļ—āļĩāđˆāļˆāļ°āļžāļīāļŠāļīāļ•āļāļēāļĢāļŠāļąāļĄāļ āļēāļĐāļ“āđŒ Node.js / NestJS āđāļĨāđ‰āļ§āļŦāļĢāļ·āļ­āļĒāļąāļ‡āļ„āļĢāļąāļš?

āļāļķāļāļāļ™āļ”āđ‰āļ§āļĒāļ•āļąāļ§āļˆāļģāļĨāļ­āļ‡āđāļšāļšāđ‚āļ•āđ‰āļ•āļ­āļš, flashcards āđāļĨāļ°āđāļšāļšāļ—āļ”āļŠāļ­āļšāđ€āļ—āļ„āļ™āļīāļ„āļ„āļĢāļąāļš

āļāļēāļĢāļˆāļąāļ”āļāļēāļĢāļ„āļ§āļēāļĄāļŠāļąāļĄāļžāļąāļ™āļ˜āđŒāļ”āđ‰āļ§āļĒ Prisma

Prisma āļ—āļģāđƒāļŦāđ‰āļāļēāļĢāļˆāļąāļ”āļāļēāļĢāļ„āļ§āļēāļĄāļŠāļąāļĄāļžāļąāļ™āļ˜āđŒāļ—āļĩāđˆāļ‹āļąāļšāļ‹āđ‰āļ­āļ™āđ€āļ›āđ‡āļ™āđ€āļĢāļ·āđˆāļ­āļ‡āļ‡āđˆāļēāļĒ āļ„āļīāļ§āļĢāļĩāđāļšāļšāļ‹āđ‰āļ­āļ™āļŠāđˆāļ§āļĒāđƒāļŦāđ‰āđ‚āļŦāļĨāļ”āļ‚āđ‰āļ­āļĄāļđāļĨāļ—āļĩāđˆāđ€āļāļĩāđˆāļĒāļ§āļ‚āđ‰āļ­āļ‡āđ„āļ”āđ‰āđƒāļ™āļ„āļģāļ‚āļ­āļ„āļĢāļąāđ‰āļ‡āđ€āļ”āļĩāļĒāļ§

src/posts/posts.service.tstypescript
import { Injectable, NotFoundException, ForbiddenException } from '@nestjs/common';
import { PrismaService } from '../prisma/prisma.service';
import { CreatePostDto } from './dto/create-post.dto';
import { UpdatePostDto } from './dto/update-post.dto';
import { Prisma } from '@prisma/client';

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

  async create(authorId: string, createPostDto: CreatePostDto) {
    // Automatic slug generation from title
    const slug = this.generateSlug(createPostDto.title);

    return this.prisma.post.create({
      data: {
        title: createPostDto.title,
        slug,
        content: createPostDto.content,
        excerpt: createPostDto.excerpt,
        // Connect to existing author
        author: {
          connect: { id: authorId },
        },
        // Connect to existing categories
        categories: createPostDto.categoryIds ? {
          create: createPostDto.categoryIds.map(categoryId => ({
            category: { connect: { id: categoryId } },
          })),
        } : undefined,
      },
      include: {
        author: {
          select: { id: true, name: true },
        },
        categories: {
          include: {
            category: true,
          },
        },
      },
    });
  }

  async findAllPublished(params: {
    page?: number;
    limit?: number;
    categorySlug?: string;
  }) {
    const { page = 1, limit = 10, categorySlug } = params;
    const skip = (page - 1) * limit;

    // Conditional filter by category
    const where: Prisma.PostWhereInput = {
      published: true,
      ...(categorySlug && {
        categories: {
          some: {
            category: { slug: categorySlug },
          },
        },
      }),
    };

    const [posts, total] = await this.prisma.$transaction([
      this.prisma.post.findMany({
        where,
        skip,
        take: limit,
        orderBy: { publishedAt: 'desc' },
        include: {
          author: {
            select: { id: true, name: true },
          },
          categories: {
            include: {
              category: { select: { name: true, slug: true } },
            },
          },
          _count: {
            select: { comments: true },
          },
        },
      }),
      this.prisma.post.count({ where }),
    ]);

    return { posts, total, pages: Math.ceil(total / limit) };
  }

  async findBySlug(slug: string) {
    const post = await this.prisma.post.findUnique({
      where: { slug },
      include: {
        author: {
          select: { id: true, name: true, profile: true },
        },
        categories: {
          include: {
            category: true,
          },
        },
        comments: {
          orderBy: { createdAt: 'desc' },
          take: 20,
          include: {
            author: {
              select: { id: true, name: true },
            },
          },
        },
      },
    });

    if (!post) {
      throw new NotFoundException(`Post "${slug}" not found`);
    }

    return post;
  }

  async publish(id: string, authorId: string) {
    // Verify author is the owner
    const post = await this.prisma.post.findUnique({
      where: { id },
      select: { authorId: true },
    });

    if (!post) {
      throw new NotFoundException(`Post with ID ${id} not found`);
    }

    if (post.authorId !== authorId) {
      throw new ForbiddenException('Publication not authorized');
    }

    return this.prisma.post.update({
      where: { id },
      data: {
        published: true,
        publishedAt: new Date(),
      },
    });
  }

  private generateSlug(title: string): string {
    return title
      .toLowerCase()
      .normalize('NFD')
      .replace(/[\u0300-\u036f]/g, '')
      .replace(/[^a-z0-9]+/g, '-')
      .replace(/(^-|-$)/g, '');
  }
}

include āļŠāđˆāļ§āļĒāđƒāļŦāđ‰āļ”āļķāļ‡āļ„āļ§āļēāļĄāļŠāļąāļĄāļžāļąāļ™āļ˜āđŒāđ€āļŠāļīāļ‡āļĨāļķāļāđ„āļ”āđ‰āļžāļĢāđ‰āļ­āļĄāļ„āļ§āļšāļ„āļļāļĄāļ§āđˆāļēāļˆāļ°āļŠāđˆāļ‡āļ„āļ·āļ™āļŸāļīāļĨāļ”āđŒāđƒāļ”āļšāđ‰āļēāļ‡

āļ—āļĢāļēāļ™āđāļ‹āļāļŠāļąāļ™āđāļĨāļ°āļāļēāļĢāļ”āļģāđ€āļ™āļīāļ™āļ‡āļēāļ™āđāļšāļšāļ­āļ°āļ•āļ­āļĄāļīāļ

Prisma āļĄāļĩāļŦāļĨāļēāļĒāļ§āļīāļ˜āļĩāđƒāļ™āļāļēāļĢāļĢāļąāļšāļ›āļĢāļ°āļāļąāļ™āļ„āļ§āļēāļĄāđ€āļ›āđ‡āļ™āļ­āļ°āļ•āļ­āļĄāļīāļāļ‚āļ­āļ‡āļāļēāļĢāļ”āļģāđ€āļ™āļīāļ™āļ‡āļēāļ™ āļ—āļĢāļēāļ™āđāļ‹āļāļŠāļąāļ™āđāļšāļšāļ­āļīāļ™āđ€āļ—āļ­āļĢāđŒāđāļ­āļ„āļ—āļĩāļŸāđƒāļŦāđ‰āļ„āļ§āļēāļĄāļĒāļ·āļ”āļŦāļĒāļļāđˆāļ™āļĄāļēāļāļ—āļĩāđˆāļŠāļļāļ”

src/orders/orders.service.tstypescript
import { Injectable, BadRequestException } from '@nestjs/common';
import { PrismaService } from '../prisma/prisma.service';
import { CreateOrderDto } from './dto/create-order.dto';

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

  async createOrder(userId: string, createOrderDto: CreateOrderDto) {
    // Interactive transaction to ensure atomicity
    return this.prisma.$transaction(async (tx) => {
      // 1. Verify stock for each item
      const items = await Promise.all(
        createOrderDto.items.map(async (item) => {
          const product = await tx.product.findUnique({
            where: { id: item.productId },
          });

          if (!product) {
            throw new BadRequestException(
              `Product ${item.productId} not found`
            );
          }

          if (product.stock < item.quantity) {
            throw new BadRequestException(
              `Insufficient stock for ${product.name}`
            );
          }

          return { product, quantity: item.quantity };
        })
      );

      // 2. Calculate total
      const total = items.reduce(
        (sum, { product, quantity }) => sum + product.price * quantity,
        0
      );

      // 3. Create order
      const order = await tx.order.create({
        data: {
          userId,
          total,
          status: 'PENDING',
          items: {
            create: items.map(({ product, quantity }) => ({
              productId: product.id,
              quantity,
              price: product.price,
            })),
          },
        },
        include: {
          items: {
            include: { product: true },
          },
        },
      });

      // 4. Update stock
      await Promise.all(
        items.map(({ product, quantity }) =>
          tx.product.update({
            where: { id: product.id },
            data: { stock: { decrement: quantity } },
          })
        )
      );

      return order;
    });
  }

  async cancelOrder(orderId: string, userId: string) {
    return this.prisma.$transaction(async (tx) => {
      // Get order with items
      const order = await tx.order.findUnique({
        where: { id: orderId },
        include: { items: true },
      });

      if (!order || order.userId !== userId) {
        throw new BadRequestException('Order not found');
      }

      if (order.status !== 'PENDING') {
        throw new BadRequestException(
          'Only pending orders can be cancelled'
        );
      }

      // Restore stock
      await Promise.all(
        order.items.map((item) =>
          tx.product.update({
            where: { id: item.productId },
            data: { stock: { increment: item.quantity } },
          })
        )
      );

      // Update status
      return tx.order.update({
        where: { id: orderId },
        data: { status: 'CANCELLED' },
      });
    });
  }
}

āļ—āļĢāļēāļ™āđāļ‹āļāļŠāļąāļ™āļĢāļąāļšāļ›āļĢāļ°āļāļąāļ™āļ§āđˆāļēāļ—āļļāļāļāļēāļĢāļ”āļģāđ€āļ™āļīāļ™āļ‡āļēāļ™āļˆāļ°āļŠāļģāđ€āļĢāđ‡āļˆāļĢāđˆāļ§āļĄāļāļąāļ™āļŦāļĢāļ·āļ­āļĨāđ‰āļĄāđ€āļŦāļĨāļ§āļĢāđˆāļ§āļĄāļāļąāļ™

āļĢāļ°āļĒāļ°āļŦāļĄāļ”āđ€āļ§āļĨāļēāļ‚āļ­āļ‡āļ—āļĢāļēāļ™āđāļ‹āļāļŠāļąāļ™

āđ‚āļ”āļĒāļ„āđˆāļēāđ€āļĢāļīāđˆāļĄāļ•āđ‰āļ™ āļ—āļĢāļēāļ™āđāļ‹āļāļŠāļąāļ™āļ‚āļ­āļ‡ Prisma āļĄāļĩāļĢāļ°āļĒāļ°āļŦāļĄāļ”āđ€āļ§āļĨāļē 5 āļ§āļīāļ™āļēāļ—āļĩ āļŠāļģāļŦāļĢāļąāļšāļāļēāļĢāļ”āļģāđ€āļ™āļīāļ™āļ‡āļēāļ™āļ—āļĩāđˆāđƒāļŠāđ‰āđ€āļ§āļĨāļēāļ™āļēāļ™ āļŠāļēāļĄāļēāļĢāļ–āļ›āļĢāļąāļšāđ„āļ”āđ‰āļ”āđ‰āļ§āļĒ $transaction([...], { timeout: 10000 })

āļĄāļīāļ”āđ€āļ”āļīāļĨāđāļ§āļĢāđŒāļ‚āļ­āļ‡ Prisma āļŠāļģāļŦāļĢāļąāļšāļāļēāļĢāļ•āļĢāļ§āļˆāļŠāļ­āļš

āļĄāļīāļ”āđ€āļ”āļīāļĨāđāļ§āļĢāđŒāļ‚āļ­āļ‡ Prisma āļŠāđˆāļ§āļĒāđƒāļŦāđ‰āļŠāļēāļĄāļēāļĢāļ–āļ”āļąāļāļˆāļąāļšāļ„āļīāļ§āļĢāļĩāđ€āļžāļ·āđˆāļ­āđ€āļžāļīāđˆāļĄāļžāļĪāļ•āļīāļāļĢāļĢāļĄāđāļšāļšāļ‚āļ§āļēāļ‡ āđ€āļŠāđˆāļ™ āļāļēāļĢāļ•āļĢāļ§āļˆāļŠāļ­āļšāļŦāļĢāļ·āļ­ soft delete

src/prisma/prisma.service.ts (version with middleware)typescript
import { Injectable, OnModuleInit, OnModuleDestroy, Logger } from '@nestjs/common';
import { PrismaClient, Prisma } from '@prisma/client';

@Injectable()
export class PrismaService extends PrismaClient implements OnModuleInit, OnModuleDestroy {
  private readonly logger = new Logger(PrismaService.name);

  constructor() {
    super({
      log: [
        { level: 'query', emit: 'event' },
        { level: 'error', emit: 'stdout' },
      ],
    });

    // Middleware for modification auditing
    this.$use(async (params: Prisma.MiddlewareParams, next) => {
      const start = Date.now();

      // Execute query
      const result = await next(params);

      const duration = Date.now() - start;

      // Log slow queries (> 100ms)
      if (duration > 100) {
        this.logger.warn(
          `Slow query: ${params.model}.${params.action} - ${duration}ms`
        );
      }

      // Audit write operations
      if (['create', 'update', 'delete'].includes(params.action)) {
        this.logger.log(
          `Audit: ${params.action} on ${params.model} - ${duration}ms`
        );
      }

      return result;
    });

    // Middleware for automatic soft delete
    this.$use(async (params, next) => {
      // Transform delete to update for certain models
      if (params.model === 'User' && params.action === 'delete') {
        params.action = 'update';
        params.args['data'] = { deletedAt: new Date() };
      }

      // Automatic exclusion of deleted records
      if (params.model === 'User' && params.action === 'findMany') {
        if (!params.args) params.args = {};
        if (!params.args.where) params.args.where = {};
        params.args.where.deletedAt = null;
      }

      return next(params);
    });
  }

  async onModuleInit() {
    await this.$connect();
  }

  async onModuleDestroy() {
    await this.$disconnect();
  }
}

āļĄāļīāļ”āđ€āļ”āļīāļĨāđāļ§āļĢāđŒāļˆāļ°āļ—āļģāļ‡āļēāļ™āļ•āļēāļĄāļĨāļģāļ”āļąāļšāļ—āļĩāđˆāļĨāļ‡āļ—āļ°āđ€āļšāļĩāļĒāļ™āđāļĨāļ°āļŠāļēāļĄāļēāļĢāļ–āđāļāđ‰āđ„āļ‚āļžāļēāļĢāļēāļĄāļīāđ€āļ•āļ­āļĢāđŒāļ‚āļ­āļ‡āļ„āļīāļ§āļĢāļĩāđ„āļ”āđ‰

āļāļēāļĢāļ›āļĢāļąāļšāļ›āļĢāļļāļ‡āļ›āļĢāļ°āļŠāļīāļ—āļ˜āļīāļ āļēāļžāļ”āđ‰āļ§āļĒ Prisma

āļĄāļĩāļŦāļĨāļēāļĒāđ€āļ—āļ„āļ™āļīāļ„āļ—āļĩāđˆāļŠāđˆāļ§āļĒāļ›āļĢāļąāļšāļ›āļĢāļļāļ‡āļ›āļĢāļ°āļŠāļīāļ—āļ˜āļīāļ āļēāļžāļ‚āļ­āļ‡āļ„āļīāļ§āļĢāļĩ Prisma āđƒāļ™āđāļ­āļ›āļžāļĨāļīāđ€āļ„āļŠāļąāļ™ NestJS

src/common/prisma-extensions.tstypescript
import { Prisma, PrismaClient } from '@prisma/client';

// Extension for standardized pagination
export const paginationExtension = Prisma.defineExtension({
  model: {
    $allModels: {
      async paginate<T, A>(
        this: T,
        args: Prisma.Exact<A, Prisma.Args<T, 'findMany'>> & {
          page?: number;
          limit?: number;
        }
      ): Promise<{
        data: Prisma.Result<T, A, 'findMany'>;
        meta: { page: number; limit: number; total: number; pages: number };
      }> {
        const { page = 1, limit = 10, ...rest } = args as any;
        const skip = (page - 1) * limit;

        const context = Prisma.getExtensionContext(this);
        const [data, total] = await Promise.all([
          (context as any).findMany({ ...rest, skip, take: limit }),
          (context as any).count({ where: (rest as any).where }),
        ]);

        return {
          data,
          meta: {
            page,
            limit,
            total,
            pages: Math.ceil(total / limit),
          },
        };
      },
    },
  },
});

// Usage in service
// const result = await this.prisma.$extends(paginationExtension)
//   .user.paginate({ page: 2, limit: 20, where: { role: 'USER' } });

āļŠāļģāļŦāļĢāļąāļšāļ„āļīāļ§āļĢāļĩāļ—āļĩāđˆāđ€āļĢāļĩāļĒāļāđƒāļŠāđ‰āļšāđˆāļ­āļĒ āđāļ„āļŠāļˆāļ°āļŠāđˆāļ§āļĒāļ›āļĢāļąāļšāļ›āļĢāļļāļ‡āđ€āļ§āļĨāļēāļ•āļ­āļšāļŠāļ™āļ­āļ‡āļ­āļĒāđˆāļēāļ‡āļĄāļĩāļ™āļąāļĒāļŠāļģāļ„āļąāļ

src/posts/posts.service.ts (with cache)typescript
import { Injectable, Inject } from '@nestjs/common';
import { CACHE_MANAGER } from '@nestjs/cache-manager';
import { Cache } from 'cache-manager';
import { PrismaService } from '../prisma/prisma.service';

@Injectable()
export class PostsService {
  constructor(
    private readonly prisma: PrismaService,
    @Inject(CACHE_MANAGER) private cacheManager: Cache,
  ) {}

  async findPopularPosts() {
    const cacheKey = 'posts:popular';

    // Try to get from cache
    const cached = await this.cacheManager.get(cacheKey);
    if (cached) {
      return cached;
    }

    // Database query
    const posts = await this.prisma.post.findMany({
      where: { published: true },
      orderBy: { comments: { _count: 'desc' } },
      take: 10,
      include: {
        author: { select: { name: true } },
        _count: { select: { comments: true } },
      },
    });

    // Cache for 5 minutes
    await this.cacheManager.set(cacheKey, posts, 300000);

    return posts;
  }

  async invalidateCache(postId: string) {
    // Selective cache invalidation
    await this.cacheManager.del('posts:popular');
    await this.cacheManager.del(`post:${postId}`);
  }
}

āļāļēāļĢāļ—āļ”āļŠāļ­āļšāļ”āđ‰āļ§āļĒ Prisma āđāļĨāļ° NestJS

āļāļēāļĢāļ—āļ”āļŠāļ­āļšāļ•āđ‰āļ­āļ‡āđƒāļŠāđ‰āļāļĨāļĒāļļāļ—āļ˜āđŒāļāļēāļ™āļ‚āđ‰āļ­āļĄāļđāļĨāđāļšāļšāđāļĒāļāļ­āļīāļŠāļĢāļ° āļāļēāļĢāđƒāļŠāđ‰āļāļēāļ™āļ‚āđ‰āļ­āļĄāļđāļĨāđ€āļ‰āļžāļēāļ°āļŠāļģāļŦāļĢāļąāļšāļāļēāļĢāļ—āļ”āļŠāļ­āļšāļŠāđˆāļ§āļĒāļĢāļąāļšāļ›āļĢāļ°āļāļąāļ™āļ„āļ§āļēāļĄāļŠāļēāļĄāļēāļĢāļ–āđƒāļ™āļāļēāļĢāļ—āļģāļ‹āđ‰āļģ

test/helpers/prisma-test.helper.tstypescript
import { PrismaClient } from '@prisma/client';
import { execSync } from 'child_process';

const prisma = new PrismaClient();

export async function setupTestDatabase() {
  // Use a test database
  process.env.DATABASE_URL = process.env.TEST_DATABASE_URL;

  // Apply migrations
  execSync('npx prisma migrate deploy', {
    env: { ...process.env, DATABASE_URL: process.env.TEST_DATABASE_URL },
  });
}

export async function cleanupTestDatabase() {
  // Delete all data in dependency order
  const tablenames = await prisma.$queryRaw<Array<{ tablename: string }>>`
    SELECT tablename FROM pg_tables WHERE schemaname='public'
  `;

  for (const { tablename } of tablenames) {
    if (tablename !== '_prisma_migrations') {
      await prisma.$executeRawUnsafe(
        `TRUNCATE TABLE "public"."${tablename}" CASCADE;`
      );
    }
  }
}

export async function disconnectTestDatabase() {
  await prisma.$disconnect();
}

āļāļēāļĢāļ—āļ”āļŠāļ­āļšāđ€āļŠāļīāļ‡āļĢāļ§āļĄāđƒāļŠāđ‰āļ•āļąāļ§āļŠāđˆāļ§āļĒāđ€āļŦāļĨāđˆāļēāļ™āļĩāđ‰āđ€āļžāļ·āđˆāļ­āļĢāļąāļšāļ›āļĢāļ°āļāļąāļ™āļŠāļ āļēāļžāđāļ§āļ”āļĨāđ‰āļ­āļĄāļ—āļĩāđˆāļŠāļ°āļ­āļēāļ”

test/users.e2e-spec.tstypescript
import { Test, TestingModule } from '@nestjs/testing';
import { INestApplication, ValidationPipe } from '@nestjs/common';
import * as request from 'supertest';
import { AppModule } from '../src/app.module';
import { PrismaService } from '../src/prisma/prisma.service';
import { cleanupTestDatabase, setupTestDatabase } from './helpers/prisma-test.helper';

describe('UsersController (e2e)', () => {
  let app: INestApplication;
  let prisma: PrismaService;

  beforeAll(async () => {
    await setupTestDatabase();

    const moduleFixture: TestingModule = await Test.createTestingModule({
      imports: [AppModule],
    }).compile();

    app = moduleFixture.createNestApplication();
    app.useGlobalPipes(new ValidationPipe({ whitelist: true }));

    prisma = app.get(PrismaService);
    await app.init();
  });

  beforeEach(async () => {
    await cleanupTestDatabase();
  });

  afterAll(async () => {
    await app.close();
  });

  describe('POST /users', () => {
    it('should create a new user', async () => {
      const createUserDto = {
        email: 'test@example.com',
        password: 'Password123!',
        name: 'Test User',
      };

      const response = await request(app.getHttpServer())
        .post('/users')
        .send(createUserDto)
        .expect(201);

      expect(response.body).toMatchObject({
        email: createUserDto.email,
        name: createUserDto.name,
      });
      expect(response.body.password).toBeUndefined();
    });

    it('should reject duplicate email', async () => {
      const createUserDto = {
        email: 'duplicate@example.com',
        password: 'Password123!',
        name: 'First User',
      };

      await request(app.getHttpServer())
        .post('/users')
        .send(createUserDto)
        .expect(201);

      await request(app.getHttpServer())
        .post('/users')
        .send({ ...createUserDto, name: 'Second User' })
        .expect(409);
    });
  });
});

āļšāļ—āļŠāļĢāļļāļ›

NestJS āđāļĨāļ° Prisma āļāđˆāļ­āđƒāļŦāđ‰āđ€āļāļīāļ”āļŠāđāļ•āļāđāļšāđ‡āļāđ€āļ­āļ™āļ”āđŒāļ—āļĩāđˆāļ—āļąāļ™āļŠāļĄāļąāļĒāđāļĨāļ°āļĄāļĩāļœāļĨāļīāļ•āļ āļēāļžāļŠāļđāļ‡ āļāļēāļĢāļāļģāļŦāļ™āļ”āļŠāļ™āļīāļ”āļ­āļąāļ•āđ‚āļ™āļĄāļąāļ•āļī āļĄāļēāļĒāđ€āļāļĢāļŠāļąāļ™āđāļšāļšāļ›āļĢāļ°āļāļēāļĻ āđāļĨāļ°āļāļēāļĢāļĢāļ§āļĄāđ€āļ‚āđ‰āļēāļāļąāļš NestJS āđāļšāļšāđ€āļ™āļ—āļĩāļŸāļŠāđˆāļ§āļĒāđƒāļŦāđ‰āļžāļąāļ’āļ™āļē API āļ—āļĩāđˆāđāļ‚āđ‡āļ‡āđāļĢāļ‡āđ„āļ”āđ‰āļ­āļĒāđˆāļēāļ‡āļĢāļ§āļ”āđ€āļĢāđ‡āļ§

āđ€āļŠāđ‡āļāļĨāļīāļŠāļ•āđŒāļŠāļģāļŦāļĢāļąāļšāļāļēāļĢāļĢāļ§āļĄ NestJS + Prisma āļ—āļĩāđˆāļ›āļĢāļ°āļŠāļšāļ„āļ§āļēāļĄāļŠāļģāđ€āļĢāđ‡āļˆ

  • ✅ āđ‚āļĄāļ”āļđāļĨ Prisma āđāļšāļš Global āđ€āļžāļ·āđˆāļ­āļ—āļģāđƒāļŦāđ‰āļāļēāļĢāļ‰āļĩāļ”āļ‡āđˆāļēāļĒāļ‚āļķāđ‰āļ™
  • ✅ āļŠāļ„āļĩāļĄāļē Prisma āļžāļĢāđ‰āļ­āļĄāļ„āļ§āļēāļĄāļŠāļąāļĄāļžāļąāļ™āļ˜āđŒāđāļĨāļ°āļ”āļąāļŠāļ™āļĩāļ—āļĩāđˆāđ€āļŦāļĄāļēāļ°āļŠāļĄ
  • ✅ āđ€āļ‹āļ­āļĢāđŒāļ§āļīāļŠāđāļšāļšāļāļģāļŦāļ™āļ”āļŠāļ™āļīāļ”āļžāļĢāđ‰āļ­āļĄāļāļēāļĢāđ€āļĨāļ·āļ­āļāļŸāļīāļĨāļ”āđŒāļ­āļĒāđˆāļēāļ‡āļŠāļąāļ”āđ€āļˆāļ™
  • ✅ āļ—āļĢāļēāļ™āđāļ‹āļāļŠāļąāļ™āļŠāļģāļŦāļĢāļąāļšāļāļēāļĢāļ”āļģāđ€āļ™āļīāļ™āļ‡āļēāļ™āđāļšāļšāļ­āļ°āļ•āļ­āļĄāļīāļ
  • ✅ āļĄāļīāļ”āđ€āļ”āļīāļĨāđāļ§āļĢāđŒāļŠāļģāļŦāļĢāļąāļšāļāļēāļĢāļ•āļĢāļ§āļˆāļŠāļ­āļšāđāļĨāļ° soft delete
  • ✅ āđāļ„āļŠāļŠāļģāļŦāļĢāļąāļšāļ„āļīāļ§āļĢāļĩāļ—āļĩāđˆāđ€āļĢāļĩāļĒāļāđƒāļŠāđ‰āļšāđˆāļ­āļĒ
  • ✅ āļāļēāļĢāļ—āļ”āļŠāļ­āļšāđ€āļŠāļīāļ‡āļĢāļ§āļĄāļžāļĢāđ‰āļ­āļĄāļāļēāļ™āļ‚āđ‰āļ­āļĄāļđāļĨāđāļĒāļāļ­āļīāļŠāļĢāļ°
  • ✅ āļāļēāļĢāđāļšāđˆāļ‡āļŦāļ™āđ‰āļēāļĄāļēāļ•āļĢāļāļēāļ™āļœāđˆāļēāļ™ extensions

āđ€āļĢāļīāđˆāļĄāļāļķāļāļ‹āđ‰āļ­āļĄāđ€āļĨāļĒ!

āļ—āļ”āļŠāļ­āļšāļ„āļ§āļēāļĄāļĢāļđāđ‰āļ‚āļ­āļ‡āļ„āļļāļ“āļ”āđ‰āļ§āļĒāļ•āļąāļ§āļˆāļģāļĨāļ­āļ‡āļŠāļąāļĄāļ āļēāļĐāļ“āđŒāđāļĨāļ°āđāļšāļšāļ—āļ”āļŠāļ­āļšāđ€āļ—āļ„āļ™āļīāļ„āļ„āļĢāļąāļš

āļāļēāļĢāļœāļŠāļĄāļœāļŠāļēāļ™āļ™āļĩāđ‰āđƒāļŠāđ‰āļˆāļļāļ”āđāļ‚āđ‡āļ‡āļ‚āļ­āļ‡āđāļ•āđˆāļĨāļ°āđ€āļ„āļĢāļ·āđˆāļ­āļ‡āļĄāļ·āļ­ āđ„āļ”āđ‰āđāļāđˆ āļŠāļ–āļēāļ›āļąāļ•āļĒāļāļĢāļĢāļĄāđ‚āļĄāļ”āļđāļĨāļ‚āļ­āļ‡ NestJS āļŠāļģāļŦāļĢāļąāļšāđ‚āļ„āļĢāļ‡āļŠāļĢāđ‰āļēāļ‡ āđāļĨāļ° Prisma āļŠāļģāļŦāļĢāļąāļšāļŠāļąāđ‰āļ™āļ‚āđ‰āļ­āļĄāļđāļĨāļ—āļĩāđˆāļ›āļĨāļ­āļ”āļ āļąāļĒāļ”āđ‰āļēāļ™āļŠāļ™āļīāļ” āļœāļĨāļĨāļąāļžāļ˜āđŒāļ„āļ·āļ­āđ‚āļ„āđ‰āļ”āļ—āļĩāđˆāļšāļģāļĢāļļāļ‡āļĢāļąāļāļĐāļēāļ‡āđˆāļēāļĒ āļ—āļ”āļŠāļ­āļšāđ„āļ”āđ‰ āđāļĨāļ°āļĄāļĩāļ›āļĢāļ°āļŠāļīāļ—āļ˜āļīāļ āļēāļžāļŠāļđāļ‡ āđ€āļŦāļĄāļēāļ°āļŠāļģāļŦāļĢāļąāļšāđāļ­āļ›āļžāļĨāļīāđ€āļ„āļŠāļąāļ™āļĢāļ°āļ”āļąāļšāļ­āļ‡āļ„āđŒāļāļĢ

āđāļ—āđ‡āļ

#nestjs
#prisma
#nodejs
#typescript
#backend

āđāļŠāļĢāđŒ

āļšāļ—āļ„āļ§āļēāļĄāļ—āļĩāđˆāđ€āļāļĩāđˆāļĒāļ§āļ‚āđ‰āļ­āļ‡

āļ„āļđāđˆāļĄāļ·āļ­ NestJS āļŠāļģāļŦāļĢāļąāļšāļŠāļĢāđ‰āļēāļ‡ REST API āļ—āļĩāđˆāļŠāļĄāļšāļđāļĢāļ“āđŒ

NestJS: āļŠāļĢāđ‰āļēāļ‡ REST API āļ—āļĩāđˆāļŠāļĄāļšāļđāļĢāļ“āđŒāļ•āļąāđ‰āļ‡āđāļ•āđˆāđ€āļĢāļīāđˆāļĄāļ•āđ‰āļ™

āļ„āļđāđˆāļĄāļ·āļ­āļ‰āļšāļąāļšāļŠāļĄāļšāļđāļĢāļ“āđŒāļŠāļģāļŦāļĢāļąāļšāļāļēāļĢāļŠāļĢāđ‰āļēāļ‡ REST API āļĢāļ°āļ”āļąāļšāļĄāļ·āļ­āļ­āļēāļŠāļĩāļžāļ”āđ‰āļ§āļĒ NestJS āļ„āļĢāļ­āļšāļ„āļĨāļļāļĄ Controller, Service, Module, āļāļēāļĢāļ•āļĢāļ§āļˆāļŠāļ­āļšāļ‚āđ‰āļ­āļĄāļđāļĨāļ”āđ‰āļ§āļĒ class-validator āđāļĨāļ°āļāļēāļĢāļˆāļąāļ”āļāļēāļĢāļ‚āđ‰āļ­āļœāļīāļ”āļžāļĨāļēāļ”

āļŠāļ–āļēāļ›āļąāļ•āļĒāļāļĢāļĢāļĄ NestJS Microservices āļāļąāļš gRPC

Microservices āļ”āđ‰āļ§āļĒ NestJS āļ›āļĩ 2026: āļŠāļ–āļēāļ›āļąāļ•āļĒāļāļĢāļĢāļĄ, gRPC āđāļĨāļ°āļ„āļģāļ–āļēāļĄāļŠāļąāļĄāļ āļēāļĐāļ“āđŒāļ‡āļēāļ™

āļ„āļđāđˆāļĄāļ·āļ­āļ‰āļšāļąāļšāļŠāļĄāļšāļđāļĢāļ“āđŒāđ€āļĢāļ·āđˆāļ­āļ‡āļŠāļ–āļēāļ›āļąāļ•āļĒāļāļĢāļĢāļĄ NestJS Microservices āļāļąāļš gRPC: transport layer, Protocol Buffers, streaming patterns āđāļĨāļ°āļ„āļģāļ–āļēāļĄāļŠāļąāļĄāļ āļēāļĐāļ“āđŒāļ‡āļēāļ™āļŠāļģāļŦāļĢāļąāļš backend engineer āļ›āļĩ 2026

āļŠāļ–āļēāļ›āļąāļ•āļĒāļāļĢāļĢāļĄ NestJS GraphQL API āļžāļĢāđ‰āļ­āļĄ resolver āđāļĨāļ° schema

NestJS āđāļĨāļ° GraphQL āđƒāļ™āļ›āļĩ 2026: Schema, Resolver āđāļĨāļ°āļ„āļģāļ–āļēāļĄāļŠāļąāļĄāļ āļēāļĐāļ“āđŒ

āļ„āļđāđˆāļĄāļ·āļ­āļ„āļĢāļšāļ–āđ‰āļ§āļ™āļŠāļģāļŦāļĢāļąāļšāļāļēāļĢāļœāļŠāļēāļ™āļĢāļ§āļĄ NestJS GraphQL āļ”āđ‰āļ§āļĒāđāļ™āļ§āļ—āļēāļ‡ schema-first āđāļĨāļ° code-first, āļĢāļđāļ›āđāļšāļš resolver āđāļĨāļ°āļ„āļģāļ–āļēāļĄāļŠāļąāļĄāļ āļēāļĐāļ“āđŒāļŠāļģāļŦāļĢāļąāļšāļ•āļģāđāļŦāļ™āđˆāļ‡ senior developer