From python
Defines type-safe database schemas, queries, relations, and migrations using Drizzle ORM in TypeScript for PostgreSQL, MySQL, SQLite, Cloudflare D1, and Durable Objects.
npx claudepluginhub martinffx/atelier --plugin pythonThis skill uses the workspace's default tool permissions.
Lightweight, type-safe ORM with SQL-like and relational query APIs for PostgreSQL, MySQL, SQLite, Cloudflare D1, and Durable Objects.
Builds type-safe database layers with Drizzle ORM in TypeScript: schema design, relational queries, migrations, and serverless integrations like Neon and Supabase.
Provides Drizzle ORM patterns for schema definition, CRUD operations, relations, queries, transactions, and migrations. Supports PostgreSQL, MySQL, SQLite, MSSQL, CockroachDB.
Guides Drizzle ORM type-safe schema design, relational queries, prepared statements, migrations, and transactions. Use for database schema, queries, migrations, or performance optimization in TypeScript.
Share bugs, ideas, or general feedback.
Lightweight, type-safe ORM with SQL-like and relational query APIs for PostgreSQL, MySQL, SQLite, Cloudflare D1, and Durable Objects.
import {
pgTable,
serial,
text,
integer,
timestamp,
boolean,
varchar,
uuid,
primaryKey,
unique,
index
} from 'drizzle-orm/pg-core'
export const users = pgTable('users', {
id: serial('id').primaryKey(),
name: text('name').notNull(),
email: varchar('email', { length: 255 }).notNull().unique(),
age: integer('age'),
isActive: boolean('is_active').default(true),
createdAt: timestamp('created_at').defaultNow().notNull(),
updatedAt: timestamp('updated_at').$onUpdate(() => new Date()),
})
export const posts = pgTable('posts', {
id: serial('id').primaryKey(),
title: text('title').notNull(),
content: text('content'),
authorId: integer('author_id')
.notNull()
.references(() => users.id, { onDelete: 'cascade' }),
createdAt: timestamp('created_at').defaultNow().notNull(),
})
See references/postgresql.md for detailed PostgreSQL patterns.
import { sqliteTable, text, integer } from 'drizzle-orm/sqlite-core'
export const users = sqliteTable('users', {
id: text('id').primaryKey(),
name: text('name').notNull(),
email: text('email').notNull(),
isActive: integer('is_active', { mode: 'boolean' }).default(true),
createdAt: text('created_at').notNull(),
})
export const posts = sqliteTable('posts', {
id: text('id').primaryKey(),
title: text('title').notNull(),
content: text('content'),
authorId: text('author_id')
.notNull()
.references(() => users.id, { onDelete: 'cascade' }),
createdAt: text('created_at').notNull(),
})
See references/sqlite.md for SQLite patterns and references/cloudflare.md for D1 and Durable Objects.
// Infer types from schema - no manual interfaces needed
export type User = typeof users.$inferSelect
export type NewUser = typeof users.$inferInsert
export type Post = typeof posts.$inferSelect
export type NewPost = typeof posts.$inferInsert
import { relations } from 'drizzle-orm'
export const usersRelations = relations(users, ({ many }) => ({
posts: many(posts),
}))
export const postsRelations = relations(posts, ({ one }) => ({
author: one(users, {
fields: [posts.authorId],
references: [users.id],
}),
}))
import { eq, and, or, gt, like, isNull, desc, asc } from 'drizzle-orm'
// Select all
const allUsers = await db.select().from(users)
// Select specific columns
const names = await db.select({ name: users.name }).from(users)
// Where clause
const activeUsers = await db
.select()
.from(users)
.where(eq(users.isActive, true))
// Multiple conditions
const filtered = await db
.select()
.from(users)
.where(and(
eq(users.isActive, true),
gt(users.age, 18)
))
// Like/pattern matching
const matching = await db
.select()
.from(users)
.where(like(users.email, '%@example.com'))
// Order and limit
const recent = await db
.select()
.from(posts)
.orderBy(desc(posts.createdAt))
.limit(10)
// Joins
const postsWithAuthors = await db
.select({
postTitle: posts.title,
authorName: users.name,
})
.from(posts)
.leftJoin(users, eq(posts.authorId, users.id))
// Requires schema with relations passed to drizzle()
const db = drizzle(pool, { schema })
// Find many with relations
const usersWithPosts = await db.query.users.findMany({
with: {
posts: true,
},
})
// Partial columns + nested relations
const partial = await db.query.users.findMany({
columns: {
id: true,
name: true,
},
with: {
posts: {
columns: {
title: true,
createdAt: true,
},
},
},
})
// Find first
const user = await db.query.users.findFirst({
where: eq(users.id, 1),
with: { posts: true },
})
// Exclude columns
const withoutEmail = await db.query.users.findMany({
columns: {
email: false, // exclude
},
})
// Single insert
const [newUser] = await db
.insert(users)
.values({ name: 'Alice', email: 'alice@example.com' })
.returning()
// Multiple insert
await db.insert(users).values([
{ name: 'Bob', email: 'bob@example.com' },
{ name: 'Carol', email: 'carol@example.com' },
])
// Upsert (on conflict)
await db
.insert(users)
.values({ name: 'Alice', email: 'alice@example.com' })
.onConflictDoUpdate({
target: users.email,
set: { name: 'Alice Updated' },
})
await db
.update(users)
.set({ isActive: false })
.where(eq(users.id, 1))
// Update with returning
const [updated] = await db
.update(users)
.set({ name: 'New Name' })
.where(eq(users.id, 1))
.returning()
await db.delete(users).where(eq(users.id, 1))
// Delete with returning
const [deleted] = await db
.delete(users)
.where(eq(users.id, 1))
.returning()
await db.transaction(async (tx) => {
const [user] = await tx
.insert(users)
.values({ name: 'Alice', email: 'alice@example.com' })
.returning()
await tx.insert(posts).values({
title: 'First Post',
authorId: user.id,
})
})
Domain entities encapsulate data transformations between API, domain, and database layers.
import type { InferInsertModel, InferSelectModel } from 'drizzle-orm'
import type { users } from './schema'
type UserRecord = InferSelectModel<typeof users>
type UserInsert = InferInsertModel<typeof users>
class UserEntity {
public readonly id: string
public readonly name: string
public readonly email: string
public readonly createdAt: Date
private constructor(data: UserEntityData) {
Object.assign(this, data)
}
// API request → Entity
static fromRequest(rq: CreateUserRequest, id?: string): UserEntity {
return new UserEntity({
id: id ?? crypto.randomUUID(),
name: rq.name,
email: rq.email,
createdAt: new Date(),
})
}
// DB record → Entity
static fromRecord(record: UserRecord): UserEntity {
return new UserEntity({
id: record.id,
name: record.name,
email: record.email,
createdAt: record.createdAt,
})
}
// Entity → DB insert
toRecord(): UserInsert {
return {
id: this.id,
name: this.name,
email: this.email,
createdAt: this.createdAt,
}
}
// Entity → API response
toResponse(): UserResponse {
return {
id: this.id,
name: this.name,
email: this.email,
createdAt: this.createdAt.toISOString(),
}
}
}
See references/entity-pattern.md for detailed examples.
Repositories wrap database access with error handling and business logic.
import { eq, and } from 'drizzle-orm'
import { users } from './schema'
import { UserEntity } from './entities/UserEntity'
class UserRepo {
constructor(private db: DrizzleDB) {}
async getById(id: string): Promise<UserEntity> {
const record = await this.db.query.users.findFirst({
where: eq(users.id, id),
})
if (!record) throw new NotFoundError('User not found')
return UserEntity.fromRecord(record)
}
async create(entity: UserEntity): Promise<UserEntity> {
try {
const [record] = await this.db
.insert(users)
.values(entity.toRecord())
.returning()
return UserEntity.fromRecord(record)
} catch (error) {
throw handleDBError(error, { userId: entity.id })
}
}
async update(entity: UserEntity): Promise<UserEntity> {
const [record] = await this.db
.update(users)
.set(entity.toRecord())
.where(eq(users.id, entity.id))
.returning()
if (!record) throw new NotFoundError('User not found')
return UserEntity.fromRecord(record)
}
}
See references/repository-pattern.md for detailed examples.
For database-specific patterns, connection setup, migrations, and testing:
schema.ts file(s)$inferSelect and $inferInsert for types - don't duplicatedb.query{ schema } to drizzle() to enable relational queriesdb.select()) for complex joinsdb.query) for nested data fetchingreferences(() => table.column)returning() to get inserted/updated/deleted rowslockVersion column for optimistic locking on mutable resources