Database with Prisma
Website Rebuild with Next.js
Chapter 6 · Database with Prisma
Chapter 4's own getBreadcrumb was planted deliberately — one database query per ancestor level. This chapter catches it, resolved via Prisma's own relational include.
Prisma's Relational include
One query, resolved by Prisma into a single nested object — pageWithAncestors.parent.parent — rather than Chapter 4's own repeated single-row query loop.
Fixing the Breadcrumb
flattenAncestors still walks a chain with a while loop, one link at a time — but it's walking an object already fully loaded in memory from Prisma's single query, not issuing a new database round-trip per step the way Chapter 4's own version did. The fix isn't "avoid loops," it's "avoid a database query inside the loop."
Verified Honestly Against Every Sibling ORM
with keyword, the nested-object shape — deliberately mirrors Prisma's own include, both in naming and in structure... a real, stated design choice from Drizzle's own documentation." This chapter's own include syntax is the shape every later sibling either mirrors closely (Drizzle) or reaches independently by a different route (Django's select_related, Eloquent's with(), ActiveRecord's includes()) — not a coincidence, and not one-directional flattery either; genuinely convergent design.
The Fixed-Depth Limitation
include above only reaches three levels deep — no more. There's no way to express "load the whole ancestor chain, however deep it is" using Prisma's own nested include syntax natively; a page stored deeper than the hardcoded nesting simply has its own remaining ancestors silently absent from the result, with parent: null at the cutoff rather than an error. This isn't a Prisma weakness specifically — it's a structural limit every ORM used across this entire series shares, each one independently rediscovering it in its own chapter, since none of them can express unbounded self-referencing depth without a raw recursive SQL query.
Five ORMs' Eager Loading, Compared
| Framework | Syntax |
|---|---|
| Next.js (Prisma) | include: { parent: { include: { parent: true } } } |
| Django | select_related('parent__parent__parent') |
| Laravel (Eloquent) | with('parent.parent.parent') |
| Rails (ActiveRecord) | includes(parent: { parent: :parent }) |
| Astro (Drizzle) | with: { parent: { with: { parent: true } } } |
Hands-On Exercises
Replace PageShell's own getBreadcrumb loop with the include-based query and flattenAncestors() from this chapter, and confirm the same correct, correctly-ordered ancestor chain still renders for a real nested page.
📄 View solutionQuery a top-level page (one with no parent at all) through this chapter's own include clause, and explain what pageWithAncestors.parent equals, and why Prisma doesn't raise an error even though the include asks for a relation that doesn't exist for this row.
📄 View solutionStore a page five levels deep and confirm the nested include clause from this chapter (three levels) fails to load the full ancestor chain — demonstrating the shared fixed-depth limitation directly, with a real example rather than just accepting the claim.
📄 View solutionChapter 6 Quick Reference
include: { parent: { include: { parent: true } } }— replaces the deliberately-planted N+1 loop with one queryflattenAncestors()— still a loop, but walking an already-loaded object in memory, not issuing new queries- Verified kinship — Drizzle's own
withdeliberately mirrors this chapter'sinclude, a documented design goal on Drizzle's own side - Fixed-depth limitation — no ORM in this series can express unbounded self-referencing depth natively; every one needs a raw recursive SQL query for that
- Next chapter: Rendering Content & the Kanji Edge Case