Menu
Nazad na Blog
1 min read
Web Development

Drizzle ORM: Die leichtgewichtige Prisma-Alternative

Drizzle ORM für TypeScript. SQL-nah, serverless-optimiert und nur 7KB. Vergleich mit Prisma und Migration Guide.

Drizzle ORMTypeScript ORMSQL BuilderServerless DatabasePrisma AlternativeEdge Computing
Drizzle ORM: Die leichtgewichtige Prisma-Alternative

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

# 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
// 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)

// 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

// 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);
// 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 });
// 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

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

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

# 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
// 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

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