Eager Loading with include and select
Objectives
By the end of this lesson, you should be able to:
- Load related data in the same query with
include - Choose exactly which fields to return with
select - Explain the “N+1 query” problem, and how
includeavoids it
💡 Why this matters: Modeling a relationship (Lessons 1 and 2) doesn’t automatically load it, by default, fetching a book returns just the book. This lesson covers pulling related data in efficiently, without a wall of extra queries.
⚠️ A note on verification: as throughout this module, the Prisma CLI can’t run inside this course’s own sandboxed tooling. The code below reflects Prisma Client’s stable, current, documented query API. Try it yourself, on your own machine, against a real database.
Without include
const book = await prisma.book.findUnique({ where: { id: 1 } });
console.log(book);
{ id: 1, title: 'Clean Code', authorId: 1 }
Just the book’s own fields, authorId is there, but the actual author isn’t loaded.
The N+1 Problem
A naive way to get each book’s author would be a separate query per book:
const books = await prisma.book.findMany();
for (const book of books) {
const author = await prisma.author.findUnique({ where: { id: book.authorId } });
console.log(book.title, author.name);
}
One query for the books, then one more query per book to fetch its author, ten books means eleven total queries. This is the “N+1 problem”, N extra queries for N results, and it gets slow fast.
Fixing It With include
const books = await prisma.book.findMany({
include: { author: true }
});
console.log(books);
[
{ id: 1, title: 'Clean Code', authorId: 1, author: { id: 1, name: 'Robert Martin' } },
{ id: 2, title: 'Clean Architecture', authorId: 1, author: { id: 1, name: 'Robert Martin' } }
]
include: { author: true } loads the related author for every book in one round trip. Prisma generates a single efficient query underneath, no matter how many books come back, this replaces the entire N+1 loop above.
include Works Both Directions
const author = await prisma.author.findUnique({
where: { id: 1 },
include: { books: true }
});
console.log(author);
{ id: 1, name: 'Robert Martin', books: [ { id: 1, title: 'Clean Code', authorId: 1 }, { id: 2, title: 'Clean Architecture', authorId: 1 } ] }
And it works for many-to-many too:
const book = await prisma.book.findUnique({
where: { id: 1 },
include: { tags: true }
});
console.log(book);
{ id: 1, title: 'Clean Code', authorId: 1, tags: [ { id: 1, name: 'programming' } ] }
Choosing Fields With select
include adds relations on top of every field. select replaces the whole shape, returning only the fields explicitly listed:
const books = await prisma.book.findMany({
select: {
title: true,
author: { select: { name: true } }
}
});
console.log(books);
[
{ title: 'Clean Code', author: { name: 'Robert Martin' } },
{ title: 'Clean Architecture', author: { name: 'Robert Martin' } }
]
No id, no authorId, just the two fields actually asked for, nested the same way for the related author. select is the right choice when an API response (Module 10’s capstone) should only expose specific fields.
Try It
- Fetch a book with
include: { author: true }, and confirm the author’s full object appears nested inside. - Fetch an author with
include: { books: true }, and confirm every one of their books appears in an array. - Rewrite one of the queries above using
selectinstead, returning onlytitleand the author’sname. - Explain, in your own words, what the N+1 problem is, and which single option fixes it.
Recap
- By default, a Prisma query returns only a model’s own fields, related data needs
includeorselectexplicitly. include: { relationName: true }loads related records in one efficient query, avoiding the N+1 problem of querying each relation separately.selectcontrols the exact shape of the response, including nestedselectfor related models, useful whenever an API shouldn’t expose every field.
This is the final lesson of this module before exercises. Next module: the full CRUD query API, replacing the raw SQL from Modules 1 and 2 with Prisma Client.