StartupKitstartupkit
Database

Database Overview

Type-safe database access with Drizzle ORM

The @repo/db package provides type-safe database access using Drizzle ORM with PostgreSQL.

Architecture

StartupKit uses a local package architecture. Your project imports from @repo/db (a local workspace package), not directly from external packages:

your-project/
├── packages/
│   └── db/              # @repo/db - local package
│       ├── src/
│       │   ├── index.ts # Database client
│       │   └── schema.ts # Your schema
│       └── drizzle/     # Migrations
└── apps/
    └── web/             # Imports from @repo/db

This architecture means:

  • Full control - Modify the database layer to fit your needs
  • Type safety - Schema types are available throughout your app
  • Single source of truth - One schema definition for all apps

Features

  • Drizzle ORM - Type-safe queries with full TypeScript support
  • PostgreSQL - Production-ready relational database
  • Connection pooling - Optimized for serverless environments
  • Migrations - Version-controlled schema changes
  • Drizzle Studio - Visual database browser

Quick Start

Import the client

app/api/users/route.ts
import { db, users } from "@repo/db"
import { eq } from "drizzle-orm"

export async function GET() {
  const allUsers = await db.select().from(users)
  return Response.json(allUsers)
}

Query with relations

import { db, users, teams, teamMembers } from "@repo/db"
import { eq } from "drizzle-orm"

const userWithTeams = await db.query.users.findFirst({
  where: eq(users.id, userId),
  with: {
    memberships: {
      with: {
        team: true
      }
    }
  }
})

Environment Setup

Add your database connection string to .env.local:

.env.local
DATABASE_URL="postgresql://user:password@host:5432/database"

For local development, you can use:

  • Neon - Serverless Postgres (recommended)
  • Supabase - Postgres with extras
  • Local PostgreSQL via Docker

Available Scripts

Run these from the packages/db directory:

CommandDescription
pnpm db:generateGenerate migrations from schema changes
pnpm db:migrateApply pending migrations
pnpm db:pushPush schema directly (dev only)
pnpm db:studioOpen Drizzle Studio

Included Schema

The starter includes tables for authentication and teams:

TablePurpose
usersUser accounts
accountsOAuth provider accounts
sessionsUser sessions
teamsTeam/organization records
teamMembersTeam membership with roles
verificationsEmail verification tokens

Type Exports

Each table exports inferred types for selects and inserts:

import type { User, NewUser, Team, NewTeam } from "@repo/db"
TypeDescription
UserSelect type for users table
NewUserInsert type for users table
TeamSelect type for teams table
NewTeamInsert type for teams table
TeamMemberSelect type for team members
NewTeamMemberInsert type for team members
AccountSelect type for OAuth accounts
SessionSelect type for sessions
VerificationSelect type for verifications

Using types in your code

import { db, users, type User, type NewUser } from "@repo/db"

async function createUser(data: NewUser): Promise<User> {
  const [user] = await db.insert(users).values(data).returning()
  return user
}

async function getUserById(id: string): Promise<User | undefined> {
  return db.query.users.findFirst({
    where: (users, { eq }) => eq(users.id, id)
  })
}

Extending the schema

When you add new tables, export types using Drizzle's inference helpers:

packages/db/src/schema.ts
import { pgTable, text, timestamp } from "drizzle-orm/pg-core"

export const posts = pgTable("Post", {
  id: text("id").primaryKey(),
  title: text("title").notNull(),
  content: text("content"),
  authorId: text("authorId").notNull(),
  createdAt: timestamp("createdAt").defaultNow().notNull()
})

export type Post = typeof posts.$inferSelect
export type NewPost = typeof posts.$inferInsert

Next Steps

On this page