Skip to content

Prisma CRUD Operations — Create, Read, Update, and Delete Data

DodaTech Updated 2026-06-28 6 min read

In this tutorial, you will learn about Prisma CRUD Operations. We cover key concepts, practical examples, and best practices to help you master this topic.

Prisma CRUD operations provide a complete set of methods for creating, reading, updating, and deleting records, with support for single and bulk operations, nested writes, and upserts.

What You'll Learn

By the end of this lesson you will create single and multiple records, use findUnique, findFirst, and findMany for reading, update records with partial updates, perform upserts, and handle delete operations.

Why It Matters

CRUD operations are the foundation of every database-backed application. Prisma's auto-generated methods simplify these operations while providing compile-time type safety.

Real-World Use

DodaZIP uses prisma.file.create() with nested writes to create a file record and a processing job in one operation. prisma.file.update() updates the processing status, and prisma.file.delete() cleans up files when users remove them.

Create Operations

Insert single or multiple records.

// Create single record
const user = await prisma.user.create({
  data: {
    email: 'alice@example.com',
    name: 'Alice Johnson',
  },
});

// Create with nested relations
const userWithPost = await prisma.user.create({
  data: {
    email: 'bob@example.com',
    name: 'Bob Smith',
    posts: {
      create: { title: 'First Post', content: 'Hello!' },
    },
  },
  include: { posts: true },
});

// Create multiple records
const users = await prisma.user.createMany({
  data: [
    { email: 'user1@test.com', name: 'User 1' },
    { email: 'user2@test.com', name: 'User 2' },
    { email: 'user3@test.com', name: 'User 3' },
  ],
  skipDuplicates: true, // Skip records that violate unique constraints
});

console.log(`Created ${users.count} users`);
# create_ops.py
# Create operation patterns

def create_patterns():
    print("Create Operation Patterns:")
    print()
    print("1. Single create:")
    print("   prisma.user.create({ data: { email, name } })")
    print("   Returns: User object")
    print()
    print("2. Nested create (with relations):")
    print("   prisma.user.create({")
    print("     data: {")
    print("       email: 'test@test.com',")
    print("       posts: { create: { title: 'Post' } }")
    print("     }")
    print("   })")
    print()
    print("3. Bulk create:")
    print("   prisma.user.createMany({")
    print("     data: [user1, user2, user3],")
    print("     skipDuplicates: true")
    print("   })")
    print("   Returns: { count: number }")

create_patterns()

Read Operations

Query records with various strategies.

// Find by unique field
const user = await prisma.user.findUnique({
  where: { email: 'alice@example.com' },
});

// Find first matching record
const firstActive = await prisma.user.findFirst({
  where: { status: 'ACTIVE' },
  orderBy: { createdAt: 'desc' },
});

// Find multiple records
const recentUsers = await prisma.user.findMany({
  where: { status: 'ACTIVE' },
  orderBy: { createdAt: 'desc' },
  take: 10,
  skip: 0,
});

// Find with selected fields only
const userNames = await prisma.user.findMany({
  select: { id: true, name: true, email: true },
});

// Find with relations included
const usersWithPosts = await prisma.user.findMany({
  include: { 
    posts: { 
      select: { title: true, createdAt: true },
      orderBy: { createdAt: 'desc' },
      take: 5,
    } 
  },
});
# read_ops.py
# Read operation patterns

def read_patterns():
    print("Read Operation Patterns:")
    print()
    print("findUnique:")
    print("  - Find by @id or @unique field")
    print("  - Returns record or null")
    print("  - Fastest query (direct index lookup)")
    print()
    print("findFirst:")
    print("  - Find first record matching conditions")
    print("  - Use with orderBy for predictable results")
    print()
    print("findMany:")
    print("  - Find all matching records")
    print("  - Supports where, orderBy, take, skip")
    print()
    print("Select vs Include:")
    print("  select: Choose specific fields (more efficient)")
    print("  include: Load related models")

read_patterns()

Update Operations

Modify existing records.

// Update single record
const updated = await prisma.user.update({
  where: { id: 1 },
  data: { 
    name: 'Updated Name',
    status: 'ACTIVE',
  },
});

// Update with nested create
const updatedWithPost = await prisma.user.update({
  where: { id: 1 },
  data: {
    posts: {
      create: { title: 'New Post' },
    },
  },
  include: { posts: true },
});

// Upsert (update or create)
const upserted = await prisma.user.upsert({
  where: { email: 'unique@email.com' },
  update: { name: 'Updated Name' },
  create: { 
    email: 'unique@email.com', 
    name: 'New User' 
  },
});

