Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/revokslab/shipfree/llms.txt

Use this file to discover all available pages before exploring further.

This guide provides practical examples of database queries using Drizzle ORM for common use cases in ShipFree.

Setup

All queries use the database client from src/database/index.ts:
import { db } from '@/database'
import { user, session, customer, subscription, payment } from '@/database/schema'
import { eq, and, or, desc, gte, lte } from 'drizzle-orm'

User Queries

Find User by Email

const findUserByEmail = async (email: string) => {
  const result = await db.query.user.findFirst({
    where: eq(user.email, email)
  })
  
  return result
}

Find User by ID with Relations

const getUserWithRelations = async (userId: string) => {
  const result = await db.query.user.findFirst({
    where: eq(user.id, userId),
    with: {
      sessions: true,
      accounts: true
    }
  })
  
  return result
}

Create New User

const createUser = async (data: {
  id: string
  email: string
  name: string
}) => {
  const [newUser] = await db.insert(user).values({
    id: data.id,
    email: data.email,
    name: data.name,
    emailVerified: false
  }).returning()
  
  return newUser
}

Update User Profile

const updateUserProfile = async (userId: string, data: {
  name?: string
  image?: string
}) => {
  const [updated] = await db
    .update(user)
    .set({
      ...data,
      updatedAt: new Date()
    })
    .where(eq(user.id, userId))
    .returning()
  
  return updated
}

Session Queries

Find Active Session by Token

const findActiveSession = async (token: string) => {
  const now = new Date()
  
  const result = await db.query.session.findFirst({
    where: and(
      eq(session.token, token),
      gte(session.expiresAt, now)
    ),
    with: {
      user: true
    }
  })
  
  return result
}

Get All User Sessions

const getUserSessions = async (userId: string) => {
  const sessions = await db.query.session.findMany({
    where: eq(session.userId, userId),
    orderBy: desc(session.createdAt)
  })
  
  return sessions
}

Delete Expired Sessions

const deleteExpiredSessions = async () => {
  const now = new Date()
  
  const deleted = await db
    .delete(session)
    .where(lte(session.expiresAt, now))
    .returning()
  
  return deleted.length
}

Subscription Queries

Get User’s Active Subscription

const getActiveSubscription = async (userId: string) => {
  const sub = await db.query.subscription.findFirst({
    where: and(
      eq(subscription.userId, userId),
      eq(subscription.status, 'active')
    ),
    orderBy: desc(subscription.createdAt)
  })
  
  return sub
}

Get Subscription with Customer Details

const getSubscriptionDetails = async (subscriptionId: string) => {
  const sub = await db.query.subscription.findFirst({
    where: eq(subscription.id, subscriptionId),
    with: {
      user: true,
      customer: true,
      payments: {
        orderBy: desc(payment.createdAt),
        limit: 10
      }
    }
  })
  
  return sub
}

Find Subscriptions by Provider

const getSubscriptionsByProvider = async (
  provider: 'stripe' | 'polar' | 'lemonsqueezy'
) => {
  const subscriptions = await db.query.subscription.findMany({
    where: and(
      eq(subscription.provider, provider),
      eq(subscription.status, 'active')
    ),
    with: {
      user: {
        columns: {
          email: true,
          name: true
        }
      }
    }
  })
  
  return subscriptions
}

Update Subscription Status

const updateSubscriptionStatus = async (
  providerSubscriptionId: string,
  newStatus: 'active' | 'canceled' | 'past_due'
) => {
  const [updated] = await db
    .update(subscription)
    .set({
      status: newStatus,
      ...(newStatus === 'canceled' && { canceledAt: new Date() }),
      updatedAt: new Date()
    })
    .where(eq(subscription.providerSubscriptionId, providerSubscriptionId))
    .returning()
  
  return updated
}

Find Subscriptions Ending Soon

const getSubscriptionsEndingSoon = async (daysFromNow: number = 7) => {
  const futureDate = new Date()
  futureDate.setDate(futureDate.getDate() + daysFromNow)
  
  const subscriptions = await db.query.subscription.findMany({
    where: and(
      eq(subscription.status, 'active'),
      lte(subscription.currentPeriodEnd, futureDate),
      gte(subscription.currentPeriodEnd, new Date())
    ),
    with: {
      user: {
        columns: {
          email: true,
          name: true
        }
      }
    }
  })
  
  return subscriptions
}

Payment Queries

Get User’s Payment History

const getUserPayments = async (userId: string, limit: number = 50) => {
  const payments = await db.query.payment.findMany({
    where: eq(payment.userId, userId),
    orderBy: desc(payment.createdAt),
    limit,
    with: {
      subscription: {
        columns: {
          plan: true,
          interval: true
        }
      }
    }
  })
  
  return payments
}

