Skip to main content

Creating Database Tables

info

This guide covers table creation using Drizzle ORM. For database setup instructions, see Setting Up PostgreSQL.

Schema Overview​

Database tables are defined in src/server/db/schema.ts using Drizzle's schema definition syntax. The schema provides type-safe table definitions that are used for migrations and queries.

Table Structure​

Here's the anatomy of a table definition:

import { pgTable, text, timestamp, boolean } from "drizzle-orm/pg-core";
import { createId } from '@paralleldrive/cuid2';

export const posts = pgTable('post', {
id: text('id').primaryKey().$defaultFn(() => createId()),
title: text('title').notNull(),
content: text('content'),
published: boolean('published').default(false),
authorId: text('author_id')
.notNull()
.references(() => users.id, { onDelete: 'cascade' }),
createdAt: timestamp('created_at').defaultNow(),
updatedAt: timestamp('updated_at').defaultNow(),
});

Column Types​

Drizzle supports various PostgreSQL column types:

import { 
text, // TEXT
varchar, // VARCHAR
boolean, // BOOLEAN
timestamp, // TIMESTAMP
integer, // INTEGER
serial, // SERIAL
json, // JSON
jsonb, // JSONB
date, // DATE
numeric, // NUMERIC
} from "drizzle-orm/pg-core";

Column Modifiers​

Customize columns with modifiers:

// Column constraints
.notNull() // NOT NULL
.unique() // UNIQUE
.default(value) // DEFAULT value
.primaryKey() // PRIMARY KEY

// Foreign keys
.references(() => otherTable.id, {
onDelete: 'cascade', // CASCADE on delete
onUpdate: 'cascade' // CASCADE on update
})

Relationships​

Define relationships between tables:

// One-to-Many relationship
export const comments = pgTable('comment', {
id: text('id').primaryKey().$defaultFn(() => createId()),
postId: text('post_id')
.notNull()
.references(() => posts.id, { onDelete: 'cascade' }),
content: text('content').notNull(),
});

// Many-to-Many relationship
export const postTags = pgTable('post_tag', {
postId: text('post_id')
.notNull()
.references(() => posts.id, { onDelete: 'cascade' }),
tagId: text('tag_id')
.notNull()
.references(() => tags.id, { onDelete: 'cascade' }),
// Composite primary key
primaryKey: { columns: [postId, tagId] },
});

Best Practices​

  1. Naming Conventions

    • Use singular form for table names
    • Use snake_case for column names
    • Use camelCase for table exports
  2. Default Values

    • Add createdAt and updatedAt timestamps
    • Use createId() for ID generation
    • Set sensible defaults for boolean flags
  3. Data Integrity

    • Add appropriate NOT NULL constraints
    • Define foreign key relationships
    • Use cascading deletes when appropriate

After Creating Tables​

After defining new tables:

# Generate migration
pnpm drizzle-kit generate:pg

# Apply migration
pnpm db:push
Schema Organization

Keep related tables in the same section of your schema file, or split into multiple files for larger projects:

// src/server/db/schema/post.ts
export const posts = pgTable('post', {...});
export const comments = pgTable('comment', {...});
export const postTags = pgTable('post_tag', {...});
warning

Remember to:

  1. Generate migrations after schema changes
  2. Test migrations both up and down
  3. Commit migration files to version control