Skip to main content

Overview

Jefftube uses PostgreSQL 16 with Drizzle ORM for type-safe database operations. The schema is defined in server/src/db/schema.ts and consists of five main tables:
  • videos - Video metadata and statistics
  • users - User accounts (IP-based)
  • comments - User comments on videos
  • commentLikes - Likes/dislikes on comments
  • videoLikes - Likes/dislikes on videos

Entity Relationship Diagram

Table Schemas

videos

Stores video metadata and aggregated statistics.
Defined in schema.ts:4-16:
ColumnTypeConstraintsDescription
idvarchar(50)PRIMARY KEYFilename without extension (e.g., “EFTA01683563”)
titlevarchar(255)nullableVideo title
filenamevarchar(255)NOT NULL, UNIQUEOriginal filename with extension
lengthintegerNOT NULLVideo duration in seconds
hasThumbnailbooleanNOT NULL, DEFAULT falseWhether a thumbnail exists
viewsintegerNOT NULL, DEFAULT 0Total view count
likesintegerNOT NULL, DEFAULT 0Net likes (likes - dislikes)
is_shortsbooleanNOT NULL, DEFAULT falseWhether video appears in Shorts feed
playlistvarchar(100)nullablePlaylist identifier (e.g., “elevator-cam”)

users

Stores user accounts created anonymously via IP address hashing.
Defined in schema.ts:18-23:
ColumnTypeConstraintsDescription
iduuidPRIMARY KEY, DEFAULT randomUnique user identifier
ipHashvarchar(64)NOT NULL, UNIQUESHA-256 hash of IP address
usernamevarchar(50)NOT NULLDisplay name (user-chosen)
createdAttimestampNOT NULL, DEFAULT now()Account creation time

comments

Stores comments and replies in a self-referential tree structure.
Defined in schema.ts:25-35:
ColumnTypeConstraintsDescription
iduuidPRIMARY KEY, DEFAULT randomUnique comment identifier
videoIdvarchar(50)NOT NULL, FK → videos.idVideo being commented on
userIduuidNOT NULL, FK → users.idAuthor of the comment
parentIduuidnullable, FK → comments.idParent comment (null for top-level)
contenttextNOT NULLComment text
createdAttimestampNOT NULL, DEFAULT now()When comment was posted
Cascade Behavior:
  • If a video is deleted → all its comments are deleted
  • If a user is deleted → all their comments are deleted

commentLikes

Tracks user likes/dislikes on comments.
Defined in schema.ts:37-45:
ColumnTypeConstraintsDescription
iduuidPRIMARY KEY, DEFAULT randomUnique like identifier
commentIduuidNOT NULL, FK → comments.idComment being liked
userIduuidNOT NULL, FK → users.idUser who liked
isLikebooleanNOT NULLtrue = like, false = dislike
createdAttimestampNOT NULL, DEFAULT now()When action was performed
Cascade Behavior:
  • If comment is deleted → all its likes are deleted
  • If user is deleted → all their likes are deleted

videoLikes

Tracks user likes/dislikes on videos.
Defined in schema.ts:47-55:
ColumnTypeConstraintsDescription
iduuidPRIMARY KEY, DEFAULT randomUnique like identifier
videoIdvarchar(50)NOT NULL, FK → videos.idVideo being liked
userIduuidNOT NULL, FK → users.idUser who liked
isLikebooleanNOT NULLtrue = like, false = dislike
createdAttimestampNOT NULL, DEFAULT now()When action was performed
Cascade Behavior:
  • If video is deleted → all its likes are deleted
  • If user is deleted → all their likes are deleted

Database Operations

Connection

The database connection is configured in drizzle.config.ts:
export default defineConfig({
  schema: "./src/db/schema.ts",
  out: "./drizzle",
  dialect: "postgresql",
  dbCredentials: {
    url: process.env.DATABASE_URL || 
         "postgres://jtube:jtube@localhost:5432/jtube",
  },
});

Migrations

1

Generate migration

After modifying schema.ts, generate a migration file:
bun run db:generate
This creates a SQL file in the drizzle/ directory.
2

Apply migration

Run the migration against your database:
bun run db:migrate
3

Or push directly (development)

For rapid development, push schema changes directly:
bun run db:push
db:push is for development only. Use db:generate and db:migrate in production.

Seeding

The seed script (src/db/seed.ts) populates the database with video data.
bun run db:seed

Common Queries

import { db } from "./db";
import { videos } from "./db/schema";
import { desc } from "drizzle-orm";

const popularVideos = await db
  .select()
  .from(videos)
  .orderBy(desc(videos.views));

Database Tools

Drizzle Studio

Launch a web-based database GUI:
bun run db:studio
This opens https://local.drizzle.studio where you can:
  • Browse tables and data
  • Run queries
  • Edit records
  • View relationships

PostgreSQL Docker Container

bun run db:start
Starts PostgreSQL in a Docker container with persistent volume.

Performance Considerations

Indexes

The schema includes strategic indexes for common queries:
IndexPurposeQuery Pattern
videos_views_idxSort by popularityORDER BY views DESC
comments_video_parent_createdFetch comment threadsWHERE video_id = ? AND parent_id IS NULL
comments_video_user_createdUser’s comments on videoWHERE video_id = ? AND user_id = ?
comment_user_uniquePrevent duplicate likesWHERE comment_id = ? AND user_id = ?
video_user_uniquePrevent duplicate likesWHERE video_id = ? AND user_id = ?

Query Optimization Tips

Instead of:
const videos = await db.select().from(videos);
Use:
const videos = await db
  .select({
    id: videos.id,
    title: videos.title,
    views: videos.views,
  })
  .from(videos);
const pageSize = 20;
const page = 1;

const paginatedVideos = await db
  .select()
  .from(videos)
  .orderBy(desc(videos.views))
  .limit(pageSize)
  .offset((page - 1) * pageSize);
The seed script demonstrates efficient batch inserts:
const batchSize = 100;
for (let i = 0; i < data.length; i += batchSize) {
  const batch = data.slice(i, i + batchSize);
  await db.insert(videos).values(batch);
}

Schema Modifications

When you need to modify the schema:
1

Edit schema.ts

Make your changes to server/src/db/schema.ts.Example: Add a description field to videos:
export const videos = pgTable("videos", {
  // ... existing fields
  description: text("description"),
});
2

Generate migration

bun run db:generate
This creates a migration file in drizzle/ that adds the column.
3

Review migration SQL

Check the generated SQL file to ensure it does what you expect.
4

Apply migration

bun run db:migrate
Or for development:
bun run db:push
5

Update TypeScript types

Drizzle automatically infers types from the schema. Your IDE should now recognize the new field.

Backup & Restore

Backup

# Backup to SQL file
docker exec jtube-postgres pg_dump -U jtube jtube > backup.sql

# Backup with compression
docker exec jtube-postgres pg_dump -U jtube jtube | gzip > backup.sql.gz

Restore

# Restore from SQL file
docker exec -i jtube-postgres psql -U jtube -d jtube < backup.sql

# Restore from compressed backup
gunzip -c backup.sql.gz | docker exec -i jtube-postgres psql -U jtube -d jtube

Next Steps

Setup Guide

Set up your development environment

Architecture

Learn about the overall system architecture

Build docs developers (and LLMs) love