Get Successful Payments

const getSuccessfulPayments = async (userId: string) => {
  const payments = await db.query.payment.findMany({
    where: and(
      eq(payment.userId, userId),
      eq(payment.status, 'succeeded')
    ),
    orderBy: desc(payment.createdAt)
  })
  
  return payments
}

Calculate Total Revenue by Plan

const getRevenueByPlan = async () => {
  const payments = await db
    .select({
      plan: subscription.plan,
      totalRevenue: sql<number>`sum(cast(${payment.amount} as numeric))`,
      count: sql<number>`count(*)::int`
    })
    .from(payment)
    .innerJoin(subscription, eq(payment.subscriptionId, subscription.id))
    .where(eq(payment.status, 'succeeded'))
    .groupBy(subscription.plan)
  
  return payments
}

Create Payment Record

const createPayment = async (data: {
  userId: string
  customerId: string
  subscriptionId?: string
  provider: 'stripe' | 'polar' | 'lemonsqueezy'
  providerPaymentId: string
  type: 'subscription' | 'one_time' | 'refund'
  status: 'succeeded' | 'pending' | 'failed'
  amount: number
  currency: string
  description?: string
}) => {
  const [newPayment] = await db.insert(payment).values({
    id: crypto.randomUUID(),
    ...data,
    amount: data.amount.toString()
  }).returning()
  
  return newPayment
}

Customer Queries

Find or Create Customer

const findOrCreateCustomer = async (data: {
  userId: string
  provider: 'stripe' | 'polar' | 'lemonsqueezy'
  providerCustomerId: string
  email?: string
}) => {
  // Try to find existing
  const existing = await db.query.customer.findFirst({
    where: and(
      eq(customer.userId, data.userId),
      eq(customer.provider, data.provider)
    )
  })
  
  if (existing) {
    return { customer: existing, created: false }
  }
  
  // Create new
  const [newCustomer] = await db.insert(customer).values({
    id: crypto.randomUUID(),
    ...data
  }).returning()
  
  return { customer: newCustomer, created: true }
}

Get Customer with All Relations

const getCustomerComplete = async (customerId: string) => {
  const result = await db.query.customer.findFirst({
    where: eq(customer.id, customerId),
    with: {
      user: true,
      subscriptions: {
        orderBy: desc(subscription.createdAt)
      },
      payments: {
        orderBy: desc(payment.createdAt),
        limit: 20
      }
    }
  })
  
  return result
}

Complex Queries

Get User’s Complete Billing Data

const getUserBillingData = async (userId: string) => {
  const userData = await db.query.user.findFirst({
    where: eq(user.id, userId),
    columns: {
      id: true,
      email: true,
      name: true
    }
  })
  
  const customerData = await db.query.customer.findFirst({
    where: eq(customer.userId, userId)
  })
  
  const activeSubscription = await db.query.subscription.findFirst({
    where: and(
      eq(subscription.userId, userId),
      eq(subscription.status, 'active')
    ),
    orderBy: desc(subscription.createdAt)
  })
  
  const recentPayments = await db.query.payment.findMany({
    where: eq(payment.userId, userId),
    orderBy: desc(payment.createdAt),
    limit: 10
  })
  
  return {
    user: userData,
    customer: customerData,
    subscription: activeSubscription,
    payments: recentPayments
  }
}

Get Dashboard Analytics

import { sql } from 'drizzle-orm'

const getDashboardStats = async () => {
  const totalUsers = await db.select({ count: sql<number>`count(*)::int` }).from(user)
  
  const activeSubscriptions = await db
    .select({ count: sql<number>`count(*)::int` })
    .from(subscription)
    .where(eq(subscription.status, 'active'))
  
  const monthlyRevenue = await db
    .select({ total: sql<number>`sum(cast(${payment.amount} as numeric))` })
    .from(payment)
    .where(
      and(
        eq(payment.status, 'succeeded'),
        gte(payment.createdAt, sql`now() - interval '30 days'`)
      )
    )
  
  const planDistribution = await db
    .select({
      plan: subscription.plan,
      count: sql<number>`count(*)::int`
    })
    .from(subscription)
    .where(eq(subscription.status, 'active'))
    .groupBy(subscription.plan)
  
  return {
    totalUsers: totalUsers[0].count,
    activeSubscriptions: activeSubscriptions[0].count,
    monthlyRevenue: monthlyRevenue[0].total || 0,
    planDistribution
  }
}

