Drizzle ORM e Next.js 16: Guida Completa all'Integrazione Database

Guida pratica a Drizzle ORM con Next.js 16 App Router. Setup, schema TypeScript, query nei Server Components, CRUD con Server Actions, migrazioni, Edge Runtime e pattern avanzati per la produzione.

Drizzle ORM Next.js 16: Setup e Query 2026

Perché Drizzle ORM è Diventato lo Standard per Next.js nel 2026

Se stai costruendo un'applicazione full-stack con Next.js 16, prima o poi ti troverai di fronte alla solita domanda: quale ORM usare per il database? Fino a un paio d'anni fa la risposta era quasi scontata — Prisma, punto. Ma nel 2026 lo scenario si è ribaltato. Drizzle ORM ha superato Prisma nei download settimanali su npm (5,1 milioni contro 4,3 milioni a Q1 2026) e si è imposto come la scelta di riferimento per le architetture serverless ed edge-first.

E non si tratta solo di numeri.

Drizzle pesa circa 7,4 KB minificato e gzippato, contro gli 1,6 MB di Prisma anche dopo il drastico taglio della versione 7. Su Vercel Functions o AWS Lambda, questa differenza si traduce in cold start misurabilmente più veloci. Però il vantaggio più significativo per chi lavora con l'App Router di Next.js è un altro: Drizzle non richiede alcuno step di generazione del codice. Nessun prisma generate da rieseguire, nessuna interruzione del fast refresh di Turbopack. Modifichi lo schema TypeScript e i tipi si aggiornano istantaneamente — una cosa che, onestamente, una volta provata non si torna più indietro.

In questa guida costruiremo passo dopo passo un'integrazione completa tra Drizzle ORM e Next.js 16. Useremo i React Server Components per le query di lettura e le Server Actions per le mutazioni. Si parte dalla configurazione e si arriva fino ai pattern avanzati per la produzione.

Prerequisiti e Stack Tecnologico

Per seguire la guida ti servono:

  • Node.js 20+ (consigliato Node.js 22 LTS)
  • Next.js 16.2+ con App Router
  • TypeScript 5.5+
  • Un database PostgreSQL — useremo Neon come esempio principale (è gratuito per lo sviluppo), ma la configurazione funziona con qualsiasi provider PostgreSQL: Supabase, Vercel Postgres, istanze locali, quello che preferisci

Lo stack scelto rappresenta il percorso con meno attrito nel 2026. Non a caso, Drizzle + Neon + Next.js App Router è il template ufficiale proposto da Vercel stessa per i nuovi progetti.

Installazione e Configurazione Iniziale

Creazione del Progetto

Se parti da zero, crea un nuovo progetto Next.js con il flag --api introdotto nella versione 16.2 (include automaticamente un esempio di Route Handler):

npx create-next-app@latest my-app --typescript --tailwind --app --api
cd my-app

Installazione delle Dipendenze

Installa Drizzle ORM, il driver per Neon e Drizzle Kit come dipendenza di sviluppo:

# Per Neon (serverless PostgreSQL)
npm install drizzle-orm @neondatabase/serverless
npm install -D drizzle-kit

# Alternativa: per PostgreSQL locale o tradizionale
npm install drizzle-orm pg
npm install -D drizzle-kit @types/pg

Un dettaglio che mi piace molto: Drizzle ORM ha zero dipendenze esterne. Fa tutta la differenza nel tree-shaking e nei bundle di produzione.

Variabili d'Ambiente

Crea un file .env.local nella root del progetto con l'URL di connessione:

# .env.local
DATABASE_URL="postgresql://user:[email protected]/mydb?sslmode=require"

Se usi Neon, trovi l'URL nella dashboard del progetto sotto "Connection Details". Seleziona il formato "Drizzle" dal menu a tendina — Neon lo offre specificamente per Drizzle ORM, il che è un bel segnale di quanto l'ecosistema si stia consolidando attorno a questo ORM.

Connessione al Database

Crea la struttura delle cartelle per il livello dati. Il consiglio è tenere tutto sotto src/db/:

