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
-
Naming Conventions
- Use singular form for table names
- Use snake_case for column names
- Use camelCase for table exports
-
Default Values
- Add
createdAt
andupdatedAt
timestamps - Use
createId()
for ID generation - Set sensible defaults for boolean flags
- Add
-
Data Integrity
- Add appropriate
NOT NULL
constraints - Define foreign key relationships
- Use cascading deletes when appropriate
- Add 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:
- Generate migrations after schema changes
- Test migrations both up and down
- Commit migration files to version control