install
source · Clone the upstream repo
git clone https://github.com/Intense-Visions/harness-engineering
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/Intense-Visions/harness-engineering "$T" && mkdir -p ~/.claude/skills && cp -r "$T/agents/skills/claude-code/prisma-client-queries" ~/.claude/skills/intense-visions-harness-engineering-prisma-client-queries && rm -rf "$T"
manifest:
agents/skills/claude-code/prisma-client-queries/SKILL.mdsource content
Prisma Client Queries
Query data with Prisma Client findUnique/findMany, create/update/delete, upsert, select, include
When to Use
- Reading data from the database with type-safe queries
- Creating, updating, or deleting records
- Selecting specific fields or including related records
- Performing upserts (create-or-update) operations
Instructions
- Instantiate the client once and reuse it. Never create a new
per request:PrismaClient
// lib/prisma.ts import { PrismaClient } from '@prisma/client'; const globalForPrisma = globalThis as unknown as { prisma: PrismaClient }; export const prisma = globalForPrisma.prisma ?? new PrismaClient(); if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma;
- Find a single record — use
for lookups byfindUnique
or@id
fields. Use@unique
when filtering by non-unique fields:findFirst
const user = await prisma.user.findUnique({ where: { id: userId } }); const admin = await prisma.user.findFirst({ where: { role: 'ADMIN' } });
- Find multiple records —
returns an array. Always paginate large result sets:findMany
const users = await prisma.user.findMany({ where: { role: 'USER' }, take: 20, skip: 0, orderBy: { createdAt: 'desc' }, });
- Select specific fields to avoid over-fetching:
const names = await prisma.user.findMany({ select: { id: true, name: true, email: true }, }); // Type: { id: string; name: string | null; email: string }[]
- Include related records with
:include
const userWithPosts = await prisma.user.findUnique({ where: { id: userId }, include: { posts: { where: { published: true }, take: 10 } }, });
- Create a record — all required fields must be provided:
const user = await prisma.user.create({ data: { email: 'new@example.com', name: 'New User' }, });
- Update a record — requires a unique
clause:where
const updated = await prisma.user.update({ where: { id: userId }, data: { name: 'Updated Name' }, });
- Upsert — create if not found, update if found:
const user = await prisma.user.upsert({ where: { email: 'user@example.com' }, create: { email: 'user@example.com', name: 'New' }, update: { name: 'Existing' }, });
- Delete — use
for single records,delete
for bulk:deleteMany
await prisma.user.delete({ where: { id: userId } }); await prisma.post.deleteMany({ where: { authorId: userId } });
- Nested writes — create or connect related records in one operation:
const user = await prisma.user.create({ data: { email: 'author@example.com', posts: { create: [{ title: 'First Post' }, { title: 'Second Post' }] }, }, include: { posts: true }, });
Details
Prisma Client is auto-generated from the schema and provides full TypeScript types for every query. The generated types enforce that you only pass valid field names, filter operators, and relation includes.
vs select
: These are mutually exclusive at the same level. include
select returns only the specified fields (plus any nested select/include). include returns all scalar fields plus the specified relations.
vs findUnique
: findFirst
findUnique can only filter by @id or @unique fields and benefits from Prisma's internal query deduplication (DataLoader batching). findFirst can filter by any field but does not deduplicate.
/ findUniqueOrThrow
: These variants throw a findFirstOrThrow
PrismaClientKnownRequestError with code P2025 instead of returning null. Use them when absence is an error condition.
Nested writes are atomic:
create with nested create/connect/connectOrCreate runs in a single implicit transaction. If any nested operation fails, the entire write is rolled back.
Return types narrow automatically: When you use
select, the return type includes only the selected fields. This is enforced at the TypeScript level, so your code stays type-safe.
Common mistakes:
- Using
wherefindFirst
would work — loses batching optimizationfindUnique - Deeply nesting
— each level adds a separate database query. Three levels deep on a list page causes N+1 problemsinclude - Not handling
fromnull
— always check or usefindUniquefindUniqueOrThrow - Forgetting that
/updateMany
do not return the affected records — only a countdeleteMany
Source
https://prisma.io/docs/orm/prisma-client/queries
Process
- Read the instructions and examples in this document.
- Apply the patterns to your implementation, adapting to your specific context.
- Verify your implementation against the details and edge cases listed above.
Harness Integration
- Type: knowledge — this skill is a reference document, not a procedural workflow.
- No tools or state — consumed as context by other skills and agents.
Success Criteria
- The patterns described in this document are applied correctly in the implementation.
- Edge cases and anti-patterns listed in this document are avoided.