Tutorial Prisma ORM: Setup dan Best Practices untuk TypeScript
ID | EN

Tutorial Prisma ORM: Setup dan Best Practices untuk TypeScript

Sabtu, 27 Des 2025

Kalau kamu pernah pakai raw SQL queries atau ORM tradisional seperti Sequelize, pasti tahu rasanya debugging query yang error di runtime. Typo nama kolom? Error baru ketahuan pas aplikasi jalan.

Prisma hadir dengan pendekatan berbeda: type-safe database access dengan auto-generated types dari schema kamu. Artinya, kalau ada typo atau salah query, TypeScript langsung kasih warning sebelum kode dijalankan.

Apa itu ORM?

ORM (Object-Relational Mapping) adalah layer abstraksi antara aplikasi dan database. Daripada nulis raw SQL:

SELECT * FROM users WHERE id = 1;

Kamu bisa pakai syntax yang lebih readable:

const user = await prisma.user.findUnique({ where: { id: 1 } });

Kenapa Prisma?

Prisma bukan ORM biasa. Ini yang bikin Prisma stand out:

FiturPrismaORM Tradisional
Type Safety100% type-safePartial atau manual
Schema DefinitionPrisma Schema LanguageDecorators/JS Objects
MigrationsAutomated & versionedManual atau semi-auto
Query BuildingFluent API dengan autocompleteString-based atau builder
ToolingPrisma Studio, CLI, VS Code extensionVaries

Instalasi dan Setup

1. Init Project

mkdir prisma-tutorial && cd prisma-tutorial
npm init -y
npm install typescript ts-node @types/node -D
npx tsc --init

2. Install Prisma

npm install prisma -D
npm install @prisma/client

3. Initialize Prisma

npx prisma init

Ini akan membuat:

  • prisma/schema.prisma - File schema utama
  • .env - File environment variables

4. Konfigurasi Database

Edit .env dengan connection string database kamu:

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

# MySQL
DATABASE_URL="mysql://user:password@localhost:3306/mydb"

# SQLite (cocok untuk development)
DATABASE_URL="file:./dev.db"

Prisma Schema: Jantung dari Prisma

File prisma/schema.prisma adalah tempat kamu mendefinisikan struktur database:

// filepath: prisma/schema.prisma

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

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

model User {
  id        Int      @id @default(autoincrement())
  email     String   @unique
  name      String?
  password  String
  role      Role     @default(USER)
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt

  posts    Post[]
  profile  Profile?
  comments Comment[]
}

enum Role {
  USER
  ADMIN
  MODERATOR
}

Penjelasan Attributes

AttributeFungsi
@idPrimary key
@uniqueNilai harus unik
@default()Nilai default
autoincrement()Auto increment untuk integer
now()Timestamp saat record dibuat
@updatedAtAuto update timestamp saat record diupdate
?Field optional (nullable)

Relations: 1:1, 1:N, dan N:M

One-to-One (1:1)

User memiliki satu Profile:

model User {
  id      Int      @id @default(autoincrement())
  email   String   @unique
  profile Profile?
}

model Profile {
  id     Int    @id @default(autoincrement())
  bio    String?
  avatar String?
  
  userId Int  @unique
  user   User @relation(fields: [userId], references: [id])
}

One-to-Many (1:N)

User memiliki banyak Post:

model User {
  id    Int    @id @default(autoincrement())
  email String @unique
  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])
}

Many-to-Many (N:M)

Post memiliki banyak Category, Category dimiliki banyak Post:

model Post {
  id         Int        @id @default(autoincrement())
  title      String
  categories Category[]
}

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

Prisma otomatis membuat junction table. Kalau butuh explicit junction table dengan field tambahan:

model Post {
  id         Int            @id @default(autoincrement())
  title      String
  categories PostCategory[]
}

model Category {
  id    Int            @id @default(autoincrement())
  name  String         @unique
  posts PostCategory[]
}

model PostCategory {
  postId     Int
  categoryId Int
  assignedAt DateTime @default(now())

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

  @@id([postId, categoryId])
}

Complete Schema Example

Berikut schema lengkap untuk blog platform:

// filepath: prisma/schema.prisma

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

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

