src/content/docs/relations-v1-v2.mdx
import Callout from '@mdx/Callout.astro'; import Prerequisites from "@mdx/Prerequisites.astro"; import Section from '@mdx/Section.astro'; import Npx from "@mdx/Npx.astro"; import Npm from "@mdx/Npm.astro"; import CodeTabs from '@mdx/CodeTabs.astro'; import CodeTab from '@mdx/CodeTab.astro';
One of the biggest updates were in Relations Schema definition
The first difference is that you no longer need to specify relations for each table separately in different objects and
then pass them all to drizzle() along with your schema. In Relational Queries v2, you now have one dedicated place to
specify all the relations for all the tables you need.
The r parameter in the callback provides comprehensive autocomplete
functionality - including all tables from your schema and functions such as one, many, and through - essentially
offering everything you need to specify your relations.
// relations.ts
import * as schema from "./schema"
import { defineRelations } from "drizzle-orm"
export const relations = defineRelations(schema, (r) => ({
...
}));
// index.ts
import { relations } from "./relations"
import { drizzle } from "drizzle-orm/..."
const db = drizzle(process.env.DATABASE_URL, { relations })
export const users = p.pgTable('users', { id: p.integer().primaryKey(), name: p.text(), invitedBy: p.integer('invited_by'), });
export const posts = p.pgTable('posts', { id: p.integer().primaryKey(), content: p.text(), authorId: p.integer('author_id'), });
</Callout>
**One place for all your relations**
<Callout title="❌ v1">
```ts
import { relations } from "drizzle-orm/_relations";
import { users, posts } from './schema';
export const usersRelation = relations(users, ({ one, many }) => ({
invitee: one(users, {
fields: [users.invitedBy],
references: [users.id],
}),
posts: many(posts),
}));
export const postsRelation = relations(posts, ({ one, many }) => ({
author: one(users, {
fields: [posts.authorId],
references: [users.id],
}),
}));
export const relations = defineRelations(schema, (r) => ({ users: { invitee: r.one.users({ from: r.users.invitedBy, to: r.users.id, }), posts: r.many.posts(), }, posts: { author: r.one.users({ from: r.posts.authorId, to: r.users.id, }), }, }));
</Callout>
You can still separate it into different `parts`, and you can make the parts any size you want
```ts
import { defineRelations, defineRelationsPart } from 'drizzle-orm';
import * as schema from "./schema";
export const relations = defineRelations(schema, (r) => ({
users: {
invitee: r.one.users({
from: r.users.invitedBy,
to: r.users.id,
}),
posts: r.many.posts(),
}
}));
export const part = defineRelationsPart(schema, (r) => ({
posts: {
author: r.one.users({
from: r.posts.authorId,
to: r.users.id,
}),
}
}));
and then you can provide it to the db instance
const db = drizzle(process.env.DB_URL, { relations: { ...relations, ...part } })
Define many without one
In v1, if you wanted only the many side of a relationship, you had to specify the one side on the other end,
which made for a poor developer experience.
In v2, you can simply use the many side without any additional steps
export const usersRelation = relations(users, ({ one, many }) => ({ posts: many(posts), }));
export const postsRelation = relations(posts, ({ one, many }) => ({ author: one(users, { fields: [posts.authorId], references: [users.id], }), }));
</Callout>
<Callout title='✅ v2'>
```ts
import { defineRelations } from "drizzle-orm";
import * as schema from "./schema";
export const relations = defineRelations(schema, (r) => ({
users: {
posts: r.many.posts({
from: r.users.id,
to: r.posts.authorId,
}),
},
}));
New optional option
optional: false at the type level makes the author key in the posts object required.
This should be used when you are certain that this specific entity will always exist.
export const relations = defineRelations(schema, (r) => ({ users: { posts: r.many.posts({ from: r.users.id, to: r.posts.authorId, optional: false, }), }, }));
</Callout>
**No modes in `drizzle()`**
We found a way to use the same strategy for all MySQL dialects, so there's no need to specify them
<Callout title="❌ v1">
```ts
import * as schema from './schema'
const db = drizzle(process.env.DATABASE_URL, { mode: "planetscale", schema });
// or
const db = drizzle(process.env.DATABASE_URL, { mode: "default", schema });
const db = drizzle(process.env.DATABASE_URL, { relations });
</Callout>
**`from` and `to` upgrades**
We've renamed `fields` to `from` and `references` to `to`, and we made both accept either a single value or an array
<Callout title="❌ v1">
```ts
...
author: one(users, {
fields: [posts.authorId],
references: [users.id],
}),
...
relationName -> alias
export const postsRelation = relations(posts, ({ one }) => ({ author: one(users, { fields: [posts.authorId], references: [users.id], relationName: "author_post", }), }));
</Callout>
<Callout title='✅ v2'>
```ts
import { defineRelations } from "drizzle-orm";
import * as schema from "./schema";
export const relations = defineRelations(schema, (r) => ({
posts: {
author: r.one.users({
from: r.posts.authorId,
to: r.users.id,
alias: "author_post",
}),
},
}));
custom types new functions
There are a few new function were added to custom types, so you can control how data is mapped on Relational Queries v2:
<Callout collapsed='fromJson'> Optional mapping function, that is used for transforming data returned by transformed to JSON in database data to desired format For example, when querying bigint column via [RQB](https://orm.drizzle.team/docs/rqb-v2) or [JSON functions](https://orm.drizzle.team/docs/json-functions), the result field will be returned as it's string representation, as opposed to bigint from regular query To handle that, we need a separate function to handle such field's mapping: ```ts fromJson(value: string): bigint { return BigInt(value); }, ``` It'll cause the returned data to change from: ```ts { customField: "5044565289845416380"; } ``` to: ```ts { customField: 5044565289845416380n; } ``` </Callout> <Callout collapsed='forJsonSelect'> Optional selection modifier function, that is used for modifying selection of column inside [JSON functions](https://orm.drizzle.team/docs/json-functions) Additional mapping that could be required for such scenarios can be handled using fromJson function Used by [relational queries](https://orm.drizzle.team/docs/rqb-v2)For example, when using bigint we need to cast field to text to preserve data integrity
forJsonSelect(identifier: SQL, sql: SQLGenerator, arrayDimensions?: number): SQL {
return sql`${identifier}::text`
},
This will change query from:
SELECT
row_to_json("t".*)
FROM
(
SELECT
"table"."custom_bigint" AS "bigint"
FROM
"table"
) AS "t"
to:
SELECT
row_to_json("t".*)
FROM
(
SELECT
"table"."custom_bigint"::text AS "bigint"
FROM
"table"
) AS "t"
Returned by query object will change from:
{
bigint: 5044565289845416000; // Partial data loss due to direct conversion to JSON format
}
to:
{
bigint: "5044565289845416380"; // Data is preserved due to conversion of field to text before JSON-ification
}
through for many-to-many relations
Previously, you would need to query through a junction table and then map it out for every response
You don't need to do it now!
<Callout collapsed='Schema'> ```ts import * as p from "drizzle-orm/pg-core";export const users = p.pgTable("users", { id: p.integer().primaryKey(), name: p.text(), verified: p.boolean().notNull(), });
export const groups = p.pgTable("groups", { id: p.integer().primaryKey(), name: p.text(), });
export const usersToGroups = p.pgTable( "users_to_groups", { userId: p .integer("user_id") .notNull() .references(() => users.id), groupId: p .integer("group_id") .notNull() .references(() => groups.id), }, (t) => [p.primaryKey({ columns: [t.userId, t.groupId] })] );
</Callout>
<Callout title="❌ v1">
```ts
export const usersRelations = relations(users, ({ many }) => ({
usersToGroups: many(usersToGroups),
}));
export const groupsRelations = relations(groups, ({ many }) => ({
usersToGroups: many(usersToGroups),
}));
export const usersToGroupsRelations = relations(usersToGroups, ({ one }) => ({
group: one(groups, {
fields: [usersToGroups.groupId],
references: [groups.id],
}),
user: one(users, {
fields: [usersToGroups.userId],
references: [users.id],
}),
}));
// Query example
const response = await db.query.users.findMany({
with: {
usersToGroups: {
columns: {},
with: {
group: true,
},
},
},
});
export const relations = defineRelations(schema, (r) => ({ users: { groups: r.many.groups({ from: r.users.id.through(r.usersToGroups.userId), to: r.groups.id.through(r.usersToGroups.groupId), }), }, groups: { participants: r.many.users(), }, }));
```ts
// Query example
const response = await db.query.users.findMany({
with: {
groups: true,
},
});
Predefined filters
<Callout title="❌ v1"> Was not supported in v1 </Callout> <Callout title='✅ v2'> ```ts {10-12} import * as schema from './schema'; import { defineRelations } from 'drizzle-orm';export const relations = defineRelations(schema, (r) => ({ groups: { verifiedUsers: r.many.users({ from: r.groups.id.through(r.usersToGroups.groupId), to: r.users.id.through(r.usersToGroups.userId), where: { verified: true, }, }), }, }) );
```ts {4}
// Query example: get groups with all verified users
const response = await db.query.groups.findMany({
with: {
verifiedUsers: true,
},
});
where is now objectFor a complete API Reference please check our Select Filters docs </Callout>
<Callout collapsed='Complex filter example using RAW'> ```ts // schema.ts import { integer, jsonb, pgTable, text, timestamp } from "drizzle-orm/pg-core";export const users = pgTable("users", { id: integer("id").primaryKey(), name: text("name"), email: text("email").notNull(), age: integer("age"), createdAt: timestamp("created_at").defaultNow(), lastLogin: timestamp("last_login"), subscriptionEnd: timestamp("subscription_end"), lastActivity: timestamp("last_activity"), preferences: jsonb("preferences"), // JSON column for user settings/preferences interests: text("interests").array(), // Array column for user interests });
```ts
const response = db.query.users.findMany({
where: {
AND: [
{
OR: [
{ RAW: (table) => sql`LOWER(${table.name}) LIKE 'john%'` },
{ name: { ilike: "jane%" } },
],
},
{
OR: [
{ RAW: (table) => sql`${table.preferences}->>'theme' = 'dark'` },
{ RAW: (table) => sql`${table.preferences}->>'theme' IS NULL` },
],
},
{ RAW: (table) => sql`${table.age} BETWEEN 25 AND 35` },
],
},
});
orderBy is now objectconst usersWithPosts = await db.query.usersTable.findMany({
where: {
id: {
gt: 10
},
posts: {
content: {
like: 'M%'
}
}
},
});
drizzle-kit pullIn new version drizzle-kit pull supports pulling relations.ts file in a new syntax:
Transfer generated relations code from drizzle/relations.ts to the file you are using to specify your relations
├ 📂 drizzle
│ ├ 📂 meta
│ ├ 📜 migration.sql
│ ├ 📜 relations.ts ────────┐
│ └ 📜 schema.ts |
├ 📂 src │
│ ├ 📂 db │
│ │ ├ 📜 relations.ts <─────┘
│ │ └ 📜 schema.ts
│ └ 📜 index.ts
└ …
You may need to change this import to a file where ALL your schema tables are located.
If there are multiple schema files, you can do the following:
import * as schema1 from './schema1'
import * as schema2 from './schema2'
...
Change drizzle database instance creation and provide relations object instead of schema
const db = drizzle('<url>', { schema })
</Callout>
<Callout title='After'>
```ts
// should be imported from a file in Step 2
import { relations } from './relations'
import { drizzle } from 'drizzle-orm/...'
const db = drizzle('<url>', { relations })
If you want to migrate manually, you can check our Drizzle Relations section for the complete API reference and examples of one-to-one, one-to-many, and many-to-many relations.
where statementsYou can check our Select Filters docs to see examples and a complete API reference.
With the new syntax, you can use AND, OR, NOT, and RAW, plus all the filtering operators that
were previously available in Relations v1.
Examples <CodeTabs items={["simple eq", "using AND", "using OR", "using NOT", "complex example using RAW"]}> <CodeTab>
const response = db.query.users.findMany({
where: {
age: 15,
},
});
select "users"."id" as "id", "users"."name" as "name"
from "users"
where ("users"."age" = $1)
export const users = pgTable("users", { id: integer("id").primaryKey(), name: text("name"), email: text("email").notNull(), age: integer("age"), createdAt: timestamp("created_at").defaultNow(), lastLogin: timestamp("last_login"), subscriptionEnd: timestamp("subscription_end"), lastActivity: timestamp("last_activity"), preferences: jsonb("preferences"), // JSON column for user settings/preferences interests: text("interests").array(), // Array column for user interests });
```ts
const response = db.query.users.findMany({
where: {
AND: [
{
OR: [
{ RAW: (table) => sql`LOWER(${table.name}) LIKE 'john%'` },
{ name: { ilike: "jane%" } },
],
},
{
OR: [
{ RAW: (table) => sql`${table.preferences}->>'theme' = 'dark'` },
{ RAW: (table) => sql`${table.preferences}->>'theme' IS NULL` },
],
},
{ RAW: (table) => sql`${table.age} BETWEEN 25 AND 35` },
],
},
});
orderBy statementsOrder by was simplified to a single object, where you specify the column and the sort direction (asc or desc)
many-to-many queriesRelational Queries v1 had a very complex way of managing many-to-many queries. You had to use junction tables to query through them explicitly, and then map those tables out, like this:
const response = await db.query.users.findMany({
with: {
usersToGroups: {
columns: {},
with: {
group: true,
},
},
},
});
After upgrading to Relational Queries v2, your many-to-many relation will look like this:
import * as schema from './schema';
import { defineRelations } from 'drizzle-orm';
export const relations = defineRelations(schema, (r) => ({
users: {
groups: r.many.groups({
from: r.users.id.through(r.usersToGroups.userId),
to: r.groups.id.through(r.usersToGroups.groupId),
}),
},
groups: {
participants: r.many.users(),
},
}));
And when you migrate your query, it will become this:
// Query example
const response = await db.query.users.findMany({
with: {
groups: true,
},
});
We've made an upgrade in a way, that all previous queries and relations definitions are still available for you. In this case you can migrate your codebase query by query without a need for a huge refactoring
To define relations using Relational Queries v1, you would need to import it from drizzle-orm
In Relational Queries v2 we moved it to drizzle-orm/_relations to give you some time for a migration
._queryTo use Relational Queries v1 you had to write db.query.
In Relational Queries v2, we moved it to db._query so that db.query could be used for a new syntax,
while still giving you the option to use the old syntax via db._query.
We had a long discussion about whether we should just deprecate db.query and replace it with
something like db.query2 or db.queryV2. In the end, we decided that all new APIs should remain
as simple as db.query, and that requiring you to replace all of your queries with db._query if you
want to keep using the old syntax is preferable to forcing everyone in the future to use
db.queryV2, db.queryV3, db.queryV4, etc.
// Using RQBv2 await db.query.users.findMany();
</Callout>
##### Step 3
Define new relations or pull them using [this guide](#how-to-migrate-relations-schema-definition-from-v1-to-v2),
then use them in your new queries or migrate your existing queries one by one.
### Internal changes
1. Every `drizzle` database, `session`, `migrator` and `transaction` instance, gained 2 additional generic arguments for RQB v2 queries
<Callout collapsed='Examples'>
**migrator**
<Callout type='error' title='before'>
```ts
export async function migrate<
TSchema extends Record<string, unknown>
>(
db: NodePgDatabase<TSchema>,
config: MigrationConfig,
) {
...
}
DrizzleConfig generic with TRelations argument and relations: TRelations fielddrizzle-orm and drizzle-orm/relations to drizzle-orm/_relations. The original imports now
include new types used by Relational Queries v2, so make sure to update your imports if you intend to use the older types:${dialect}-core/query-builders/query files were moved to ${dialect}-core/query-builders/_query
with RQB v2's alternatives being put in their place