Prisma Next is in early access.Read the docs

MongoDB data modeling

Model documents, embedded documents, and references for MongoDB, and decide when to embed and when to reference.

MongoDB stores data as documents grouped into collections. A document can nest objects and arrays directly, which gives you a real choice for related data: keep it inside the document (embed it) or store it in its own collection and link to it (reference it).

Making that choice well is the core of MongoDB data modeling, and it is what this page focuses on. If you are new to models and keys, start with the data modeling overview.

Documents, collections, and the _id key

A Prisma Next model maps to a collection, and each record is a document. Every document has an _id field as its primary key. ObjectId is the idiomatic type for it:

model User {
  id    ObjectId @id @map("_id")
  name  String
  email String
  @@map("users")
}

@map("_id") maps the model's id field to MongoDB's mandatory _id field, and @@map("users") names the collection.

Embed or reference

Two pieces of related data can live together in one document (embedded) or in separate collections linked by an id (referenced).

Embedding nests the data inside its parent: an order and its line items in one document. One read returns everything, and a write updates parent and children together, atomically. The trade-off: embedded data has no independent life. You cannot query it on its own, and it rides along on every read of the parent.

Referencing keeps the data in its own collection and stores the linked document's _id. Loading both takes a second lookup, but each record stands on its own: it can be queried, listed, and updated independently, and it is not duplicated when several parents point at it.

A quick way to decide:

SignalExampleLean toward
Always loaded with the parentAn order's line itemsEmbed
Small and bounded in sizeA user's mailing addressEmbed
No meaning outside the parentA post's SEO metadataEmbed
Grows without limitA user's activity eventsReference
Queried or updated on its ownProducts in a catalogReference
Shared by many parentsA tag on thousands of postsReference

A blog post's comments make it concrete. If you always render them with the post and their count stays modest, embed them. If comments can grow into the thousands, or you need "every comment by this author across posts", reference them.

Embedded documents

Describe the shape of embedded data with a type block, then use it as a field. An embedded type has no collection of its own; it exists only inside its parent document.

type Address {
  street  String
  city    String
  zip     String?
  country String
}

model User {
  id      ObjectId @id @map("_id")
  name    String
  address Address?
  @@map("users")
}

A single embedded value models a one-to-one. A list of embedded values models a one-to-many:

type CartItem {
  productId String
  name      String
  amount    Int
}

model Cart {
  id    ObjectId   @id @map("_id")
  items CartItem[]
  @@map("carts")
}

Cart.items lives inside the cart document. There is no separate collection and no join to load the items; every read of a cart returns them.

An embedded Address has no _id and cannot be queried on its own. It is loaded, updated, and deleted with the document that holds it. That is the right behavior for values that belong to their parent, and the wrong one for records with their own life.

References across collections

When related records need their own collection, store one document's _id on the other and declare the relation with the same @relation(fields:, references:) used everywhere in Prisma Next:

model User {
  id    ObjectId @id @map("_id")
  name  String
  posts Post[]
  @@map("users")
}

model Post {
  id       ObjectId @id @map("_id")
  title    String
  authorId ObjectId
  author   User     @relation(fields: [authorId], references: [id])
  @@map("posts")
}

This is a one-to-many by reference: many posts point at one user, and each post is queryable on its own. When you include the relation in a query, Prisma Next resolves it with a $lookup aggregation.

A $lookup is a real cost on hot paths, so document models often denormalize: embed a small copy of the fields a read needs (a comment stores its author's name) while the full record stays in its own collection. You trade some duplication, and the work of keeping the copy in sync, for reads that touch one document.

Polymorphic collections

MongoDB collections do not enforce a single shape, so it is idiomatic to store documents of different kinds in one collection and tell them apart with a discriminator field.

Prisma Next models this with a base model plus variant models, all sharing one collection:

model Post {
  id       ObjectId @id @map("_id")
  title    String
  kind     String

  @@discriminator(kind)
  @@map("posts")
}

model Article {
  summary String

  @@base(Post, "article")
}

model Tutorial {
  difficulty String
  duration   Int

  @@base(Post, "tutorial")
}

@@discriminator(kind) names the field that records each document's variant. @@base(Post, "article") declares Article as the "article" variant of Post. A query for posts returns articles and tutorials together, and you can narrow to one variant in queries.

Use one polymorphic collection when the variants are handled together far more than separately: a notifications collection of email, SMS, and push messages read as one stream. Prefer separate collections when the types rarely appear in the same query or need very different indexes.

Prompt your coding agent

Projects scaffolded with create-prisma@next install Prisma Next skills for your coding agent; the prisma-next-contract skill covers document modeling. Prompts that map to each section:

  • "Using the prisma-next-contract skill, add an embedded Address type to the User model."
  • "Cart items are always read with the cart. Model them as an embedded list."
  • "Comments can grow without limit. Move them from an embedded array to a referenced collection."
  • "Split the posts collection into Article and Tutorial variants with a discriminator."

Next steps

On this page