model User {
  id        Int      @id @default(autoincrement())
  email     String   @unique
  name      String?
  password  String
  role      Role     @default(USER)
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt

  posts    Post[]
  profile  Profile?
  comments Comment[]
}

model Profile {
  id        Int     @id @default(autoincrement())
  bio       String?
  avatar    String?
  website   String?
  twitter   String?
  github    String?

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

model Post {
  id          Int      @id @default(autoincrement())
  slug        String   @unique
  title       String
  content     String?
  excerpt     String?
  published   Boolean  @default(false)
  publishedAt DateTime?
  createdAt   DateTime @default(now())
  updatedAt   DateTime @updatedAt

  authorId   Int
  author     User       @relation(fields: [authorId], references: [id])
  categories Category[]
  comments   Comment[]
  tags       Tag[]

  @@index([authorId])
  @@index([published])
}

model Category {
  id          Int    @id @default(autoincrement())
  name        String @unique
  slug        String @unique
  description String?
  posts       Post[]
}

model Tag {
  id    Int    @id @default(autoincrement())
  name  String @unique
  slug  String @unique
  posts Post[]
}

model Comment {
  id        Int      @id @default(autoincrement())
  content   String
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt

  postId   Int
  post     Post     @relation(fields: [postId], references: [id], onDelete: Cascade)
  authorId Int
  author   User     @relation(fields: [authorId], references: [id])
  parentId Int?
  parent   Comment? @relation("CommentReplies", fields: [parentId], references: [id])
  replies  Comment[] @relation("CommentReplies")

  @@index([postId])
  @@index([authorId])
}

enum Role {
  USER
  ADMIN
  MODERATOR
}

Migrations

Setelah schema siap, jalankan migration:

# Development: Create dan apply migration
npx prisma migrate dev --name init

# Production: Apply pending migrations
npx prisma migrate deploy

# Reset database (hapus semua data!)
npx prisma migrate reset

Tips Migration

  1. Selalu review SQL yang di-generate - Prisma generate SQL di folder prisma/migrations/
  2. Jangan edit migration files - Kalau perlu ubah, buat migration baru
  3. Gunakan nama yang deskriptif - add_user_avatar lebih baik dari update_1

Generate Prisma Client

Setiap kali schema berubah, regenerate client:

npx prisma generate

Ini akan generate types berdasarkan schema kamu.

CRUD Operations

Setup Prisma Client

// filepath: src/lib/prisma.ts
import { PrismaClient } from '@prisma/client';

const globalForPrisma = globalThis as unknown as {
  prisma: PrismaClient | undefined;
};

export const prisma = globalForPrisma.prisma ?? new PrismaClient();

if (process.env.NODE_ENV !== 'production') {
  globalForPrisma.prisma = prisma;
}

Pattern ini mencegah multiple PrismaClient instances saat hot reload di development.

Create

// Create single record
const user = await prisma.user.create({
  data: {
    email: '[email protected]',
    name: 'John Doe',
    password: 'hashedpassword',
  },
});

// Create dengan nested relation
const userWithProfile = await prisma.user.create({
  data: {
    email: '[email protected]',
    name: 'Jane Doe',
    password: 'hashedpassword',
    profile: {
      create: {
        bio: 'Full-stack developer',
        github: 'janedoe',
      },
    },
  },
  include: {
    profile: true,
  },
});

// Create many
const users = await prisma.user.createMany({
  data: [
    { email: '[email protected]', name: 'User 1', password: 'hash1' },
    { email: '[email protected]', name: 'User 2', password: 'hash2' },
  ],
  skipDuplicates: true, // Skip jika email sudah ada
});

Read

// Find by ID
const user = await prisma.user.findUnique({
  where: { id: 1 },
});

// Find by unique field
const userByEmail = await prisma.user.findUnique({
  where: { email: '[email protected]' },
});

// Find first match
const admin = await prisma.user.findFirst({
  where: { role: 'ADMIN' },
});

// Find many
const allUsers = await prisma.user.findMany();

// Find with conditions
const activeAuthors = await prisma.user.findMany({
  where: {
    role: 'USER',
    posts: {
      some: {
        published: true,
      },
    },
  },
});

Update

// Update single
const updatedUser = await prisma.user.update({
  where: { id: 1 },
  data: { name: 'John Updated' },
});

// Update many
const deactivated = await prisma.user.updateMany({
  where: {
    lastLogin: {
      lt: new Date('2024-01-01'),
    },
  },
  data: {
    role: 'INACTIVE',
  },
});

// Upsert (update or create)
const user = await prisma.user.upsert({
  where: { email: '[email protected]' },
  update: { name: 'John Doe Updated' },
  create: {
    email: '[email protected]',
    name: 'John Doe',
    password: 'hashedpassword',
  },
});

Delete

// Delete single
const deleted = await prisma.user.delete({
  where: { id: 1 },
});

// Delete many
const deletedCount = await prisma.user.deleteMany({
  where: {
    role: 'INACTIVE',
  },
});

Advanced Queries

Select: Pilih Field Spesifik

const users = await prisma.user.findMany({
  select: {
    id: true,
    email: true,
    name: true,
    // password tidak di-select untuk keamanan
  },
});

Include: Load Relations

// Include single relation
const userWithPosts = await prisma.user.findUnique({
  where: { id: 1 },
  include: {
    posts: true,
  },
});

// Include nested relations
const userComplete = await prisma.user.findUnique({
  where: { id: 1 },
  include: {
    profile: true,
    posts: {
      include: {
        categories: true,
        comments: {
          include: {
            author: {
              select: {
                id: true,
                name: true,
              },
            },
          },
        },
      },
    },
  },
});

Where: Filtering

// Equals
const user = await prisma.user.findFirst({
  where: { email: '[email protected]' },
});

// Not equals
const nonAdmins = await prisma.user.findMany({
  where: { role: { not: 'ADMIN' } },
});

// Contains (case-insensitive by default di PostgreSQL)
const searchResults = await prisma.user.findMany({
  where: {
    name: { contains: 'john' },
  },
});

// Starts with / Ends with
const gmailUsers = await prisma.user.findMany({
  where: {
    email: { endsWith: '@gmail.com' },
  },
});

// In array
const specificRoles = await prisma.user.findMany({
  where: {
    role: { in: ['ADMIN', 'MODERATOR'] },
  },
});

// Greater than, Less than
const recentPosts = await prisma.post.findMany({
  where: {
    createdAt: { gte: new Date('2024-01-01') },
  },
});

// AND, OR, NOT
const complexQuery = await prisma.user.findMany({
  where: {
    AND: [
      { role: 'USER' },
      {
        OR: [
          { email: { contains: 'company.com' } },
          { name: { startsWith: 'Admin' } },
        ],
      },
    ],
    NOT: {
      posts: { none: {} }, // User yang punya minimal 1 post
    },
  },
});

Filtering Relations

// Users yang punya minimal 1 post published
const authors = await prisma.user.findMany({
  where: {
    posts: {
      some: {
        published: true,
      },
    },
  },
});

// Users yang semua post-nya published
const allPublished = await prisma.user.findMany({
  where: {
    posts: {
      every: {
        published: true,
      },
    },
  },
});

// Users tanpa post sama sekali
const noPostUsers = await prisma.user.findMany({
  where: {
    posts: {
      none: {},
    },
  },
});

Pagination

// Offset-based pagination
const page = 2;
const pageSize = 10;

const posts = await prisma.post.findMany({
  skip: (page - 1) * pageSize,
  take: pageSize,
  orderBy: { createdAt: 'desc' },
});

// Cursor-based pagination (lebih efisien untuk dataset besar)
const nextPosts = await prisma.post.findMany({
  take: 10,
  skip: 1, // Skip the cursor
  cursor: { id: lastPostId },
  orderBy: { id: 'asc' },
});

Ordering

// Single order
const posts = await prisma.post.findMany({
  orderBy: { createdAt: 'desc' },
});

// Multiple order
const users = await prisma.user.findMany({
  orderBy: [
    { role: 'asc' },
    { name: 'asc' },
  ],
});

// Order by relation
const usersByPostCount = await prisma.user.findMany({
  orderBy: {
    posts: {
      _count: 'desc',
    },
  },
});

Aggregation

// Count
const userCount = await prisma.user.count();
const adminCount = await prisma.user.count({
  where: { role: 'ADMIN' },
});

// Aggregate
const stats = await prisma.post.aggregate({
  _count: true,
  _avg: { viewCount: true },
  _max: { viewCount: true },
  _min: { viewCount: true },
  where: { published: true },
});

// Group by
const postsByAuthor = await prisma.post.groupBy({
  by: ['authorId'],
  _count: { id: true },
  _avg: { viewCount: true },
  having: {
    id: {
      _count: { gt: 5 },
    },
  },
  orderBy: {
    _count: {
      id: 'desc',
    },
  },
});

Transactions

Sequential Transactions

const result = await prisma.$transaction(async (tx) => {
  // 1. Create user
  const user = await tx.user.create({
    data: {
      email: '[email protected]',
      name: 'New User',
      password: 'hashedpassword',
    },
  });

  // 2. Create profile
  const profile = await tx.profile.create({
    data: {
      userId: user.id,
      bio: 'Hello world',
    },
  });

  // 3. Create welcome post
  const post = await tx.post.create({
    data: {
      slug: 'welcome-new-user',
      title: 'Welcome!',
      content: 'Welcome to our platform',
      authorId: user.id,
      published: true,
    },
  });

  return { user, profile, post };
});

Batch Transactions

// Multiple independent operations
const [users, posts, comments] = await prisma.$transaction([
  prisma.user.findMany(),
  prisma.post.findMany({ where: { published: true } }),
  prisma.comment.count(),
]);

Interactive Transactions dengan Timeout

const result = await prisma.$transaction(
  async (tx) => {
    // Operasi yang mungkin lama
    const users = await tx.user.findMany();
    
    for (const user of users) {
      await tx.user.update({
        where: { id: user.id },
        data: { lastProcessed: new Date() },
      });
    }
    
    return users.length;
  },
  {
    maxWait: 5000, // 5 detik max waiting untuk acquire connection
    timeout: 30000, // 30 detik timeout untuk transaction
  }
);

Database Seeding

Buat file seed untuk populate data awal:

// filepath: prisma/seed.ts
import { PrismaClient } from '@prisma/client';

const prisma = new PrismaClient();

async function main() {
  // Clean existing data
  await prisma.comment.deleteMany();
  await prisma.post.deleteMany();
  await prisma.profile.deleteMany();
  await prisma.category.deleteMany();
  await prisma.tag.deleteMany();
  await prisma.user.deleteMany();

  console.log('Seeding database...');

  // Create users
  const admin = await prisma.user.create({
    data: {
      email: '[email protected]',
      name: 'Admin User',
      password: '$2b$10$hashedpassword', // Use bcrypt in real app
      role: 'ADMIN',
      profile: {
        create: {
          bio: 'Platform administrator',
          github: 'admin',
        },
      },
    },
  });

  const author = await prisma.user.create({
    data: {
      email: '[email protected]',
      name: 'John Author',
      password: '$2b$10$hashedpassword',
      role: 'USER',
      profile: {
        create: {
          bio: 'Tech writer and developer',
          website: 'https://johnauthor.dev',
          twitter: 'johnauthor',
          github: 'johnauthor',
        },
      },
    },
  });

  // Create categories
  const categories = await Promise.all([
    prisma.category.create({
      data: { name: 'Technology', slug: 'technology', description: 'Tech articles' },
    }),
    prisma.category.create({
      data: { name: 'Tutorial', slug: 'tutorial', description: 'How-to guides' },
    }),
    prisma.category.create({
      data: { name: 'Opinion', slug: 'opinion', description: 'Opinion pieces' },
    }),
  ]);

  // Create tags
  const tags = await Promise.all([
    prisma.tag.create({ data: { name: 'JavaScript', slug: 'javascript' } }),
    prisma.tag.create({ data: { name: 'TypeScript', slug: 'typescript' } }),
    prisma.tag.create({ data: { name: 'Prisma', slug: 'prisma' } }),
    prisma.tag.create({ data: { name: 'Database', slug: 'database' } }),
  ]);

  // Create posts
  const post1 = await prisma.post.create({
    data: {
      slug: 'getting-started-with-prisma',
      title: 'Getting Started with Prisma',
      content: 'Prisma is a next-generation ORM...',
      excerpt: 'Learn how to use Prisma in your projects',
      published: true,
      publishedAt: new Date(),
      authorId: author.id,
      categories: {
        connect: [{ id: categories[0].id }, { id: categories[1].id }],
      },
      tags: {
        connect: [{ id: tags[2].id }, { id: tags[3].id }],
      },
    },
  });

  const post2 = await prisma.post.create({
    data: {
      slug: 'typescript-best-practices',
      title: 'TypeScript Best Practices in 2025',
      content: 'TypeScript has evolved significantly...',
      excerpt: 'Master TypeScript with these best practices',
      published: true,
      publishedAt: new Date(),
      authorId: author.id,
      categories: {
        connect: [{ id: categories[0].id }],
      },
      tags: {
        connect: [{ id: tags[0].id }, { id: tags[1].id }],
      },
    },
  });

  // Create comments
  await prisma.comment.create({
    data: {
      content: 'Great article! Very helpful.',
      postId: post1.id,
      authorId: admin.id,
    },
  });

  console.log('Seeding completed!');
  console.log({
    users: await prisma.user.count(),
    posts: await prisma.post.count(),
    categories: await prisma.category.count(),
    tags: await prisma.tag.count(),
    comments: await prisma.comment.count(),
  });
}

main()
  .catch((e) => {
    console.error(e);
    process.exit(1);
  })
  .finally(async () => {
    await prisma.$disconnect();
  });

Tambahkan di package.json:

{
  "prisma": {
    "seed": "ts-node prisma/seed.ts"
  }
}

Jalankan seeding:

npx prisma db seed

Prisma Studio

Prisma Studio adalah GUI untuk explore dan edit data:

npx prisma studio

Akan membuka browser di http://localhost:5555. Kamu bisa:

  • Browse semua tables
  • View dan edit records
  • Filter dan sort data
  • Lihat relations

Performance Tips

1. Gunakan Select untuk Limit Fields

// ❌ Mengambil semua fields termasuk password
const users = await prisma.user.findMany();

// ✅ Hanya ambil yang diperlukan
const users = await prisma.user.findMany({
  select: {
    id: true,
    email: true,
    name: true,
  },
});

2. Batasi Nested Includes

// ❌ Deep nesting = banyak JOIN = slow
const post = await prisma.post.findUnique({
  where: { id: 1 },
  include: {
    author: {
      include: {
        posts: {
          include: {
            comments: {
              include: {
                author: true,
              },
            },
          },
        },
      },
    },
  },
});

// ✅ Pisahkan ke multiple queries jika perlu
const post = await prisma.post.findUnique({
  where: { id: 1 },
  include: { author: true },
});

const comments = await prisma.comment.findMany({
  where: { postId: 1 },
  include: { author: { select: { id: true, name: true } } },
  take: 20,
});

3. Gunakan Index di Schema

model Post {
  id        Int    @id @default(autoincrement())
  slug      String @unique
  authorId  Int
  published Boolean @default(false)
  createdAt DateTime @default(now())

  @@index([authorId])
  @@index([published])
  @@index([createdAt])
  @@index([published, createdAt]) // Composite index
}

4. Connection Pooling

Untuk serverless atau high-traffic:

// filepath: src/lib/prisma.ts
import { PrismaClient } from '@prisma/client';

const prisma = new PrismaClient({
  datasources: {
    db: {
      url: process.env.DATABASE_URL,
    },
  },
  log: process.env.NODE_ENV === 'development' 
    ? ['query', 'error', 'warn'] 
    : ['error'],
});

export default prisma;

Untuk Vercel/serverless, gunakan Prisma Accelerate atau connection pooler seperti PgBouncer.

5. Batch Operations

// ❌ Multiple round trips
for (const id of userIds) {
  await prisma.user.update({
    where: { id },
    data: { lastSeen: new Date() },
  });
}

// ✅ Single query
await prisma.user.updateMany({
  where: { id: { in: userIds } },
  data: { lastSeen: new Date() },
});

Best Practices

1. Struktur Project

src/
├── lib/
│   └── prisma.ts          # Singleton Prisma client
├── repositories/          # Data access layer
│   ├── user.repository.ts
│   └── post.repository.ts
├── services/              # Business logic
│   ├── user.service.ts
│   └── post.service.ts
└── ...

prisma/
├── schema.prisma
├── seed.ts
└── migrations/

2. Repository Pattern

// filepath: src/repositories/user.repository.ts
import prisma from '@/lib/prisma';
import { Prisma } from '@prisma/client';

export const userRepository = {
  async findById(id: number) {
    return prisma.user.findUnique({
      where: { id },
      select: {
        id: true,
        email: true,
        name: true,
        role: true,
        profile: true,
      },
    });
  },

  async findByEmail(email: string) {
    return prisma.user.findUnique({
      where: { email },
    });
  },

  async create(data: Prisma.UserCreateInput) {
    return prisma.user.create({ data });
  },

  async update(id: number, data: Prisma.UserUpdateInput) {
    return prisma.user.update({
      where: { id },
      data,
    });
  },

  async delete(id: number) {
    return prisma.user.delete({
      where: { id },
    });
  },

  async findManyWithPagination({
    page = 1,
    pageSize = 10,
    where,
    orderBy,
  }: {
    page?: number;
    pageSize?: number;
    where?: Prisma.UserWhereInput;
    orderBy?: Prisma.UserOrderByWithRelationInput;
  }) {
    const [users, total] = await Promise.all([
      prisma.user.findMany({
        where,
        orderBy: orderBy ?? { createdAt: 'desc' },
        skip: (page - 1) * pageSize,
        take: pageSize,
        select: {
          id: true,
          email: true,
          name: true,
          role: true,
          createdAt: true,
        },
      }),
      prisma.user.count({ where }),
    ]);

    return {
      users,
      pagination: {
        page,
        pageSize,
        total,
        totalPages: Math.ceil(total / pageSize),
      },
    };
  },
};

3. Error Handling

import { Prisma } from '@prisma/client';

async function createUser(data: Prisma.UserCreateInput) {
  try {
    return await prisma.user.create({ data });
  } catch (error) {
    if (error instanceof Prisma.PrismaClientKnownRequestError) {
      // P2002: Unique constraint violation
      if (error.code === 'P2002') {
        throw new Error('Email already exists');
      }
      // P2025: Record not found
      if (error.code === 'P2025') {
        throw new Error('User not found');
      }
    }
    throw error;
  }
}

4. Type-Safe Includes dengan Satisfies

import { Prisma } from '@prisma/client';

const postWithAuthor = {
  include: {
    author: {
      select: {
        id: true,
        name: true,
        email: true,
      },
    },
    categories: true,
  },
} satisfies Prisma.PostDefaultArgs;

type PostWithAuthor = Prisma.PostGetPayload<typeof postWithAuthor>;

async function getPost(id: number): Promise<PostWithAuthor | null> {
  return prisma.post.findUnique({
    where: { id },
    ...postWithAuthor,
  });
}

5. Environment-Specific Configuration

// filepath: src/lib/prisma.ts
import { PrismaClient } from '@prisma/client';

const prismaClientSingleton = () => {
  return new PrismaClient({
    log:
      process.env.NODE_ENV === 'development'
        ? ['query', 'error', 'warn']
        : ['error'],
    errorFormat: process.env.NODE_ENV === 'development' ? 'pretty' : 'minimal',
  });
};

declare global {
  var prisma: undefined | ReturnType<typeof prismaClientSingleton>;
}

const prisma = globalThis.prisma ?? prismaClientSingleton();

if (process.env.NODE_ENV !== 'production') {
  globalThis.prisma = prisma;
}

export default prisma;

Kesimpulan

Prisma adalah game-changer untuk database access di TypeScript:

  1. Type safety - Error terdeteksi saat compile time
  2. Developer experience - Autocomplete, Prisma Studio, excellent docs
  3. Migrations - Versioned, automated, predictable
  4. Performance - Query optimization, connection pooling support
  5. Ecosystem - Prisma Accelerate, Pulse, dan growing community

Mulai dengan schema yang simple, pahami relations dan queries, lalu scale up dengan best practices yang sudah dibahas.

Resources