src/
├── db/
│   ├── index.ts        # Connessione al database
│   ├── schema.ts       # Definizione delle tabelle
│   └── migrations/     # File di migrazione generati
├── app/
│   └── ...

Client di Connessione con Neon

// src/db/index.ts
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 });

export type Database = typeof db;

Passando { schema } al costruttore drizzle() abilitiamo le Relational Queries, un'API dichiarativa per query con join che vedremo più avanti. Non saltare questo passaggio.

Client di Connessione con PostgreSQL Locale

Se invece preferisci usare un'istanza PostgreSQL locale o un provider diverso da Neon:

// src/db/index.ts
import { drizzle } from "drizzle-orm/node-postgres";
import { Pool } from "pg";
import * as schema from "./schema";

const pool = new Pool({
  connectionString: process.env.DATABASE_URL,
});

export const db = drizzle(pool, { schema });

Dettaglio importante (e rassicurante): la directory src/db/ è permanentemente inaccessibile dal client nell'App Router di Next.js. Tutte le query Drizzle vengono eseguite esclusivamente lato server — nei Server Components, nelle Server Actions o nei Route Handlers. Non c'è rischio di esporre le credenziali del database al browser.

Definizione dello Schema del Database

In Drizzle lo schema è puro codice TypeScript. Non esiste un linguaggio di schema separato come il PSL di Prisma — definisci tabelle, colonne, relazioni e vincoli usando funzioni e oggetti TypeScript. Ecco un esempio concreto:

// src/db/schema.ts
import {
  pgTable,
  serial,
  text,
  varchar,
  timestamp,
  integer,
  boolean,
  pgEnum,
} from "drizzle-orm/pg-core";
import { relations } from "drizzle-orm";

// Enum PostgreSQL nativo
export const roleEnum = pgEnum("role", ["admin", "editor", "viewer"]);

// Tabella utenti
export const users = pgTable("users", {
  id: serial("id").primaryKey(),
  name: varchar("name", { length: 255 }).notNull(),
  email: varchar("email", { length: 255 }).notNull().unique(),
  role: roleEnum("role").default("viewer").notNull(),
  createdAt: timestamp("created_at").defaultNow().notNull(),
});

// Tabella articoli
export const posts = pgTable("posts", {
  id: serial("id").primaryKey(),
  title: varchar("title", { length: 500 }).notNull(),
  content: text("content"),
  published: boolean("published").default(false).notNull(),
  authorId: integer("author_id")
    .references(() => users.id, { onDelete: "cascade" })
    .notNull(),
  createdAt: timestamp("created_at").defaultNow().notNull(),
  updatedAt: timestamp("updated_at").defaultNow().notNull(),
});

// Definizione delle relazioni per le Relational Queries
export const usersRelations = relations(users, ({ many }) => ({
  posts: many(posts),
}));

export const postsRelations = relations(posts, ({ one }) => ({
  author: one(users, {
    fields: [posts.authorId],
    references: [users.id],
  }),
}));

Ci sono alcuni punti da notare:

  • I tipi TypeScript vengono inferiti automaticamente dallo schema — nessun comando generate necessario
  • Le relazioni sono dichiarate separatamente dalle tabelle, tramite la funzione relations(). Questo è intenzionale: servono solo per le Relational Queries e non generano vincoli SQL aggiuntivi
  • I vincoli di foreign key vengono definiti direttamente nella colonna con .references() — sono quelli che effettivamente creano il vincolo nel database

Inferenza dei Tipi dallo Schema

Drizzle fornisce due helper per estrarre i tipi TypeScript dalle tabelle. Sono estremamente utili per tipizzare componenti e Server Actions:

// src/db/schema.ts (aggiungere in fondo al file)
import { InferSelectModel, InferInsertModel } from "drizzle-orm";

export type User = InferSelectModel<typeof users>;
export type NewUser = InferInsertModel<typeof users>;
export type Post = InferSelectModel<typeof posts>;
export type NewPost = InferInsertModel<typeof posts>;

