better-drizzle

Selecting fields

where vs select vs include - which rows qualify, and which fields survive.

Three options control a query's result, and they solve different problems:

GoalUse
"Only rows that match these conditions"where
"Only these scalar and relation fields"select
"The full row, plus these relations"include

where filters rows. select and include shape the payload - and the return type is inferred from whichever you use.

where filters rows

await client.posts.findMany({
	where: { published: true },
});

Without select or include, you get the full row for each table - all scalar columns, no relations.

select narrows the payload

select lists exactly the fields you want. Anything not listed is dropped, and the return type narrows to match:

const rows = await client.posts.findMany({
	select: {
		id: true,
		title: true,
	},
});
//    ^? { id: number; title: string }[]

Unknown select keys throw at runtime, including keys set to false or undefined. This also applies to nested projections. Use only declared columns and relations. Batch write projections (updateMany, deleteMany, updateEach, upsertMany) accept scalar columns only.

Reach for select when:

  • you want to reduce payload size
  • the response goes straight to an API boundary
  • you do not want to accidentally leak extra columns

include keeps the row and adds relations

include keeps every scalar column and attaches the relations you name:

const posts = await client.posts.findMany({
	include: { author: true },
});
//    ^? (Post & { author: User | null })[]

Reach for include when a handler wants the model plus related records.

Pick one

select and include are mutually exclusive on a single query level - use select to narrow, or include to keep-and-extend. (You can still narrow a relation inside an include; see below.)

Projecting relations

Both select and include can shape a relation by giving it a nested query instead of true:

const posts = await client.posts.findMany({
	select: {
		id: true,
		title: true,
		author: {
			select: { id: true, name: true },
		},
	},
});

The nested object accepts the same where, select, orderBy, and take you would use on a top-level query - so you can filter and order related rows too.

A note on API boundaries

At API boundaries, prefer explicit shapes. Returning large default payloads "because it was easy" is how extra columns leak into responses. A narrow select (or a small include) keeps the contract obvious and the payload small.

On this page