// Update multiple records
const result = await prisma.user.updateMany({
  where: { status: 'INACTIVE' },
  data: { status: 'ARCHIVED' },
});

console.log(`Updated ${result.count} users`);
# update_ops.py
# Update operation patterns

def update_patterns():
    print("Update Operation Patterns:")
    print()
    print("update (single):")
    print("  - Update one record by unique identifier")
    print("  - Throws if record not found")
    print("  - Returns the updated record")
    print()
    print("updateMany:")
    print("  - Update multiple records matching filter")
    print("  - Returns { count: number }")
    print("  - Does NOT return individual records")
    print()
    print("upsert:")
    print("  - Update if exists, create if not")
    print("  - Uses unique field for matching")
    print("  - Requires both update and create data")
    print()
    print("Nested updates:")
    print("  - Update related records inline")
    print("  - Supports create, update, delete, connect")

update_patterns()

Delete Operations

Remove records from the database.

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

// Delete multiple records
const result = await prisma.user.deleteMany({
  where: { 
    status: 'INACTIVE',
    createdAt: { lt: new Date('2024-01-01') },
  },
});

console.log(`Deleted ${result.count} users`);

// Delete with cascade (if referential actions configured)
// Deleting a user with onDelete: Cascade removes related posts
# delete_ops.py
# Delete operation patterns

def delete_patterns():
    print("Delete Operation Patterns:")
    print()
    print("delete (single):")
    print("  - Delete one record by unique identifier")
    print("  - Throws if record not found")
    print("  - Returns the deleted record")
    print()
    print("deleteMany:")
    print("  - Delete multiple records matching filter")
    print("  - Returns { count: number }")
    print("  - More efficient than deleting one by one")
    print()
    print("Important:")
    print("  - Check referential actions before delete")
    print("  - Cascade deletes remove related records")
    print("  - Restrict prevents delete if children exist")
    print("  - Consider soft delete for important data")

delete_patterns()

Common Mistakes

  1. Not handling null from findUnique: findUnique returns null for missing records. Accessing properties on null causes runtime errors.

  2. Forgetting await: Prisma methods return Promises. Forgetting await returns a Promise object instead of the result.

  3. Using update without checking existence: update throws an error if the record does not exist. Use findUnique first or wrap in try/catch.

  4. Overfetching with include: Including all relations with include: { posts: true } loads all related data. Use select or limit included fields.

  5. Bulk operations without where filter: deleteMany() and updateMany() without a where filter affect ALL records. Always specify a filter.

Practice Questions

  1. How do you create a record with related data in one call? Use nested writes: create({ data: { user: { create: {...} } } }).

  2. What is the difference between findUnique and findFirst? findUnique uses an @id or @unique field for direct lookup. findFirst uses arbitrary filters.

  3. What does upsert do? It updates a record if it exists, or creates a new one if it does not.

  4. Why should you be careful with deleteMany without a where filter? It deletes all records in the table, which is usually not intended.

  5. Challenge: Implement a complete CRUD service for a blog application with User, Post, and Comment models, including error handling and validation.

FAQ

Can I create multiple records with relations?

Yes. Use createMany for the parent and nested creates within the same transaction.

What happens if update finds no record?

Prisma throws a PrismaClientKnownRequestError with code P2025.

How do I return only specific fields?

Use the select option in any query.

Can I update multiple records atomically?

Yes. updateMany runs as a single SQL UPDATE statement.

What is the difference between delete and deleteMany?

delete requires a unique filter. deleteMany accepts any filter.

Mini Project

Create a CRUD service for a product inventory system with create, read (with filters), update, and delete operations, including upsert for bulk product imports.

def inventory_crud():
    print("Inventory CRUD Operations:")
    print()
    print("ProductService:")
    print("  createProduct(data)      -- Create new product")
    print("  getProduct(id)           -- Get by ID")
    print("  listProducts(filters)    -- List with filters")
    print("  updateProduct(id, data)  -- Update product")
    print("  deleteProduct(id)        -- Delete product")
    print("  bulkUpsert(products)     -- Import/update products")
    print()
    print("Example query patterns:")
    print("  prisma.product.findMany({")
    print("    where: { category: 'electronics', inStock: true },")
    print("    orderBy: { price: 'asc' },")
    print("    take: 20")
    print("  })")

inventory_crud()

What's Next

Next: Filtering and Sorting for advanced queries.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro