Prisma ha dominato il panorama degli ORM TypeScript per anni, ma nel 2026 Drizzle ORM è diventato la scelta preferita per i developer che vogliono type-safety totale, query builder SQL-like senza magia implicita e performance superiori. La differenza principale: Drizzle non genera query al volo — le costruisce in modo prevedibile e statico, con un’esperienza di debug molto più semplice. In questa guida costruiamo un’applicazione completa con Drizzle, PostgreSQL su Supabase e Next.js.
Perché Drizzle invece di Prisma
Le differenze principali che spingono i developer a migrare da Prisma a Drizzle nel 2026:
Bundle size: Drizzle è 10-50x più leggero di Prisma — fondamentale per Edge Functions e serverless. Prisma genera e include un query engine binario nella build; Drizzle è puro JavaScript/TypeScript. Query SQL trasparenti: Drizzle scrive query che puoi leggere e capire. Non c’è una “magia” che trasforma le operazioni Prisma in SQL — quello che scrivi è quasi esattamente quello che viene eseguito. Type inference migliore: lo schema Drizzle è scritto in TypeScript puro, non in un DSL separato (schema.prisma). I tipi vengono inferiti direttamente dalla definizione della tabella, senza generazione di codice. Migliori sugli Edge: Cloudflare Workers, Vercel Edge, Supabase Edge Functions — tutti hanno limitazioni sui binari che rendono Prisma difficile. Drizzle funziona ovunque giri JavaScript.
Installazione e configurazione
npm install drizzle-orm postgres
npm install -D drizzle-kit @types/pgConfigura la connessione (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!;
// Per produzione: pool di connessioni
const client = postgres(connectionString, { max: 10 });
export const db = drizzle(client, { schema });Definire lo schema con pgTable
Lo schema in Drizzle è TypeScript puro — nessun file .prisma, nessuna generazione di codice:
// db/schema.ts
import {
pgTable, serial, text, integer, boolean,
timestamp, uuid, varchar, decimal, index, foreignKey,
} from "drizzle-orm/pg-core";
export const users = pgTable("users", {
id: uuid("id").primaryKey().defaultRandom(),
email: varchar("email", { length: 255 }).notNull().unique(),
name: text("name").notNull(),
plan: text("plan", { enum: ["free", "pro", "team"] }).default("free"),
createdAt: timestamp("created_at").defaultNow().notNull(),
updatedAt: timestamp("updated_at").defaultNow().notNull(),
});
export const projects = pgTable("projects", {
id: serial("id").primaryKey(),
userId: uuid("user_id").notNull().references(() => users.id, { onDelete: "cascade" }),
name: varchar("name", { length: 100 }).notNull(),
description: text("description"),
isPublic: boolean("is_public").default(false).notNull(),
createdAt: timestamp("created_at").defaultNow().notNull(),
}, (table) => ({
userIdIdx: index("projects_user_id_idx").on(table.userId),
}));
export const tasks = pgTable("tasks", {
id: serial("id").primaryKey(),
projectId: integer("project_id").notNull().references(() => projects.id, { onDelete: "cascade" }),
title: text("title").notNull(),
status: text("status", { enum: ["todo", "in_progress", "done"] }).default("todo"),
priority: integer("priority").default(0),
dueDate: timestamp("due_date"),
createdAt: timestamp("created_at").defaultNow().notNull(),
});
// Tipi TypeScript inferiti automaticamente
export type User = typeof users.$inferSelect;
export type Project = typeof projects.$inferSelect;
export type Task = typeof tasks.$inferSelect;
export type NewTask = typeof tasks.$inferInsert;Select: query type-safe
import { db } from "@/db";
import { users, projects, tasks } from "@/db/schema";
import { eq, and, desc, gt, sql } from "drizzle-orm";
// Select semplice
const allUsers = await db.select().from(users);
// Tipo inferito: User[]
// Select con filtro
const user = await db
.select()
.from(users)
.where(eq(users.email, "mario@example.com"))
.limit(1);
// Select con join
const projectsWithTasks = await db
.select({
projectId: projects.id,
projectName: projects.name,
taskCount: sql<number>`COUNT(${tasks.id})`,
})
.from(projects)
.leftJoin(tasks, eq(projects.id, tasks.projectId))
.where(eq(projects.userId, userId))
.groupBy(projects.id, projects.name)
.orderBy(desc(projects.createdAt));
// Query relazionale (con schema definito)
const usersWithProjects = await db.query.users.findMany({
where: eq(users.plan, "pro"),
with: {
projects: {
where: eq(projects.isPublic, true),
orderBy: desc(projects.createdAt),
limit: 5,
},
},
});Insert, Update, Delete
// INSERT — con returning per avere i dati inseriti
const [newProject] = await db
.insert(projects)
.values({
userId: userId,
name: "Nuovo progetto",
description: "Descrizione",
isPublic: false,
})
.returning();
// newProject: Project (tipo inferito)
// INSERT multiplo
await db.insert(tasks).values([
{ projectId: newProject.id, title: "Task 1", status: "todo" },
{ projectId: newProject.id, title: "Task 2", status: "in_progress" },
]);
// UPDATE
const [updated] = await db
.update(tasks)
.set({
status: "done",
updatedAt: new Date(),
})
.where(and(eq(tasks.id, taskId), eq(tasks.projectId, projectId)))
.returning();
// DELETE con returning
const [deleted] = await db
.delete(projects)
.where(eq(projects.id, projectId))
.returning({ id: projects.id, name: projects.name });
// Upsert (insert or update)
await db
.insert(users)
.values({ email, name, plan: "free" })
.onConflictDoUpdate({
target: users.email,
set: { name, updatedAt: new Date() },
});Migrations con drizzle-kit
Drizzle-kit confronta il tuo schema TypeScript con lo stato attuale del database e genera i file SQL di migrazione. Configura drizzle.config.ts:
// drizzle.config.ts
import { defineConfig } from "drizzle-kit";
export default defineConfig({
schema: "./db/schema.ts",
out: "./db/migrations",
dialect: "postgresql",
dbCredentials: {
url: process.env.DATABASE_URL!,
},
verbose: true,
strict: true,
});Workflow di sviluppo:
# Genera il file di migrazione SQL dallo schema
npx drizzle-kit generate
# Applica le migrazioni al database
npx drizzle-kit migrate
# Apri Drizzle Studio (GUI database nel browser)
npx drizzle-kit studio
# Push diretto senza migration file (solo sviluppo)
npx drizzle-kit pushI file di migrazione generati sono SQL standard leggibili — puoi ispezionarli e modificarli prima di applicarli. In produzione, esegui le migrazioni come step del tuo deployment pipeline.
Integrazione con Next.js App Router
Drizzle funziona perfettamente nei Server Components e nelle Server Actions di Next.js. Esempio di Server Action per creare un task:
// app/actions/tasks.ts
"use server";
import { db } from "@/db";
import { tasks } from "@/db/schema";
import { revalidatePath } from "next/cache";
import { z } from "zod";
const CreateTaskSchema = z.object({
projectId: z.number().int().positive(),
title: z.string().min(1).max(200),
priority: z.number().int().min(0).max(3).optional(),
});
export async function createTask(formData: FormData) {
const parsed = CreateTaskSchema.safeParse({
projectId: Number(formData.get("projectId")),
title: formData.get("title"),
priority: Number(formData.get("priority") ?? 0),
});
if (!parsed.success) {
return { error: parsed.error.flatten() };
}
const [task] = await db
.insert(tasks)
.values(parsed.data)
.returning();
revalidatePath(`/projects/${parsed.data.projectId}`);
return { task };
}Risorse correlate
Approfondisci sul blog
- Supabase: database, auth e realtime
- GitHub Actions CI/CD: guida completa 2026
- Stack zero cost per SaaS: Vercel + Supabase
Documentazione e strumenti
FAQ
Drizzle funziona con database diversi da PostgreSQL?
Sì. Drizzle supporta PostgreSQL, MySQL, SQLite e altri. L’API è quasi identica tra i dialect — cambia principalmente l’import (drizzle-orm/mysql2, drizzle-orm/better-sqlite3) e alcune funzioni specifiche del database. Per SQLite in ambiente serverless (Cloudflare D1, Turso), Drizzle è la scelta di riferimento.
Come migro da Prisma a Drizzle?
La migrazione non è automatizzata ma è gestibile. Il processo: (1) riscrivi lo schema schema.prisma in pgTable TypeScript, (2) genera una migrazione Drizzle che porta il database allo stato attuale (senza applicarla — il DB è già migrato da Prisma), (3) aggiorna le query una file alla volta. Non esiste un tool di migrazione automatica da Prisma a Drizzle nel 2026.
Drizzle ha un equivalente di Prisma Studio?
Sì: npx drizzle-kit studio apre una GUI web per esplorare e modificare i dati del database. Non è ricca come Prisma Studio in termini di funzionalità avanzate, ma copre i casi d’uso principali per il debug durante lo sviluppo.
Posso usare Drizzle con Supabase?
Sì, ed è una delle combinazioni più usate nel 2026. Usi Drizzle come query builder TypeScript sopra il database PostgreSQL di Supabase, e il client Supabase per auth, realtime e storage. Non conflicto — sono due layer separati che si completano. Connetti Drizzle direttamente alla connection string PostgreSQL di Supabase (non via REST API).