InferSelectModel genera il tipo con tutti i campi (inclusi quelli con default). InferInsertModel genera il tipo dove i campi con default sono opzionali — esattamente quello che serve per i form di creazione. Sembra un dettaglio piccolo, ma in pratica ti risparmia un sacco di tipizzazione manuale.

Configurazione di Drizzle Kit e Migrazioni

Drizzle Kit è lo strumento CLI che gestisce le migrazioni. Crea un file di configurazione nella root del progetto:

// drizzle.config.ts
import { defineConfig } from "drizzle-kit";

export default defineConfig({
  schema: "./src/db/schema.ts",
  out: "./src/db/migrations",
  dialect: "postgresql",
  dbCredentials: {
    url: process.env.DATABASE_URL!,
  },
  strict: true,   // IMPORTANTE: previene perdita di dati su rename
  verbose: true,
});

Il flag strict: true è fondamentale, non saltarlo. Quando Drizzle Kit rileva una modifica ambigua (tipo la rinomina di una colonna), ti chiede conferma invece di interpretarla come "elimina la vecchia + crea la nuova". Senza questo flag, rischi di perdere dati in produzione. Ho visto succedere.

Comandi Principali

Aggiungi questi script al package.json:

{
  "scripts": {
    "db:generate": "drizzle-kit generate",
    "db:migrate": "drizzle-kit migrate",
    "db:push": "drizzle-kit push",
    "db:studio": "drizzle-kit studio"
  }
}
  • db:generate — Confronta lo schema attuale con quello precedente e genera file SQL di migrazione
  • db:migrate — Esegue le migrazioni generate sul database
  • db:push — Applica lo schema direttamente senza generare file di migrazione (perfetto per lo sviluppo)
  • db:studio — Avvia Drizzle Studio, un'interfaccia web per esplorare e modificare i dati

La regola pratica: per lo sviluppo locale usa db:push e itera velocemente. Per la produzione, genera sempre le migrazioni con db:generate e applicale con db:migrate — così puoi versionarle in Git e avere un audit trail completo.

# Sviluppo: applica lo schema direttamente
npm run db:push

# Produzione: genera e applica la migrazione
npm run db:generate
npm run db:migrate

Query nei React Server Components

Ed eccoci al bello. Qui il vantaggio dell'App Router di Next.js 16 diventa davvero evidente. I Server Components sono asincroni per default, il che significa che puoi eseguire query Drizzle direttamente nel componente — niente API intermedie, niente useEffect, niente stato client:

// app/posts/page.tsx
import { db } from "@/db";
import { posts, users } from "@/db/schema";
import { desc, eq } from "drizzle-orm";

export default async function PostsPage() {
  // Query con join — stile SQL
  const allPosts = await db
    .select({
      id: posts.id,
      title: posts.title,
      published: posts.published,
      createdAt: posts.createdAt,
      authorName: users.name,
    })
    .from(posts)
    .leftJoin(users, eq(posts.authorId, users.id))
    .orderBy(desc(posts.createdAt));

  return (
    <main className="p-6">
      <h1 className="text-3xl font-bold mb-6">Articoli</h1>
      <ul className="space-y-4">
        {allPosts.map((post) => (
          <li key={post.id} className="border p-4 rounded-lg">
            <h2 className="text-xl font-semibold">{post.title}</h2>
            <p className="text-gray-600">
              di {post.authorName} &mdash;{" "}
              {post.createdAt.toLocaleDateString("it-IT")}
            </p>
          </li>
        ))}
      </ul>
    </main>
  );
}

Nessun JavaScript viene inviato al browser per questo componente. La query gira sul server, il risultato viene renderizzato in HTML e spedito al client. Zero waterfall, zero loading state intermedi. È una di quelle cose che quando le vedi funzionare ti chiedi perché non abbiamo sempre fatto così.

Relational Queries: L'Alternativa Dichiarativa

Se hai configurato le relazioni nello schema (e passato { schema } al costruttore di Drizzle), puoi usare le Relational Queries — un'API più dichiarativa per query con join:

