StartupKitstartupkit
Database

Queries

Query patterns with Drizzle ORM

Drizzle provides two query APIs: the SQL-like query builder and the relational query API.

Basic Queries

Select all

import { ,  } from "@repo/db"

const  = await .select().from()

Select with columns

const  = await 
  .select({
    : .id,
    : .email
  })
  .from()

Where clause

import { ,  } from "@repo/db"
import { , , , ,  } from "drizzle-orm"

// Simple equality
const  = await 
  .select()
  .from()
  .where((.email, "[email protected]"))

// Multiple conditions
const  = await 
  .select()
  .from()
  .where(
    (
      (.role, "admin"),
      (.emailVerified, true)
    )
  )

// Pattern matching
const  = await 
  .select()
  .from()
  .where((.email, "%@gmail.com"))

Order and limit

const  = await 
  .select()
  .from()
  .orderBy((.createdAt))
  .limit(10)

Relational Queries

Use the relational API for nested data:

// Get user with their team memberships
const  = await .query.users.findFirst({
  : (.id, ),
  : {
    : {
      : {
        : true
      }
    }
  }
})

Find many with relations

const  = await .query.teams.findMany({
  : {
    : {
      : {
        : {
          : {
            : true,
            : true,
            : true
          }
        }
      }
    }
  }
})

Insert

Single insert

const [] = await 
  .insert()
  .values({
    : .(),
    : "[email protected]",
    : "New User"
  })
  .returning()

Bulk insert

await .insert().values([
  { : "1", : "First Post", :  },
  { : "2", : "Second Post", :  }
])

Insert with conflict handling

await 
  .insert()
  .values({ , ,  })
  .onConflictDoUpdate({
    : .email,
    : { , : new () }
  })

Update

await 
  .update()
  .set({ 
    : "Updated Name",
    : new ()
  })
  .where((.id, ))

Update and return

const [] = await 
  .update()
  .set({ : true })
  .where((.id, ))
  .returning()

Delete

await 
  .delete()
  .where((.userId, ))

Soft delete pattern

// Add deletedAt to schema
export const  = ("Post", {
  // ...
  : ("deletedAt", { : "date" })
})

// Soft delete
await 
  .()
  .({ : new () })
  .((.id, ))

// Query non-deleted only
const  = await 
  .()
  .()
  .((.deletedAt))

Joins

Inner join

const  = await 
  .select({
    : ,
    : 
  })
  .from()
  .innerJoin(, (.authorId, .id))

Left join

const  = await 
  .select()
  .from()
  .leftJoin(, (.id, .authorId))

Aggregations

import { , ,  } from "@repo/db"
import { , ,  } from "drizzle-orm"

// Count
const [{  }] = await 
  .select({ : () })
  .from()

// Group by
const  = await 
  .select({
    : .authorId,
    : ()
  })
  .from()
  .groupBy(.authorId)

Transactions

await .transaction(async () => {
  const [] = await 
    .insert()
    .values({ : , : "New Team", : "new-team" })
    .returning()

  await .insert().values({
    : .id,
    : ,
    : "owner"
  })
})

Raw SQL

For complex queries:

import {  } from "@repo/db"
import {  } from "drizzle-orm"

const  = await .execute(`
  SELECT * FROM "User" 
  WHERE "createdAt" > NOW() - INTERVAL '7 days'
`)

Type-Safe Patterns

Query wrapper functions

import { , , type User } from "@repo/db"
import {  } from "drizzle-orm"

export async function (: string): <User | null> {
  const [] = await 
    .select()
    .from()
    .where((.id, ))
  return  ?? null
}

export async function (: string): <User | null> {
  const [] = await 
    .select()
    .from()
    .where((.email, ))
  return  ?? null
}

In Server Components

app/dashboard/page.tsx
import { ,  } from "@repo/db"
import {  } from "@repo/auth/server"
import {  } from "drizzle-orm"

export default async function () {
  const  = await ()
  if (!) return null

  const [] = await 
    .select()
    .from()
    .where((.id, .user.id))

  return <>Welcome, {.name}</>
}

On this page