Find Users by Subscription Plan

const getUsersByPlan = async (planName: 'free' | 'starter' | 'pro' | 'enterprise') => {
  const users = await db
    .select({
      userId: user.id,
      email: user.email,
      name: user.name,
      plan: subscription.plan,
      status: subscription.status,
      subscriptionId: subscription.id
    })
    .from(user)
    .innerJoin(subscription, eq(user.id, subscription.userId))
    .where(
      and(
        eq(subscription.plan, planName),
        eq(subscription.status, 'active')
      )
    )
  
  return users
}

Transaction Examples

Create User with Customer

const createUserWithCustomer = async (data: {
  email: string
  name: string
  provider: 'stripe' | 'polar' | 'lemonsqueezy'
  providerCustomerId: string
}) => {
  return await db.transaction(async (tx) => {
    // Create user
    const [newUser] = await tx.insert(user).values({
      id: crypto.randomUUID(),
      email: data.email,
      name: data.name,
      emailVerified: false
    }).returning()
    
    // Create customer
    const [newCustomer] = await tx.insert(customer).values({
      id: crypto.randomUUID(),
      userId: newUser.id,
      provider: data.provider,
      providerCustomerId: data.providerCustomerId,
      email: data.email
    }).returning()
    
    return { user: newUser, customer: newCustomer }
  })
}

Cancel Subscription with Refund

const cancelSubscriptionWithRefund = async (
  subscriptionId: string,
  refundAmount: number
) => {
  return await db.transaction(async (tx) => {
    // Update subscription
    const [canceledSub] = await tx
      .update(subscription)
      .set({
        status: 'canceled',
        canceledAt: new Date(),
        updatedAt: new Date()
      })
      .where(eq(subscription.id, subscriptionId))
      .returning()
    
    // Create refund payment record
    const [refund] = await tx.insert(payment).values({
      id: crypto.randomUUID(),
      userId: canceledSub.userId,
      subscriptionId: subscriptionId,
      provider: canceledSub.provider,
      providerPaymentId: `refund_${crypto.randomUUID()}`,
      type: 'refund',
      status: 'succeeded',
      amount: refundAmount.toString(),
      currency: canceledSub.currency || 'usd',
      description: 'Subscription cancellation refund'
    }).returning()
    
    return { subscription: canceledSub, refund }
  })
}

Query Builder Patterns

Dynamic Filters

const searchSubscriptions = async (filters: {
  userId?: string
  status?: string
  plan?: string
  provider?: string
}) => {
  const conditions = []
  
  if (filters.userId) {
    conditions.push(eq(subscription.userId, filters.userId))
  }
  if (filters.status) {
    conditions.push(eq(subscription.status, filters.status))
  }
  if (filters.plan) {
    conditions.push(eq(subscription.plan, filters.plan))
  }
  if (filters.provider) {
    conditions.push(eq(subscription.provider, filters.provider))
  }
  
  const results = await db.query.subscription.findMany({
    where: conditions.length > 0 ? and(...conditions) : undefined,
    orderBy: desc(subscription.createdAt)
  })
  
  return results
}

Pagination

const getPaginatedPayments = async ({
  userId,
  page = 1,
  pageSize = 20
}: {
  userId: string
  page?: number
  pageSize?: number
}) => {
  const offset = (page - 1) * pageSize
  
  const [payments, totalCount] = await Promise.all([
    db.query.payment.findMany({
      where: eq(payment.userId, userId),
      orderBy: desc(payment.createdAt),
      limit: pageSize,
      offset
    }),
    db.select({ count: sql<number>`count(*)::int` })
      .from(payment)
      .where(eq(payment.userId, userId))
  ])
  
  return {
    data: payments,
    pagination: {
      page,
      pageSize,
      total: totalCount[0].count,
      totalPages: Math.ceil(totalCount[0].count / pageSize)
    }
  }
}

Best Practices

When creating or updating multiple related records, wrap operations in a transaction to ensure data consistency.
await db.transaction(async (tx) => {
  // Multiple operations here
})
The schema includes indexes on commonly queried fields like userId, providerCustomerId, and status. Always filter by indexed columns when possible.
Use Drizzle’s with clause to fetch related data in a single query instead of making multiple queries.
const user = await db.query.user.findFirst({
  where: eq(user.id, userId),
  with: {
    sessions: true,
    accounts: true
  }
})
Many fields are nullable. Always check for null before using values.
const email = customer.email || user.email

Next Steps

Database Schema

View complete schema reference

Drizzle ORM Docs

Official Drizzle documentation

Build docs developers (and LLMs) love