frameworks
July 21, 2026 · 7 min read · 0 views

Drizzle ORM 0.35: New Relational Queries API & SQLite Improvements for Edge Computing

Drizzle ORM 0.35 introduces a powerful relational queries API, improved SQLite support, and better edge runtime compatibility—enabling type-safe database operations across serverless and edge platforms.

What’s New in Drizzle ORM 0.35

Drizzle ORM, the lightweight TypeScript ORM for SQL databases, just released version 0.35 with significant improvements aimed at modern JavaScript developers building serverless and edge applications. This release focuses on three core areas: a new relational queries API that simplifies complex JOINs, enhanced SQLite support for edge runtimes, and better developer experience through improved type inference.

If you’re building applications on Vercel Edge Functions, Cloudflare Workers, or AWS Lambda, this release directly addresses pain points around database access patterns and runtime compatibility.

Understanding the New Relational Queries API

Drizzle 0.35 introduces a query builder pattern that feels more intuitive for developers accustomed to ORMs like Prisma or Sequelize. The new relational API lets you query related data without manually constructing complex SQL joins.

The Old Way (0.34 and Earlier)

Previously, fetching related data required explicit JOIN syntax:

import { db } from './db';
import { users, posts, comments } from './schema';
import { eq } from 'drizzle-orm';

const userWithPosts = await db
  .select()
  .from(users)
  .leftJoin(posts, eq(users.id, posts.userId))
  .leftJoin(comments, eq(posts.id, comments.postId))
  .where(eq(users.id, 1));

// Result requires manual normalization
console.log(userWithPosts); // Array of raw rows

While explicit, this approach requires developers to normalize the results themselves and reason about JOINs at a lower level.

The New Relational Queries Way

With 0.35, you can use the new .query API:

import { db } from './db';
import { users } from './schema';
import { eq } from 'drizzle-orm';

const userWithPosts = await db.query.users.findFirst({
  where: eq(users.id, 1),
  with: {
    posts: {
      with: {
        comments: true,
      },
    },
  },
});

// Result is properly typed and normalized
console.log(userWithPosts);
// {
//   id: 1,
//   name: 'Alice',
//   posts: [
//     {
//       id: 101,
//       title: 'First Post',
//       comments: [{ id: 1001, text: 'Great post!' }],
//     },
//   ],
// }

The key differences:

  • Automatic normalization: Results come back as properly nested objects
  • Type-safe: TypeScript knows the exact shape of your data
  • Readable: The with object mirrors your schema relationships
  • Flexible: You can conditionally include relations using partial objects

Setting Up Drizzle 0.35 with Relational Queries

To use the new relational queries API, you need to define relationships in your schema. Let’s walk through a complete setup:

Step 1: Define Your Schema with Relations

// schema.ts
import { integer, text, timestamp, pgTable, serial, foreignKey } 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(),
});

export const posts = pgTable(
  'posts',
  {
    id: serial('id').primaryKey(),
    title: text('title').notNull(),
    content: text('content'),
    userId: integer('user_id').notNull(),
    createdAt: timestamp('created_at').defaultNow(),
  },
  (table) => ([
    foreignKey({
      columns: [table.userId],
      references: [users.id],
      name: 'posts_user_fk',
    }),
  ])
);

export const comments = pgTable(
  'comments',
  {
    id: serial('id').primaryKey(),
    text: text('text').notNull(),
    postId: integer('post_id').notNull(),
    userId: integer('user_id').notNull(),
    createdAt: timestamp('created_at').defaultNow(),
  },
  (table) => ([
    foreignKey({
      columns: [table.postId],
      references: [posts.id],
      name: 'comments_post_fk',
    }),
    foreignKey({
      columns: [table.userId],
      references: [users.id],
      name: 'comments_user_fk',
    }),
  ])
);

// Define relations for type safety
export const usersRelations = relations(users, ({ many }) => ({
  posts: many(posts),
  comments: many(comments),
}));

export const postsRelations = relations(posts, ({ one, many }) => ({
  author: one(users, {
    fields: [posts.userId],
    references: [users.id],
  }),
  comments: many(comments),
}));

export const commentsRelations = relations(comments, ({ one }) => ({
  post: one(posts, {
    fields: [comments.postId],
    references: [posts.id],
  }),
  author: one(users, {
    fields: [comments.userId],
    references: [users.id],
  }),
}));

Step 2: Query with Relations

// queries.ts
import { db } from './db';
import { users, posts } from './schema';
import { eq } from 'drizzle-orm';

// Find a user with all their posts and comments
const userProfile = await db.query.users.findFirst({
  where: eq(users.id, 1),
  with: {
    posts: {
      with: {
        comments: {
          with: {
            author: true,
          },
        },
      },
    },
  },
});

// Find all users with their post count (using columns to limit fields)
const usersWithPostCount = await db.query.users.findMany({
  with: {
    posts: {
      columns: {
        id: true,
        title: true,
      },
    },
  },
});

// Conditional relations - only include if needed
const posts_only = await db.query.users.findFirst({
  where: eq(users.id, 1),
  with: {
    posts: true, // Include all posts with default fields
  },
});

