Files
Portfolio/blog-posts/44-drizzle-orm-lightweight.md
T
damjan_savicandClaude Opus 4.5 43484c5023 Add blog posts, cleanup unused files, update components
- Add 100 blog posts covering AI, development, and tech topics
- Add .env.example for environment configuration
- Add accessibility and lighthouse audit scripts
- Remove obsolete SEO reports and temporary files
- Remove dev-dist build artifacts and backup files
- Remove unused portrait images (moved/consolidated elsewhere)
- Update contact form and component improvements

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-25 11:42:11 +01:00

495 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Drizzle ORM: Die leichtgewichtige Prisma-Alternative
**Meta-Description:** Drizzle ORM für TypeScript. SQL-nah, serverless-optimiert und nur 7KB. Vergleich mit Prisma und Migration Guide.
**Keywords:** Drizzle ORM, TypeScript ORM, SQL Builder, Serverless Database, Prisma Alternative, Edge Computing, Lightweight ORM
---
## Einführung
Drizzle ORM ist ein **TypeScript-first ORM** mit SQL-naher Syntax. Mit nur ~7KB (gzipped) und null Dependencies ist es perfekt für Serverless und Edge Computing wo Prismas Bundle-Size zum Problem wird.
---
## Drizzle vs Prisma
```
┌─────────────────────────────────────────────────────────────┐
│ DRIZZLE vs PRISMA │
├─────────────────────────────────────────────────────────────┤
│ │
│ DRIZZLE PRISMA │
│ ──────────────────── ──────────────────── │
│ ~7KB gzipped ~600KB+ (mit Engine) │
│ SQL-nah (Query Builder) Abstrahierte API │
│ Code-First Schema Schema-First (.prisma) │
│ Zero Dependencies Rust Binary Engine │
│ Instant Cold Starts Cold Start Issues │
│ │
│ Best for: Best for: │
│ • Serverless/Edge • Rapid Development │
│ • SQL-Erfahrene Devs • Schema-First Teams │
│ • Performance-Critical • Große Teams/Abstraction │
│ • Kleine Bundles • Prisma Studio │
│ │
│ Supported DBs: Supported DBs: │
│ PostgreSQL, MySQL, SQLite PostgreSQL, MySQL, │
│ Turso, Neon, PlanetScale SQLite, MongoDB, │
│ Cloudflare D1, Vercel SQL Server, CockroachDB │
│ │
└─────────────────────────────────────────────────────────────┘
```
---
## Installation & Setup
```bash
# Installation
npm install drizzle-orm
npm install -D drizzle-kit
# Für PostgreSQL
npm install postgres
# Für MySQL
npm install mysql2
# Für SQLite/Turso
npm install @libsql/client
```
```typescript
// drizzle.config.ts
import { defineConfig } from 'drizzle-kit';
export default defineConfig({
schema: './src/db/schema.ts',
out: './drizzle',
dialect: 'postgresql',
dbCredentials: {
url: process.env.DATABASE_URL!
}
});
```
---
## Schema Definition (Code-First)
```typescript
// src/db/schema.ts
import {
pgTable,
uuid,
text,
timestamp,
boolean,
integer,
pgEnum
} from 'drizzle-orm/pg-core';
import { relations } from 'drizzle-orm';
// Enum
export const roleEnum = pgEnum('role', ['user', 'admin', 'moderator']);
// Users Table
export const users = pgTable('users', {
id: uuid('id').defaultRandom().primaryKey(),
email: text('email').notNull().unique(),
name: text('name'),
role: roleEnum('role').default('user').notNull(),
createdAt: timestamp('created_at').defaultNow().notNull(),
updatedAt: timestamp('updated_at').defaultNow().notNull()
});
// Posts Table
export const posts = pgTable('posts', {
id: uuid('id').defaultRandom().primaryKey(),
title: text('title').notNull(),
content: text('content'),
published: boolean('published').default(false).notNull(),
authorId: uuid('author_id')
.notNull()
.references(() => users.id, { onDelete: 'cascade' }),
createdAt: timestamp('created_at').defaultNow().notNull(),
updatedAt: timestamp('updated_at').defaultNow().notNull()
});
// Comments Table
export const comments = pgTable('comments', {
id: uuid('id').defaultRandom().primaryKey(),
content: text('content').notNull(),
postId: uuid('post_id')
.notNull()
.references(() => posts.id, { onDelete: 'cascade' }),
authorId: uuid('author_id')
.notNull()
.references(() => users.id, { onDelete: 'cascade' }),
createdAt: timestamp('created_at').defaultNow().notNull()
});
// Relations
export const usersRelations = relations(users, ({ many }) => ({
posts: many(posts),
comments: many(comments)
}));
export const postsRelations = relations(posts, ({ one, many }) => ({
author: one(users, {
fields: [posts.authorId],
references: [users.id]
}),
comments: many(comments)
}));
export const commentsRelations = relations(comments, ({ one }) => ({
post: one(posts, {
fields: [comments.postId],
references: [posts.id]
}),
author: one(users, {
fields: [comments.authorId],
references: [users.id]
})
}));
// Type Inference
export type User = typeof users.$inferSelect;
export type NewUser = typeof users.$inferInsert;
export type Post = typeof posts.$inferSelect;
export type NewPost = typeof posts.$inferInsert;
```
---
## Database Connection
```typescript
// src/db/index.ts
import { drizzle } from 'drizzle-orm/postgres-js';
import postgres from 'postgres';
import * as schema from './schema';
const connectionString = process.env.DATABASE_URL!;
// Für Queries
const queryClient = postgres(connectionString);
export const db = drizzle(queryClient, { schema });
// Für Migrations (separater Client)
const migrationClient = postgres(connectionString, { max: 1 });
export const migrationDb = drizzle(migrationClient);
```
```typescript
// Für Serverless (Neon, Vercel)
import { drizzle } from 'drizzle-orm/neon-http';
import { neon } from '@neondatabase/serverless';
import * as schema from './schema';
const sql = neon(process.env.DATABASE_URL!);
export const db = drizzle(sql, { schema });
```
```typescript
// Für Edge (Cloudflare D1)
import { drizzle } from 'drizzle-orm/d1';
import * as schema from './schema';
export interface Env {
DB: D1Database;
}
export default {
async fetch(request: Request, env: Env) {
const db = drizzle(env.DB, { schema });
// ...
}
};
```
---
## CRUD Operations
```typescript
import { db } from '@/db';
import { users, posts, comments } from '@/db/schema';
import { eq, and, or, desc, asc, like, sql } from 'drizzle-orm';
// CREATE
async function createUser(data: NewUser) {
const [user] = await db
.insert(users)
.values(data)
.returning();
return user;
}
async function createManyUsers(data: NewUser[]) {
return await db
.insert(users)
.values(data)
.returning();
}
// READ - Single
async function getUserById(id: string) {
const [user] = await db
.select()
.from(users)
.where(eq(users.id, id))
.limit(1);
return user;
}
// READ - Mit Relations (Query API)
async function getUserWithPosts(id: string) {
const result = await db.query.users.findFirst({
where: eq(users.id, id),
with: {
posts: {
where: eq(posts.published, true),
orderBy: [desc(posts.createdAt)],
limit: 10
}
}
});
return result;
}
// READ - Liste mit Filtering
async function getPublishedPosts(page: number = 1, pageSize: number = 10) {
const offset = (page - 1) * pageSize;
const results = await db
.select({
id: posts.id,
title: posts.title,
content: posts.content,
createdAt: posts.createdAt,
authorName: users.name,
authorEmail: users.email
})
.from(posts)
.innerJoin(users, eq(posts.authorId, users.id))
.where(eq(posts.published, true))
.orderBy(desc(posts.createdAt))
.limit(pageSize)
.offset(offset);
return results;
}
// UPDATE
async function updatePost(id: string, data: Partial<NewPost>) {
const [updated] = await db
.update(posts)
.set({
...data,
updatedAt: new Date()
})
.where(eq(posts.id, id))
.returning();
return updated;
}
// UPSERT
async function upsertUser(email: string, data: Partial<NewUser>) {
const [user] = await db
.insert(users)
.values({ email, ...data })
.onConflictDoUpdate({
target: users.email,
set: { ...data, updatedAt: new Date() }
})
.returning();
return user;
}
// DELETE
async function deletePost(id: string) {
const [deleted] = await db
.delete(posts)
.where(eq(posts.id, id))
.returning();
return deleted;
}
```
---
## Advanced Queries
```typescript
import { sql, count, avg, sum } from 'drizzle-orm';
// Aggregations
async function getPostStats() {
const [stats] = await db
.select({
totalPosts: count(posts.id),
publishedPosts: count(sql`CASE WHEN ${posts.published} THEN 1 END`),
})
.from(posts);
return stats;
}
// Group By
async function getPostsByAuthor() {
return await db
.select({
authorId: posts.authorId,
authorName: users.name,
postCount: count(posts.id)
})
.from(posts)
.innerJoin(users, eq(posts.authorId, users.id))
.groupBy(posts.authorId, users.name)
.orderBy(desc(count(posts.id)));
}
// Subqueries
async function getUsersWithPostCount() {
const postCountSubquery = db
.select({
authorId: posts.authorId,
count: count(posts.id).as('post_count')
})
.from(posts)
.groupBy(posts.authorId)
.as('post_counts');
return await db
.select({
id: users.id,
name: users.name,
postCount: postCountSubquery.count
})
.from(users)
.leftJoin(postCountSubquery, eq(users.id, postCountSubquery.authorId));
}
// Raw SQL
async function searchPosts(query: string) {
return await db.execute(sql`
SELECT * FROM posts
WHERE to_tsvector('german', title || ' ' || content)
@@ plainto_tsquery('german', ${query})
`);
}
// Transactions
async function createPostWithComments(
post: NewPost,
comments: { content: string; authorId: string }[]
) {
return await db.transaction(async (tx) => {
const [newPost] = await tx
.insert(posts)
.values(post)
.returning();
if (comments.length > 0) {
await tx.insert(comments).values(
comments.map(c => ({
...c,
postId: newPost.id
}))
);
}
return newPost;
});
}
```
---
## Migrations
```bash
# Schema-Änderungen generieren
npx drizzle-kit generate
# Migrations ausführen
npx drizzle-kit migrate
# Push (Development - ohne Migration Files)
npx drizzle-kit push
# Studio (GUI)
npx drizzle-kit studio
```
```typescript
// Programmatische Migration
import { migrate } from 'drizzle-orm/postgres-js/migrator';
import { migrationDb } from './db';
async function runMigrations() {
await migrate(migrationDb, { migrationsFolder: './drizzle' });
console.log('Migrations complete');
}
```
---
## Zod Integration
```typescript
import { createInsertSchema, createSelectSchema } from 'drizzle-zod';
import { z } from 'zod';
import { users, posts } from './schema';
// Auto-generierte Schemas
const insertUserSchema = createInsertSchema(users);
const selectUserSchema = createSelectSchema(users);
// Mit Erweiterungen
const createUserSchema = createInsertSchema(users, {
email: z.string().email('Ungültige E-Mail'),
name: z.string().min(2, 'Name zu kurz').optional()
}).omit({ id: true, createdAt: true, updatedAt: true });
const updateUserSchema = createUserSchema.partial();
// Verwendung
async function createUserValidated(input: unknown) {
const data = createUserSchema.parse(input);
return await createUser(data);
}
```
---
## Fazit
Drizzle ORM bietet:
1. **Minimal Footprint**: ~7KB, zero dependencies
2. **SQL-Kontrolle**: Transparente, vorhersagbare Queries
3. **Type-Safety**: Volle TypeScript-Integration
4. **Edge-Ready**: Perfekt für Serverless/Edge
Für Performance-kritische und serverless Anwendungen ist Drizzle die bessere Wahl.
---
## Bildprompts
1. "Lightweight feather next to database icons, minimal bundle size concept"
2. "SQL code transforming into TypeScript types, type-safe ORM visualization"
3. "Edge computing nodes with database connections, serverless architecture"
---
## Quellen
- [Drizzle ORM Documentation](https://orm.drizzle.team/)
- [Drizzle vs Prisma Comparison](https://www.prisma.io/docs/orm/more/comparisons/prisma-and-drizzle)
- [Drizzle vs Prisma 2026](https://medium.com/@codabu/drizzle-vs-prisma-choosing-the-right-typescript-orm-in-2026-deep-dive-63abb6aa882b)
- [Drizzle Kit](https://orm.drizzle.team/kit-docs/overview)