// app/users/[id]/page.tsx
import { db } from "@/db";
import { eq } from "drizzle-orm";
import { users } from "@/db/schema";
import { notFound } from "next/navigation";

interface Props {
  params: Promise<{ id: string }>;
}

export default async function UserProfilePage({ params }: Props) {
  const { id } = await params;

  const user = await db.query.users.findFirst({
    where: eq(users.id, parseInt(id)),
    with: {
      posts: {
        orderBy: (posts, { desc }) => [desc(posts.createdAt)],
        limit: 10,
      },
    },
  });

  if (!user) notFound();

  return (
    <div className="p-6">
      <h1 className="text-3xl font-bold">{user.name}</h1>
      <p className="text-gray-600 mb-6">{user.email}</p>
      <h2 className="text-xl font-semibold mb-4">Ultimi articoli</h2>
      {user.posts.map((post) => (
        <article key={post.id} className="mb-3">
          <h3 className="font-medium">{post.title}</h3>
        </article>
      ))}
    </div>
  );
}

Le Relational Queries sono particolarmente comode per query annidate — equivalgono a un LEFT JOIN con raggruppamento automatico dei risultati. Internamente Drizzle genera una singola query SQL ottimizzata, quindi le performance non ne risentono.

Operazioni CRUD con le Server Actions

Le Server Actions di Next.js sono il meccanismo ideale per le mutazioni del database con Drizzle. Definisci la logica in un file con la direttiva "use server", e Next.js si occupa di tutto il resto — serializzazione, gestione degli errori, revalidazione della cache. Vediamo come.

Creazione di un Record

// src/actions/posts.ts
"use server";

import { db } from "@/db";
import { posts } from "@/db/schema";
import { revalidatePath } from "next/cache";
import { redirect } from "next/navigation";
import { z } from "zod";

const CreatePostSchema = z.object({
  title: z.string().min(3, "Il titolo deve avere almeno 3 caratteri").max(500),
  content: z.string().optional(),
  authorId: z.coerce.number().positive(),
});

export async function createPost(formData: FormData) {
  const parsed = CreatePostSchema.safeParse({
    title: formData.get("title"),
    content: formData.get("content"),
    authorId: formData.get("authorId"),
  });

  if (!parsed.success) {
    return { error: parsed.error.flatten().fieldErrors };
  }

  const [newPost] = await db
    .insert(posts)
    .values({
      title: parsed.data.title,
      content: parsed.data.content ?? null,
      authorId: parsed.data.authorId,
    })
    .returning();

  revalidatePath("/posts");
  redirect(`/posts/${newPost.id}`);
}

Aggiornamento di un Record

// src/actions/posts.ts (continuazione)
const UpdatePostSchema = z.object({
  id: z.coerce.number().positive(),
  title: z.string().min(3).max(500).optional(),
  content: z.string().optional(),
  published: z.coerce.boolean().optional(),
});

export async function updatePost(formData: FormData) {
  const parsed = UpdatePostSchema.safeParse({
    id: formData.get("id"),
    title: formData.get("title"),
    content: formData.get("content"),
    published: formData.get("published"),
  });

  if (!parsed.success) {
    return { error: parsed.error.flatten().fieldErrors };
  }

  const { id, ...updateData } = parsed.data;

  await db
    .update(posts)
    .set({ ...updateData, updatedAt: new Date() })
    .where(eq(posts.id, id));

  revalidatePath("/posts");
  revalidatePath(`/posts/${id}`);
}

Eliminazione di un Record

// src/actions/posts.ts (continuazione)
import { eq } from "drizzle-orm";

export async function deletePost(id: number) {
  await db.delete(posts).where(eq(posts.id, id));
  revalidatePath("/posts");
}

Integrazione nel Form Client

Le Server Actions si collegano ai form React in modo trasparente. Ecco un componente client che usa la action di creazione:

// app/posts/new/PostForm.tsx
"use client";

import { useActionState } from "react";
import { createPost } from "@/actions/posts";

