Many-to-Many & Nested Writes
Prisma Fundamentals
Chapter 8 · Relations II: Many-to-Many & Nested Writes
A post can have several tags, and each tag can be on many posts. Neither side can hold a single foreign key, so this needs a third table in between. This chapter shows Prisma's two ways of modelling that, and then covers nested writes: creating or linking related records in the same call, which works for every kind of relation from Chapter 7 too.
How Many-to-Many Works in a Database
Each row of the join table links one post to one tag. Post 1 is tagged "prisma" and "sql"; post 2 is tagged "prisma".
Implicit Many-to-Many
The simplest way: put a list field on both sides and let Prisma manage the join table.
Prisma creates a hidden join table, named from the two model names in alphabetical order — here
_PostToTag, with columns A and B. You never refer to it in your code.
- Both models must have a single-field
@id(no composite IDs). - You can't store anything else about the link, such as when a tag was added or by whom.
- You can't set
onDeleteoronUpdateon it. - It doesn't work on MongoDB.
Explicit Many-to-Many
When the link itself carries information, make the join table a real model:
This is really two one-to-many relations (Chapter 7): a post has many PostTag rows, and so does a
tag. The composite key stops the same tag being added to a post twice.
| Implicit | Explicit | |
|---|---|---|
| Schema | Two list fields | A third model with two relations |
| Extra data on the link | No | Yes (assignedAt, order, …) |
| Queries | Simpler: post.tags are tags | One step more: post.tags are links, each with a tag |
| Delete behaviour | Managed by Prisma | You choose it |
| Use when | A plain link is enough | The link needs its own data or rules |
Nested Writes
A nested write creates, links or unlinks related records inside the same
create or update call. Prisma runs the whole thing as one transaction: either everything
succeeds, or nothing is saved.
| Operation | What it does |
|---|---|
create | Create the related record(s) and link them |
connect | Link existing records, found by a unique field |
connectOrCreate | Link a record if it exists, create it if not |
disconnect | Remove the link, keeping both records |
set | Replace all links with exactly this list |
delete, update, upsert | Change the related records themselves |
Creating a user with posts and a profile in one call
You never set userId or authorId yourself: Prisma fills in the foreign keys because it
knows how the records are related.
Tagging a post (implicit many-to-many)
Tagging a post (explicit many-to-many)
With an explicit join model you create a PostTag row, and inside it connect or create the
Tag. It's one level deeper than the implicit version.
data object, set a relation either by its foreign key (authorId: 1) or through the
relation field (author: { connect: { id: 1 } }). Mixing the two styles in the same call gives a type
error. Nested writes need the relation-field style.
Hands-On Exercises
Add an implicit many-to-many relation between Post and Tag. In a single create call, create a post for an existing user, tagged with "prisma" (which already exists) and "typescript" (which doesn't). Then look at the join table in Prisma Studio.
Write a function setPostTags(slug, tagNames) that makes a post's tags exactly match the given list, creating any tags that don't exist yet.
A course platform links students to courses. For each link it must record when the student enrolled and whether they've completed the course. Design the models, and explain why an implicit relation won't do.
📄 View solutionChapter 8 Quick Reference
- Many-to-many needs a join table linking the two models
- Implicit: list fields on both sides; Prisma manages a hidden
_AToBtable; no extra data, single-field IDs only, not on MongoDB - Explicit: a join model with two relations and
@@id([aId, bId]); can store extra data and set delete rules - Nested writes:
create,connect,connectOrCreate,disconnect,set,delete,update,upsert - A nested write runs as one transaction; Prisma fills in the foreign keys
- Set a relation by foreign key or by relation field — not both in one call