You can test JSON structures returned from these queries using JSON Formatter to ensure your data validation logic is correct.

SQLite Edge Runtime Improvements

Drizzle 0.35 significantly improves SQLite support for edge environments. This is crucial if you’re using platforms like Cloudflare Workers or Vercel Edge Functions where you need a lightweight, embedded database.

Why SQLite on Edge?

Traditional database connections from edge functions to remote databases incur latency penalties. SQLite eliminates this by embedding the database directly in your edge worker:

// Works on Cloudflare Workers, Vercel Edge Functions
import { Database } from '@vlcn.io/crsqlite-wasm';
import { drizzle } from 'drizzle-orm/better-sqlite3';
import * as schema from './schema';

export default {
  async fetch(request: Request, env: Env) {
    const db = drizzle(new Database(env.DB), { schema });
    
    const recentPosts = await db.query.posts.findMany({
      limit: 10,
      orderBy: (posts, { desc }) => [desc(posts.createdAt)],
    });
    
    return new Response(JSON.stringify(recentPosts), {
      headers: { 'Content-Type': 'application/json' },
    });
  },
};

Connection Pool Improvements

For traditional server environments, 0.35 includes better connection pooling:

// db.ts
import { drizzle } from 'drizzle-orm/node-postgres';
import { Pool } from 'pg';
import * as schema from './schema';

const pool = new Pool({
  host: process.env.DB_HOST,
  port: 5432,
  database: process.env.DB_NAME,
  user: process.env.DB_USER,
  password: process.env.DB_PASSWORD,
  max: 20, // Maximum connections
  idleTimeoutMillis: 30000,
  connectionTimeoutMillis: 2000,
});

export const db = drizzle(pool, { schema });

Getting Started with Drizzle 0.35

Installation

npm install drizzle-orm@latest
npm install -D drizzle-kit

Create Your First Migration

npx drizzle-kit generate

This generates SQL migration files based on your schema definitions.

Running Migrations

// migrate.ts
import { migrate } from 'drizzle-orm/node-postgres/migrator';
import { db } from './db';
import path from 'path';

async function runMigrations() {
  await migrate(db, {
    migrationsFolder: path.join(process.cwd(), 'drizzle'),
  });
  console.log('Migrations completed!');
}

runMigrations().catch(console.error);

Common Pitfalls with Relational Queries

1. Forgetting to Define Relations

Doesn’t work:

// schema.ts has no relations defined
const user = await db.query.users.findFirst({
  with: { posts: true }, // Error: posts not recognized
});

Works:

// Define relations in schema
export const usersRelations = relations(users, ({ many }) => ({
  posts: many(posts),
}));

const user = await db.query.users.findFirst({
  with: { posts: true }, // TypeScript knows about posts
});

2. N+1 Query Problem

The relational API solves N+1 queries by batching, but watch out for deep nesting:

// ✅ Good - single query with batching
const users = await db.query.users.findMany({
  with: {
    posts: true,
  },
});

// ❌ Potentially slow - nested deep without filtering
const users = await db.query.users.findMany({
  with: {
    posts: {
      with: {
        comments: {
          with: {
            replies: {
              with: {
                author: true,
              },
            },
          },
        },
      },
    },
  },
});

3. Type Inference with Partial Relationships

When conditionally including relations, explicitly type your result:

type UserProfile = typeof schema.users.$inferSelect & {
  posts: (typeof schema.posts.$inferSelect)[] | undefined;
};

const user: UserProfile = await db.query.users.findFirst({
  where: eq(users.id, 1),
  with: {
    posts: Math.random() > 0.5 ? true : false, // Conditional
  },
});

Why It Matters: Real-World Impact

Drizzle ORM 0.35’s improvements matter because:

  1. Developer Experience: The relational queries API reduces cognitive overhead when working with complex data models. You write less boilerplate and get better type safety.

  2. Edge Compatibility: As serverless and edge computing become standard for modern applications, having first-class support for SQLite and optimized connection handling is crucial.

  3. Type Safety: Full TypeScript support throughout the query building process catches schema mismatches at compile time, not runtime.

  4. Performance: Drizzle’s approach to batching queries in the relational API helps prevent N+1 query problems common in other ORMs.

If you’re working with complex JSON responses from your queries, you can use JSON Formatter to validate and debug the structure. When you need to compare query results before and after schema changes, Diff Checker helps catch unintended differences.

Migration Path from Older Versions

If you’re on Drizzle 0.33 or 0.34, upgrading is generally smooth:

  1. Update package: npm install [email protected]
  2. Keep existing code: Old query syntax still works
  3. Gradually adopt relational API: Migrate queries one at a time
  4. No schema changes required: Existing schema definitions work as-is

Next Steps

Start exploring Drizzle ORM 0.35 by:

  • Building a schema with proper relations
  • Testing the relational queries API in your project
  • Benchmarking query performance if you have complex nested data
  • Considering SQLite for edge deployments if latency is a constraint

The combination of type-safe queries, relational convenience, and edge runtime support makes Drizzle 0.35 a solid choice for modern TypeScript backend development.

Related Kloubot Tools

This post was generated with AI assistance and reviewed for accuracy.