export function PostForm({ authorId }: { authorId: number }) {
  const [state, formAction, isPending] = useActionState(createPost, null);

  return (
    <form action={formAction} className="space-y-4">
      <input type="hidden" name="authorId" value={authorId} />

      <div>
        <label htmlFor="title" className="block font-medium">
          Titolo
        </label>
        <input
          id="title"
          name="title"
          type="text"
          required
          className="border rounded px-3 py-2 w-full"
        />
        {state?.error?.title && (
          <p className="text-red-500 text-sm">{state.error.title}</p>
        )}
      </div>

      <div>
        <label htmlFor="content" className="block font-medium">
          Contenuto
        </label>
        <textarea
          id="content"
          name="content"
          rows={6}
          className="border rounded px-3 py-2 w-full"
        />
      </div>

      <button
        type="submit"
        disabled={isPending}
        className="bg-blue-600 text-white px-4 py-2 rounded disabled:opacity-50"
      >
        {isPending ? "Salvataggio..." : "Pubblica articolo"}
      </button>
    </form>
  );
}

Il pattern è semplice e pulito: validazione sul server con Zod, mutazione con Drizzle, e revalidatePath per aggiornare automaticamente le pagine che mostrano i dati modificati. Nessun endpoint API intermedio, nessun fetch manuale. Questo è il tipo di developer experience che rende difficile tornare ai vecchi pattern REST.

Pattern Avanzati per la Produzione

Paginazione Server-Side

La paginazione nei Server Components con Drizzle è piuttosto elegante, grazie ai query params dell'URL:

// app/posts/page.tsx
import { db } from "@/db";
import { posts } from "@/db/schema";
import { desc, sql } from "drizzle-orm";

interface Props {
  searchParams: Promise<{ page?: string; q?: string }>;
}

const PAGE_SIZE = 20;

export default async function PostsPage({ searchParams }: Props) {
  const { page = "1", q } = await searchParams;
  const currentPage = Math.max(1, parseInt(page));
  const offset = (currentPage - 1) * PAGE_SIZE;

  // Query con filtro opzionale e conteggio totale
  const whereClause = q
    ? sql`${posts.title} ILIKE ${`%${q}%`}`
    : undefined;

  const [items, countResult] = await Promise.all([
    db
      .select()
      .from(posts)
      .where(whereClause)
      .orderBy(desc(posts.createdAt))
      .limit(PAGE_SIZE)
      .offset(offset),
    db
      .select({ count: sql<number>`count(*)` })
      .from(posts)
      .where(whereClause),
  ]);

  const totalPages = Math.ceil(countResult[0].count / PAGE_SIZE);

  return (
    <div>
      {/* Render items e controlli paginazione */}
    </div>
  );
}

Transazioni

Per operazioni che coinvolgono più tabelle e devono essere atomiche, Drizzle supporta le transazioni in modo molto diretto:

// src/actions/users.ts
"use server";

import { db } from "@/db";
import { users, posts } from "@/db/schema";
import { eq } from "drizzle-orm";

export async function deleteUserWithPosts(userId: number) {
  await db.transaction(async (tx) => {
    // Elimina prima tutti gli articoli dell'utente
    await tx.delete(posts).where(eq(posts.authorId, userId));
    // Poi elimina l'utente
    await tx.delete(users).where(eq(users.id, userId));
  });
}

Se una qualsiasi operazione all'interno della transazione fallisce, tutte vengono annullate. Nessuna sorpresa.

Prepared Statements

Per query eseguite frequentemente, i prepared statement migliorano le performance evitando la ricompilazione della query a ogni esecuzione:

// src/db/queries.ts
import { db } from "@/db";
import { posts } from "@/db/schema";
import { eq } from "drizzle-orm";
import { placeholder } from "drizzle-orm";

export const getPostById = db
  .select()
  .from(posts)
  .where(eq(posts.id, placeholder("id")))
  .prepare("get_post_by_id");

// Utilizzo nel Server Component
// const post = await getPostById.execute({ id: 42 });

Validazione Integrata con Zod e Drizzle

