Nested posts from one findMany
const result = await db.query.users.findMany({ with: { posts: true, }, });
$ bun index.ts
[
{
id: 1,
name: "Ada",
email: "ada@example.com",
createdAt: 2026-09-11T11:18:33.188Z,
posts: [
{
id: 1,
title: "SQL is awesome",
content: "first post",
authorId: 1,
}, {
id: 2,
title: "But check relational queries",
content: "second post",
authorId: 1,
}
],
}
]
select "users"."id", "users"."name", "users"."email", "users"."created_at", "users_posts"."data" as "posts" from "users" "users" left join lateral ( select coalesce( json_agg(json_build_array( "users_posts"."id", "users_posts"."title", "users_posts"."content", "users_posts"."author_id" )), '[]'::json) as "data" from "posts" "users_posts" where "users_posts"."author_id" = "users"."id" ) "users_posts" on true
Drizzle ORM returns a user with an array of posts from one findMany call. That call is db.query.users.findMany, with posts true. Ada comes back with two posts already nested. The SQL Drizzle sent is one statement. drizzle-orm 0.45.2. Episode one of this series left a row in users. This sitting adds posts and reads both back in one call.
Two lines install Drizzle
$ bun add drizzle-orm@0.45.2 pg bun add v1.2.23 (cf136713) Resolving dependencies Resolved, downloaded and extracted [41] Saved lockfile installed drizzle-orm@0.45.2 installed pg@8.23.0 38 packages installed [3.67s] $ bun add -d drizzle-kit@0.31.10 @types/pg bun add v1.2.23 (cf136713) Saved lockfile installed drizzle-kit@0.31.10 with binaries: - drizzle-kit installed @types/pg@8.23.1 [22.00ms] done
runtime
dev only
Install is two lines. bun add drizzle-orm at 0.45.2 and pg puts the query builder and the PostgreSQL driver in the project. bun add dash d drizzle-kit at 0.31.10 adds the command line tool as a development dependency. The first command installed thirty eight packages in three and seven tenths of a second.
0.45.2 still takes schema
drizzle-orm 0.45.2 and drizzle-kit 0.31.10, on PostgreSQL. This is the stable line. A version one is on the way, 1.0.0 release candidate 4. The live Query page currently installs drizzle-orm at rc, and shows defineRelations. 0.45.2 still takes the tables and the relations objects under the name schema. The live docs pass a v2 object under the name relations. Mix the keys. db.query comes back empty.
users table and posts table
export const users = pgTable("users", { id: serial("id").primaryKey(), name: text("name").notNull(), email: text("email").notNull().unique(), createdAt: timestamp("created_at").defaultNow().notNull(), }); export const posts = pgTable("posts", { id: serial("id").primaryKey(), title: text("title").notNull(), content: text("content").notNull(), authorId: integer("author_id") .notNull() .references(() => users.id), });
The smallest program starts here. It returns nested rows. schema dot ts now has two tables. users is already in the series, with id, name, a unique email, and a created-at stamp. posts adds a title, content, and an authorId column pointing at the user.
Relations go in under schema
export const usersRelations = relations(users, ({ many }) => ({ posts: many(posts), })); export const postsRelations = relations(posts, ({ one }) => ({ author: one(users, { fields: [posts.authorId], references: [users.id], }), }));
import { drizzle } from "drizzle-orm/node-postgres"; import * as schema from "./schema"; const db = drizzle(process.env.DATABASE_URL!, { schema });
Two relations exports. usersRelations says users have many posts. postsRelations says a post has one author, matching authorId to users.id. Pass the tables and those relation objects into drizzle under the name schema. db.query is compiled from that schema object. db.select from users still works if you skip the schema object. db.query.users needs the schema object.
Ada and two nested posts
$ bun index.ts
[
{
id: 1,
name: "Ada",
email: "ada@example.com",
createdAt: 2026-09-11T11:18:33.188Z,
posts: [
{
id: 1,
title: "SQL is awesome",
content: "first post",
authorId: 1,
}, {
id: 2,
title: "But check relational queries",
content: "second post",
authorId: 1,
}
],
}
]
bun index dot ts prints one user, Ada. posts is an array of two objects. Title one, SQL is awesome. Title two, But check relational queries. The posts array is already grouped on the way out.
A single SQL statement
On the Query page, under Partial fields select, the manual says keep in mind that a single SQL statement is outputted by Drizzle. findMany with posts true can return a nested object. Drizzle compiles one statement.
0.45.2 toSQL is one left join lateral
select "users"."id", "users"."name", "users"."email", "users"."created_at", "users_posts"."data" as "posts" from "users" "users" left join lateral ( select coalesce( json_agg(json_build_array( "users_posts"."id", "users_posts"."title", "users_posts"."content", "users_posts"."author_id" )), '[]'::json ) as "data" from "posts" "users_posts" where "users_posts"."author_id" = "users"."id" ) "users_posts" on true
toSQL on 0.45.2 prints one select, a left join lateral, and json_agg of json_build_array. LATERAL means Postgres runs the inner select once per user row. json_agg packs those related rows into one JSON array in the same SELECT. The driver never groups a flat join list. The live Include-subqueries block is the v2 generator. It uses row_to_json and aliases d0 and d1. Our capture uses json_build_array. The capture is the proof. The Relations page Select with joins tab is leftJoin plus a mappedResult you write yourself. Relational queries skip that mapping.
The relations graph is application-level
On the Relations page, the Foreign keys section. Relations do not affect the database schema in any way and do not create foreign keys implicitly. Two schemas, one with foreign keys and one without, will work exactly the same for relational queries. The relations graph is a TypeScript object that says which tables point at which. db.query reads that object. Our lab has the foreign key and the relations graph. db.query reads the graph. The references call on authorId is for migrations.
0.45.2 needs both sides of the graph
$ bun gotcha-many-only.ts
query keys [ "users", "posts" ]
THREW Error There is not enough
information to infer relation
"users.posts"
usersRelations says many posts. findMany with posts true still throws until postsRelations declares one users, with fields and references. The error reads There is not enough information to infer relation users.posts. The v1 to v2 page names this Define many without one. The v2 line can declare many alone. 0.45.2 still needs both sides of the graph. The matching one is what lets with posts true compile a join.
schema plus drizzle plus findMany
import { integer, pgTable, serial, text, timestamp } from "drizzle-orm/pg-core"; import { relations } from "drizzle-orm"; export const users = pgTable("users", { id: serial("id").primaryKey(), name: text("name").notNull(), email: text("email").notNull().unique(), createdAt: timestamp("created_at").defaultNow().notNull(), }); export const posts = pgTable("posts", { id: serial("id").primaryKey(), title: text("title").notNull(), content: text("content").notNull(), authorId: integer("author_id") .notNull() .references(() => users.id), }); export const usersRelations = relations(users, ({ many }) => ({ posts: many(posts), })); export const postsRelations = relations(posts, ({ one }) => ({ author: one(users, { fields: [posts.authorId], references: [users.id], }), }));
import { drizzle } from "drizzle-orm/node-postgres"; import * as schema from "./schema"; const db = drizzle(process.env.DATABASE_URL!, { schema }); const result = await db.query.users.findMany({ with: { posts: true, }, });
schema exports users, posts, usersRelations, and postsRelations. index imports the whole file as schema. drizzle gets the connection string and that schema object. Then db.query.users.findMany, with posts true. with posts true is how you ask for the nested array. The Query page columns option picks which fields come back. It still emits one statement.
Nested JavaScript from one SQL statement
$ bun index.ts
[
{
id: 1,
name: "Ada",
email: "ada@example.com",
createdAt: 2026-09-11T11:18:33.188Z,
posts: [
{ id: 1, title: "SQL is awesome",
content: "first post", authorId: 1 },
{ id: 2, title: "But check relational queries",
content: "second post", authorId: 1 }
],
}
]
select "users"."id", "users"."name", "users"."email", "users"."created_at", "users_posts"."data" as "posts" from "users" "users" left join lateral ( select coalesce( json_agg(json_build_array( "users_posts"."id", "users_posts"."title", "users_posts"."content", "users_posts"."author_id" )), '[]'::json) as "data" from "posts" "users_posts" where "users_posts"."author_id" = "users"."id" ) "users_posts" on true
bun index prints Ada, posts already nested. Two titles: SQL is awesome, and But check relational queries. bun tosql prints one select, left join lateral, json_agg of json_build_array. Declare the graph. Pass it as schema on 0.45.2. Call db.query. Nested JavaScript comes back from one SQL statement.
db.query.users is undefined
$ bun empty-query.ts query keys [] query.users undefined typeof query.users undefined select().from(users) rows 1 query.users.findMany THREW TypeError undefined is not an object (evaluating 'db.query.users.findMany')
$ bun gotcha-relations-key.ts
query keys with { relations } []
query.users undefined
This is the part people get wrong. db.select from users works. db.query.users is undefined. That is drizzle-orm issue 4390, closed January 2026, labelled rqb. drizzle never received the graph. On 0.45.2, omit schema. query keys prints an empty array. Pass the live-docs key relations. You get the same empty query. Tables only in schema is quieter. query.users exists. with posts true throws relation.referencedTable. On the rc line the empty-query miss is omitting relations.
Pasting defineRelations fails to compile
{
"dependencies": {
"drizzle-orm": "0.45.2",
"pg": "^8.23.0"
}
}
$ bun paste-v2.ts
SyntaxError: Export named
'defineRelations' not found
in module '.../drizzle-orm/index.js'.
Bun v1.2.23 (macOS arm64)
error TS2305: Module "drizzle-orm" has no exported member 'defineRelations'. 'relations' does not exist in type 'DrizzleConfig'
This is the part people get wrong. The Query and Relations pages install drizzle-orm at rc and show defineRelations. The docs skip this. npm i drizzle-orm still gives 0.45.2. Paste the live snippet onto 0.45.2 and Bun throws SyntaxError: Export named defineRelations not found. tsc says TS2305, no exported member defineRelations. tsc also says relations does not exist in type DrizzleConfig. The v1 to v2 page renames the option from schema to relations. Mix the names. db.query is empty. The v1 form that compiles is relations per table, passed as schema.
many posts throws until one users
$ bun gotcha-many-only.ts
query keys [ "users", "posts" ]
THREW Error There is not enough
information to infer relation
"users.posts"
This is the part people get wrong. On 0.45.2, usersRelations with only many posts throws There is not enough information to infer relation users.posts. The matching postsRelations, one users with fields and references, is what lets with posts true compile a join. The v1 to v2 page calls this Define many without one.
If you delete the relations objects
import { integer, pgTable, serial, text, timestamp } from "drizzle-orm/pg-core"; import { relations } from "drizzle-orm"; export const users = pgTable("users", { id: serial("id").primaryKey(), name: text("name").notNull(), email: text("email").notNull().unique(), createdAt: timestamp("created_at").defaultNow().notNull(), }); export const posts = pgTable("posts", { id: serial("id").primaryKey(), title: text("title").notNull(), content: text("content").notNull(), authorId: integer("author_id") .notNull() .references(() => users.id), }); export const usersRelations = relations(users, ({ many }) => ({ posts: many(posts), })); export const postsRelations = relations(posts, ({ one }) => ({ author: one(users, { fields: [posts.authorId], references: [users.id], }), }));
const db = drizzle(process.env.DATABASE_URL!, { schema }); const result = await db.query.users.findMany({ with: { posts: true }, });
The whole path is two tables, two relations exports, schema into drizzle, and one findMany with posts true. A nested object comes back. One SQL statement went out. drizzle-orm 0.45.2, drizzle-kit 0.31.10. If you delete the relations objects and keep the foreign keys, what does db.query still know?




















