CRUD With Prisma Client

Prisma Fundamentals

Chapter 5 ยท CRUD With Prisma Client

With a schema and a database in place, it's time to use them. This chapter covers the four basic operations — Create, Read, Update and Delete — using the blog schema from Chapter 3, plus how to handle the errors Prisma throws and how to look at your data with Prisma Studio.

The pattern behind every query
Every model gets the same set of methods: prisma.user.create(...), prisma.post.findMany(...), and so on. Each takes one object of options, such as where, data and select, and every one returns a Promise, so use await. Your editor's autocompletion will show exactly which options each method accepts.

Create

import { prisma } from "./lib/prisma"; // One row: returns the created record, including defaults like id and createdAt const post = await prisma.post.create({ data: { title: "Hello Prisma", slug: "hello-prisma", content: "My first post" }, }); // Many rows: returns only a count const result = await prisma.post.createMany({ data: [ { title: "Second post", slug: "second-post" }, { title: "Third post", slug: "third-post" }, ], }); console.log(result.count); // 2 // Many rows, returning the records (PostgreSQL, CockroachDB, SQLite) const posts = await prisma.post.createManyAndReturn({ data: [{ title: "Fourth", slug: "fourth" }], });

TypeScript checks the data object against the schema: leave out a required field such as slug, or misspell one, and it won't compile. Fields with defaults (id, published, createdAt) can be left out.

skipDuplicates isn't everywhere
createMany accepts skipDuplicates: true to quietly skip rows that would break a unique rule, but SQLite, SQL Server and MongoDB don't support it. On those, check first or insert one at a time.

Read

MethodReturnsUse when
findUniqueOne record or nullLooking up by the ID or another @unique field
findUniqueOrThrowOne record, or throws an errorThe record must exist
findFirstThe first match or nullSearching by any field, where several might match
findFirstOrThrowThe first match, or throwsAs above, when a match must exist
findManyAn array (possibly empty)Lists of records
countA numberHow many records match
const bySlug = await prisma.post.findUnique({ where: { slug: "hello-prisma" } }); const firstDraft = await prisma.post.findFirst({ where: { published: false } }); const all = await prisma.post.findMany(); const drafts = await prisma.post.count({ where: { published: false } });
findUnique only accepts unique fields
prisma.post.findUnique({ where: { title: "Hello" } }) won't compile, because title isn't unique — the database could return several rows. Use findFirst or findMany for non-unique fields. Chapter 6 covers where in depth.

Choosing fields: select

// Only fetch what you need; the result type shrinks to match const titles = await prisma.post.findMany({ select: { id: true, title: true }, }); // titles: { id: number; title: string }[]

Update

// One record, found by a unique field const published = await prisma.post.update({ where: { slug: "hello-prisma" }, data: { published: true, publishedAt: new Date() }, }); // Many records: returns a count await prisma.post.updateMany({ where: { published: false }, data: { content: "Coming soon" }, }); // Atomic number operations: safe even if two requests arrive at once await prisma.post.update({ where: { slug: "hello-prisma" }, data: { viewCount: { increment: 1 } }, });
Don't read, add one, then write
Reading viewCount, adding 1 in your code, and saving it back can lose counts: if two visitors arrive at the same moment, both read 5 and both save 6. { increment: 1 } asks the database to do the addition itself, so both visits are counted. There are also decrement, multiply, divide and set.

Upsert: update or create

const user = await prisma.user.upsert({ where: { email: "ada@example.com" }, update: { name: "Ada Lovelace" }, // if she exists create: { email: "ada@example.com", name: "Ada Lovelace" }, // if not });

Delete

// One record, by a unique field; returns the deleted record await prisma.post.delete({ where: { slug: "fourth" } }); // Many records; returns a count await prisma.post.deleteMany({ where: { published: false } });
An empty where deletes everything
prisma.post.deleteMany({}) — or updateMany with no where — affects every row in the table. It's occasionally what you want (clearing test data), and a disaster when it isn't.

Handling Errors

When the database rejects an operation, Prisma throws a PrismaClientKnownRequestError with a code that tells you why. Two you'll meet constantly:

CodeMeaningTypical cause
P2002Unique constraint failedCreating a user with an email that already exists
P2025Record not foundupdate or delete on a record that doesn't exist; the ...OrThrow methods
import { Prisma } from "./generated/prisma/client"; try { await prisma.user.create({ data: { email: "ada@example.com" } }); } catch (e) { if (e instanceof Prisma.PrismaClientKnownRequestError && e.code === "P2002") { console.log("That email is already registered."); } else { throw e; // anything else is unexpected: let it surface } }

Looking at Your Data: Prisma Studio

npx prisma studio

This opens a browser-based table viewer on your own machine, where you can browse, filter, add, edit and delete rows without writing any code. It's ideal for checking what your scripts actually did.

Hands-On Exercises

Exercise 1

Write a script that creates three posts with createMany, publishes one of them, counts the drafts, and then prints just the titles of all posts. Check the results in Prisma Studio.

๐Ÿ“„ View solution
Exercise 2

Write a function registerUser(email, name) that returns a friendly message instead of crashing when the email is already taken, and a function recordView(slug) that increments a post's view count and returns null if the post doesn't exist.

๐Ÿ“„ View solution
Exercise 3

For each task, pick the best method and explain why: (a) show one post from its URL slug; (b) find the most recent unpublished post; (c) make sure an "about" page exists, creating it only if missing; (d) clear all posts before a test run.

๐Ÿ“„ View solution

Chapter 5 Quick Reference

  • Every model has the same methods; each takes an options object and returns a Promise
  • Create: create, createMany (returns a count), createManyAndReturn (PostgreSQL, CockroachDB, SQLite)
  • Read: findUnique (unique fields only), findFirst, findMany, count, and ...OrThrow variants
  • select fetches only the fields you name, and narrows the result type
  • Update: update, updateMany, upsert; use { increment: 1 } and friends for counters
  • Delete: delete, deleteMany — an empty where affects every row
  • Errors: Prisma.PrismaClientKnownRequestError; P2002 duplicate unique value, P2025 record not found
  • npx prisma studio opens a browser view of your data