Una novità recente che vale la pena menzionare: Drizzle ha incorporato i pacchetti di validazione direttamente nel core. Non servono più dipendenze separate come drizzle-zod — tutto è disponibile da drizzle-orm:

// src/db/validation.ts
import { createInsertSchema, createSelectSchema } from "drizzle-orm/zod";
import { posts } from "./schema";

// Genera automaticamente uno schema Zod dalla tabella
export const insertPostSchema = createInsertSchema(posts, {
  // Override per campi specifici
  title: (schema) =>
    schema.min(3, "Minimo 3 caratteri").max(500, "Massimo 500 caratteri"),
});

export const selectPostSchema = createSelectSchema(posts);

Questo elimina la duplicazione tra schema del database e schema di validazione. Quando aggiungi una colonna alla tabella, lo schema Zod si aggiorna in automatico. Meno codice da mantenere, meno possibilità di disallineamento.

Compatibilità Edge Runtime

Uno dei vantaggi più concreti di Drizzle rispetto a Prisma è la compatibilità nativa con l'Edge Runtime. Se usi Neon con il driver HTTP, le tue query funzionano su Vercel Edge Functions, Cloudflare Workers e qualsiasi altro edge runtime senza configurazione aggiuntiva:

// app/api/posts/route.ts
import { db } from "@/db";
import { posts } from "@/db/schema";
import { desc } from "drizzle-orm";

// Questo Route Handler gira sull'Edge Runtime
export const runtime = "edge";

export async function GET() {
  const latestPosts = await db
    .select()
    .from(posts)
    .orderBy(desc(posts.createdAt))
    .limit(10);

  return Response.json(latestPosts);
}

Il driver HTTP di Neon (@neondatabase/serverless) è progettato specificamente per ambienti edge: non usa connessioni TCP persistenti, ma invia query come richieste HTTP. Questo si traduce in latenze più prevedibili e zero problemi di connection pooling.

Per chi preferisce Turso (basato su libSQL/SQLite), Drizzle offre un driver dedicato che funziona altrettanto bene sull'edge:

# Installazione per Turso
npm install @libsql/client
// Connessione Drizzle + Turso
import { drizzle } from "drizzle-orm/libsql";
import { createClient } from "@libsql/client";

const client = createClient({
  url: process.env.TURSO_DATABASE_URL!,
  authToken: process.env.TURSO_AUTH_TOKEN,
});

export const db = drizzle(client);

Integrazione con il Sistema di Cache di Next.js 16

Next.js 16 ha introdotto nuove API di caching come use cache e cacheTag. Drizzle si integra perfettamente con queste API — e qui le cose diventano davvero interessanti:

// src/data/posts.ts
"use cache";

import { db } from "@/db";
import { posts } from "@/db/schema";
import { desc, eq } from "drizzle-orm";
import { cacheTag, cacheLife } from "next/cache";

export async function getPublishedPosts() {
  cacheTag("posts");
  cacheLife("hours");

  return db
    .select()
    .from(posts)
    .where(eq(posts.published, true))
    .orderBy(desc(posts.createdAt));
}

export async function getPostById(id: number) {
  cacheTag(`post-${id}`);
  cacheLife("hours");

  const result = await db
    .select()
    .from(posts)
    .where(eq(posts.id, id))
    .limit(1);

  return result[0] ?? null;
}

Quando una Server Action modifica un articolo, invalida il tag specifico:

// Nella Server Action di aggiornamento
import { revalidateTag } from "next/cache";

// Dopo l'update
revalidateTag("posts");
revalidateTag(`post-${id}`);

Questo pattern combina il meglio di entrambi i mondi: query veloci in cache per le letture, invalidazione chirurgica per le scritture. In pratica significa che le pagine si caricano quasi istantaneamente ma i dati sono sempre aggiornati quando servono.

Drizzle Studio: Esplorare i Dati Visivamente

Drizzle Kit include Drizzle Studio, un'interfaccia web che si avvia con un solo comando:

npm run db:studio

