| name | Add Database Table |
| description | Add a new database table with Drizzle ORM |
Add Database Table Skill
You are helping add a new database table to a Next.js application using Drizzle ORM with PostgreSQL.
Context
- ORM: Drizzle ORM
- Database: PostgreSQL
- Schema Location:
apps/web/lib/server/db/schema/
- Migration Tool: Drizzle Kit
Workflow
1. Understand Requirements
Ask the user:
- What is the table name? (use singular form, e.g., "user", "post")
- What fields are needed?
- What data types for each field?
- Are there relationships to other tables?
- What constraints? (unique, not null, default values)
2. Create Schema File
Create a new file in lib/server/db/schema/:
import {
pgTable,
text,
timestamp,
uuid,
boolean,
integer,
varchar,
} from "drizzle-orm/pg-core"
import { createInsertSchema, createSelectSchema } from "drizzle-zod"
import { relations } from "drizzle-orm"
export const [tableName]s = pgTable("[table_name]", {
id: uuid("id").primaryKey().defaultRandom(),
createdAt: timestamp("createdAt").notNull().defaultNow(),
updatedAt: timestamp("updatedAt").notNull().defaultNow(),
})
export const [tableName]sRelations = relations([tableName]s, ({ one, many }) => ({
}))
export const insert[TableName]Schema = createInsertSchema([tableName]s)
export const select[TableName]Schema = createSelectSchema([tableName]s)
export type [TableName] = typeof [tableName]s.$inferSelect
export type New[TableName] = typeof [tableName]s.$inferInsert
3. Common Field Types
text("field_name")
varchar("field_name", { length: 255 })
integer("field_name")
serial("field_name")
real("field_name")
boolean("field_name")
timestamp("field_name")
date("field_name")
uuid("field_name")
json("field_name")
jsonb("field_name")
4. Common Constraints
.notNull()
.unique()
.default(value)
.defaultNow()
.defaultRandom()
.primaryKey()
.references(() => users.id, { onDelete: "cascade" })
5. Foreign Key Example
import { users } from "./users"
export const posts = pgTable("post", {
id: uuid("id").primaryKey().defaultRandom(),
title: text("title").notNull(),
content: text("content").notNull(),
userId: uuid("userId")
.notNull()
.references(() => users.id, { onDelete: "cascade" }),
createdAt: timestamp("createdAt").notNull().defaultNow(),
updatedAt: timestamp("updatedAt").notNull().defaultNow(),
})
export const postsRelations = relations(posts, ({ one }) => ({
user: one(users, {
fields: [posts.userId],
references: [users.id],
}),
}))
6. Export from Index
Add export to lib/server/db/schema/index.ts:
export * from "./users"
export * from "./posts"
export * from "./[new-table]"
7. Push Schema to Database
Run the migration:
npm run db:push
npm run db:generate
npm run db:migrate
8. Example Queries
Show the user how to query the new table:
import { db } from "@/lib/server/db"
import { [tableName]s } from "@/lib/server/db/schema"
import { eq, and, or, gt } from "drizzle-orm"
const all = await db.query.[tableName]s.findMany()
const one = await db.query.[tableName]s.findFirst({
where: eq([tableName]s.id, id),
})
const withRelations = await db.query.[tableName]s.findFirst({
where: eq([tableName]s.id, id),
with: {
relatedTable: true,
},
})
const [created] = await db
.insert([tableName]s)
.values({
})
.returning()
const [updated] = await db
.update([tableName]s)
.set({
updatedAt: new Date(),
})
.where(eq([tableName]s.id, id))
.returning()
await db
.delete([tableName]s)
.where(eq([tableName]s.id, id))
Validation with Zod
The generated Zod schemas can be used for validation:
import { insert[TableName]Schema } from "@/lib/server/db/schema"
export async function create[TableName](data: unknown) {
try {
const validated = insert[TableName]Schema.parse(data)
const [created] = await db
.insert([tableName]s)
.values(validated)
.returning()
return { success: true, data: created }
} catch (error) {
return { success: false, error: "Validation failed" }
}
}
Custom Zod Validation
You can extend the generated schemas:
import { z } from "zod"
export const customInsertSchema = insert[TableName]Schema.extend({
email: z.string().email(),
age: z.number().min(0).max(150),
password: z.string().min(8),
})
Complete Example: Blog Posts Table
import { pgTable, text, timestamp, uuid, boolean } from "drizzle-orm/pg-core"
import { createInsertSchema, createSelectSchema } from "drizzle-zod"
import { relations } from "drizzle-orm"
import { users } from "./users"
export const posts = pgTable("post", {
id: uuid("id").primaryKey().defaultRandom(),
title: text("title").notNull(),
content: text("content").notNull(),
slug: text("slug").notNull().unique(),
published: boolean("published").default(false),
authorId: uuid("authorId")
.notNull()
.references(() => users.id, { onDelete: "cascade" }),
createdAt: timestamp("createdAt").notNull().defaultNow(),
updatedAt: timestamp("updatedAt").notNull().defaultNow(),
})
export const postsRelations = relations(posts, ({ one }) => ({
author: one(users, {
fields: [posts.authorId],
references: [users.id],
}),
}))
export const insertPostSchema = createInsertSchema(posts)
export const selectPostSchema = createSelectSchema(posts)
export type Post = typeof posts.$inferSelect
export type NewPost = typeof posts.$inferInsert
Checklist
Before completing, ensure:
Now help the user create their database table!