Harness-engineering prisma-relations-pattern

Prisma Relations Pattern

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-relations-pattern" ~/.claude/skills/intense-visions-harness-engineering-prisma-relations-pattern-6cd2e5 && rm -rf "$T"
manifest: agents/skills/claude-code/prisma-relations-pattern/SKILL.md
source content

Prisma Relations Pattern

Model one-to-one, one-to-many, many-to-many, and self-relations with @relation in Prisma

When to Use

  • Adding foreign key relationships between Prisma models
  • Modeling one-to-one, one-to-many, or many-to-many associations
  • Creating self-referential relations (e.g., manager/employee, parent/child)
  • Resolving ambiguous relations when two models have multiple connections

Instructions

  1. One-to-many — place the scalar foreign key and
    @relation
    on the "many" side. The "one" side gets an array field:
model User {
  id    String @id @default(cuid())
  posts Post[]
}

model Post {
  id       String @id @default(cuid())
  author   User   @relation(fields: [authorId], references: [id])
  authorId String
}
  1. One-to-one — identical to one-to-many but the relation field is singular on both sides. Add
    @unique
    to the foreign key:
model User {
  id      String   @id @default(cuid())
  profile Profile?
}

model Profile {
  id     String @id @default(cuid())
  user   User   @relation(fields: [userId], references: [id])
  userId String @unique
}
  1. Many-to-many (implicit) — use array fields on both sides. Prisma creates the join table automatically:
model Post {
  id   String @id @default(cuid())
  tags Tag[]
}

model Tag {
  id    String @id @default(cuid())
  posts Post[]
}
  1. Many-to-many (explicit) — create an explicit join model when you need extra fields on the relationship:
model PostTag {
  post   Post   @relation(fields: [postId], references: [id])
  postId String
  tag    Tag    @relation(fields: [tagId], references: [id])
  tagId  String
  assignedAt DateTime @default(now())

  @@id([postId, tagId])
}
  1. Self-relations — reference the same model. Use named
    @relation
    to disambiguate:
model Employee {
  id         String     @id @default(cuid())
  manager    Employee?  @relation("ManagerReports", fields: [managerId], references: [id])
  managerId  String?
  reports    Employee[] @relation("ManagerReports")
}
  1. Disambiguate multiple relations between the same two models using the relation name string:
model User {
  id            String @id @default(cuid())
  writtenPosts  Post[] @relation("Author")
  editedPosts   Post[] @relation("Editor")
}

model Post {
  id       String @id @default(cuid())
  author   User   @relation("Author", fields: [authorId], references: [id])
  authorId String
  editor   User?  @relation("Editor", fields: [editorId], references: [id])
  editorId String?
}
  1. Referential actions — set
    onDelete
    and
    onUpdate
    behavior:
author User @relation(fields: [authorId], references: [id], onDelete: Cascade, onUpdate: Cascade)

Options:

Cascade
,
Restrict
,
NoAction
,
SetNull
,
SetDefault
.

  1. Always add
    @@index([foreignKeyField])
    on relation scalar fields for query performance.

Details

Prisma relations are defined at the Prisma schema level and map to foreign keys in the database. The

@relation
attribute is required on the side that stores the foreign key (the scalar field side).

Implicit vs explicit many-to-many: Implicit join tables follow the naming convention

_ModelAToModelB
(alphabetical). You cannot query the join table directly or add columns to it. Switch to explicit if you ever need metadata on the relationship.

Referential integrity modes:

  • foreignKeys
    (default for relational databases) — enforced by the database
  • prisma
    — enforced by Prisma Client at the application level, required for databases that do not support foreign keys (e.g., PlanetScale with Vitess)

Cascade gotchas:

  • onDelete: Cascade
    on a required relation means deleting a parent deletes all children — this is often not what you want for audit-sensitive data
  • onDelete: SetNull
    requires the foreign key field to be optional (
    String?
    )
  • The default referential action varies by provider — PostgreSQL defaults to
    NoAction
    , which throws on violation

Performance considerations:

  • Every
    include
    on a relation triggers an additional SQL query (not a JOIN). For deeply nested includes, consider using raw queries with explicit JOINs
  • Implicit many-to-many creates an unindexed join table — add indexes manually via a migration if query performance degrades

Source

https://prisma.io/docs/orm/prisma-schema/data-model/relations

Process

  1. Read the instructions and examples in this document.
  2. Apply the patterns to your implementation, adapting to your specific context.
  3. 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.