Studio si apre nel browser e ti permette di esplorare le tabelle, eseguire query, modificare i dati e visualizzare le relazioni. È particolarmente utile durante lo sviluppo per verificare che le migrazioni abbiano funzionato correttamente e per ispezionare i dati inseriti dalle Server Actions.

A differenza di Prisma Studio, Drizzle Studio funziona anche con database remoti senza configurazione aggiuntiva — basta che l'URL di connessione nel file di configurazione sia corretto. Un piccolo vantaggio pratico che si apprezza ogni giorno.

Errori Comuni e Come Evitarli

Prima di chiudere, ecco gli errori che vedo fare più spesso (e che ho fatto anch'io, a dire il vero):

  • Dimenticare strict: true nelle migrazioni — Senza questa opzione, rinominare una colonna viene interpretato come eliminazione e ricreazione. Risultato: perdi tutti i dati di quella colonna. Non è divertente
  • Importare il client database in un Client Component — Il modulo src/db/index.ts usa moduli Node.js (pg, @neondatabase/serverless) che non esistono nel browser. Se lo importi in un file con "use client", otterrai un errore di build piuttosto criptico
  • Non usare returning() dopo insert/update — Se hai bisogno dei dati appena inseriti (tipo l'ID auto-generato), usa sempre .returning() per evitare una seconda query inutile
  • Ignorare il connection pooling in produzione — Per applicazioni con traffico significativo, configura sempre un pool di connessioni. Con node-postgres usa Pool invece di Client; con Neon il driver HTTP gestisce automaticamente il pooling

Domande Frequenti

Drizzle ORM è pronto per la produzione?

Sì, assolutamente. La versione 1.0 è ancora in beta (v1.0.0-beta.2 ad aprile 2026), ma la versione stabile 0.45.x è ampiamente usata in produzione da migliaia di applicazioni. Il team di Drizzle ha chiarito che la beta riguarda principalmente nuove funzionalità come il supporto MSSQL e la nuova architettura di migrazione, non instabilità del core. Molte aziende su Vercel lo usano in produzione senza problemi.

Posso usare Drizzle ORM con MySQL o SQLite in Next.js?

Certo. Drizzle supporta PostgreSQL, MySQL e SQLite con pacchetti dedicati (drizzle-orm/mysql-core, drizzle-orm/sqlite-core). Per SQLite in ambienti serverless, Turso con @libsql/client è la combinazione più diffusa. Per MySQL, PlanetScale con il driver serverless è la scelta più comune. L'API rimane praticamente identica indipendentemente dal database scelto.

Come gestisco le migrazioni in un team con più sviluppatori?

Usa drizzle-kit generate per creare file di migrazione SQL che puoi versionare in Git. La versione 1.0 beta ha introdotto una nuova architettura di migrazioni che elimina i conflitti Git sul file journal e include un sistema di commutativity check — rileva le collisioni tra migrazioni di sviluppatori diversi e suggerisce come risolverle. Una manna per i team distribuiti.

Drizzle ORM funziona con il nuovo sistema di caching di Next.js 16?

Sì, perfettamente. Le query Drizzle nei Server Components possono essere wrappate con la direttiva "use cache" e annotate con cacheTag e cacheLife. Le Server Actions possono poi invalidare cache specifiche con revalidateTag. L'integrazione è trasparente perché Drizzle restituisce semplici oggetti JavaScript serializzabili — nessun adattamento necessario.

È meglio Drizzle o Prisma per un nuovo progetto Next.js nel 2026?

Dipende. Drizzle è la scelta migliore se deployi su edge runtime o serverless, vuoi il bundle più piccolo possibile, o il tuo team ha familiarità con SQL. Prisma resta preferibile se il team è nuovo ai database, hai bisogno di supporto MongoDB, o vuoi un sistema di migrazioni più automatizzato. Detto questo, per la maggior parte dei nuovi progetti Next.js con deploy su Vercel, Drizzle è diventato il default raccomandato nel 2026.

Sull'Autore Editorial Team

Our team of expert writers and editors.