Strona główna / Artykuły

Drizzle kontra Prisma: wybór ORM dla TypeScript bez nadmiernego haju

Porównaj Drizzle i Prisma pod względem projektowania schematu, stylu zapytań, zapisów wstrzykniętych, migracji, transakcji oraz wydajności, wraz z zaleceniami dotyczącymi tego, który z nich nadaje się dla której grupy.

Wydawca: REACTAPP.TOP7 min czytania1588 słów

Źródła: Drizzle vs Prisma: Choosing the Right TypeScript ORM in 2026 (Deep Dive)

Drizzle ORM i Prisma to dwa najczęściej wymieniane rozwiązania przy wyborze ORM do projektu w TypeScript. Rozwiązują one ten sam problem, czyli przekształcanie dostępu do bazy danych w kod typowany, ale wychodzą z przeciwnych założeń dotyczących tego, na jak bliskim poziomie powinno się pozostać SQL. Żadna z tych opcji nie jest błędna. Jeśli wasz zespół już dobrze zna jedno z nich, kontynuujcie jego używanie i poświęćcie czas na rozwój produktu. Porównanie staje się ważniejsze, gdy rozpoczynacie coś nowego lub musicie wyjaśnić istniejącą konfigurację kolegom.

Dwa narzędzia

Drizzle pojawił się w 2022 roku i szybko zyskał popularność wśród programistów, którzy chcą mieć kontrolę bez dodatkowego obciążenia. Jest to rozwiązanie oparte przede wszystkim na TypeScript: tabele, relacje i zapytania są deklarowane w tym języku. Można go opisać jako cienką warstwę typową nad SQL. Typy są wywnioskowywane automatycznie, surowy SQL pozostaje bezpieczny pod względem typów, a projekt nadaje się do kodu wymagającego wysokiej wydajności. Obsługiwane są PostgreSQL, MySQL, SQLite oraz SQL Server.

Prisma pojawiła się wcześniej, w 2018 roku, i budowała swoją reputację na doświadczeniu programistów, produktywności oraz narzędziach. Model danych znajduje się w pliku .prisma napisanym w Prisma Schema Language (PSL), a polecenie prisma generate przekształca go w w pełni ztypowany klient z API wysokiego poziomu. Związki między danymi są obsługiwane automatycznie, a ekosystem jest większy i bardziej rozwinięty, w tym Prisma Studio – wizualny przeglądarka i edytor danych. Wśród obsługiwanych baz danych znajdują się PostgreSQL, MySQL, SQLite oraz MongoDB i inne.

Definiowanie schematu

Tutaj filozofie różnią się najbardziej widocznie, a wybór wpływa na codzienną pracę oraz bezpieczeństwo typów.

Drizzle: kod na pierwszym miejscu

Schemat w Drizzle to zwykły TypeScript. Tabele, kolumny oraz związki między danymi znajdują się w tym samym języku co aplikacja, więc nie ma etapu generowania, a zmiany pojawiają się natychmiast w edytorze.

import { pgTable, serial, varchar, text, integer, timestamp } from "drizzle-orm/pg-core";

// Authors table
export const authors = pgTable("authors", {
  id: serial("id").primaryKey(),
  name: varchar("name", { length: 100 }).notNull(),
  bio: text("bio"),
});

// Books table referencing Authors
export const books = pgTable("books", {
  id: serial("id").primaryKey(),
  title: varchar("title", { length: 150 }).notNull(),
  summary: text("summary"),
  authorId: integer("author_id")
    .notNull()
    .references(() => authors.id),
  publishedAt: timestamp("published_at").defaultNow(),
});

Ponieważ typy są wywnioskowywane, wynik zapytania ma określony kształt bez konieczności używania dodatkowego pliku schematu. Dzięki temu unikamy niespodzianek podczas wykonywania aplikacji.

Prisma: schemat na początku

Prisma przechowuje model w oddzielnym pliku .prisma:

model Author {
  id    Int     @id @default(autoincrement())
  name  String
  bio   String?
  books Book[]
}

model Book {
  id          Int      @id @default(autoincrement())
  title       String
  summary     String?
  publishedAt DateTime @default(now())
  authorId    Int
  author      Author   @relation(fields: [authorId], references: [id])
}

Schemat jest wyraźnie oddzielony od kodu aplikacji. Po każdej zmianie prisma generate odbudowuje klienta, dzięki czemu typy są zgodne. Dla osób z niewielką wiedzą na temat SQL abstrakcja ta jest bardziej przyjazna, ponieważ pracuje się z generowanym klientem wysokiego poziomu, a nie z logiką w stylu SQL. Zaletą jest to, że istnieje dodatkowa warstwa pomiędzy kodem a bazą danych.

Pisanie zapytań

Oba podejścia umożliwiają pisanie zapytań z weryfikacją typów, ale ich forma jest inna.

Drizzle

Budownik zapytań jest celowo prosty i przewidywalny, a jego struktura przypomina SQL. Jeśli znasz się na łączeniach tabel i klauzulach warunkowych, jest to naturalne rozwiązanie, a to, co napiszesz, dokładnie odpowiada temu, co jest wykonywane w bazie danych. W tym przykładzie łączymy książki z autorami oraz filtrowamy i sortujemy wyniki:

const results = await db
  .select({
    bookId: books.id,
    title: books.title,
    publishedAt: books.publishedAt,
    authorName: authors.name,
  })
  .from(books)
  .leftJoin(authors, eq(books.authorId, authors.id))
  .where(eq(authors.id, authorId))
  .orderBy(books.publishedAt);

Umożliwia to precyzyjną kontrolę oraz wysoce przewidywalny SQL. Znacząca różnica w porównaniu z Prismą pojawia się podczas tworzenia powiązanych wierszy. Drizzle nie obsługuje zagnieżdżonych operacji zapisu, więc każda operacja wstawiania danych w relacjach jest realizowana ręcznie, zazwyczaj w ramach transakcji, aby wszystko pozostało spójne:

const result = await db.transaction(async (tx) => {
  const [newAuthor] = await tx.insert(authors).values({
    name: "Alice",
    bio: "Fantasy author",
  }).returning({ id: authors.id });

  const [newBook] = await tx.insert(books).values({
    title: "The Dream Forest",
    authorId: newAuthor.id,
  }).returning();

  return { newAuthor, newBook };
});

Pełna przejrzystość to zaleta. Więcej szablonowych kodów to wada.

Prisma

Prisma obiera przeciwną drogę. Wywołujesz metody w automatycznie wygenerowanym kliencie, a baza danych pozostaje dostępna poprzez metody w stylu obiektowym. To samo wyszukiwanie wygląda w ten sposób:

const results = await prisma.book.findMany({
  where: { authorId },
  select: {
    id: true,
    title: true,
    publishedAt: true,
    author: {
      select: { name: true },
    },
  },
  orderBy: { publishedAt: "asc" },
});

Tutaj nikt nie musi myśleć w SQL, co tłumaczy duży popyt na Prismę. Świetnie radzi sobie również przy tworzeniu powiązanych danych, ponieważ obsługuje zagnieżdżone operacje zapisu. Książkę i jej autora można stworzyć w jednej prośbie:

const newBook = await prisma.book.create({
  data: {
    title: "The Dream Forest",
    summary: "A surreal adventure.",
    author: {
      create: {
        name: "Alice",
        bio: "Fantasy author",
      },
    },
  },
});

Żadne klucze obce nie muszą być ręcznie konfigurowane. To, co tracisz, to możliwość widzenia, ponieważ rzeczywiście wykonywany SQL jest przed tobą ukryty, więc masz mniejszą kontrolę niż w przypadku Drizzle.

Migracje

Zmiana schematu z biegiem czasu ma znaczenie w każdym rzeczywistym projekcie, a oba narzędzia radzą sobie z tym dobrze.

Drizzle

Ponieważ schemat jest napisany w TypeScript, stanowi on źródło prawdy, a interfejs wiersza poleceń (drizzle-kit) wytwarza migracje na podstawie różnic w Twoich definicjach. W procesie pracy opartym na migracjach zaczynasz od generowania:

npx drizzle-kit generate

To porównuje obecny stan z twoim schematem TypeScript i tworzy plik migracji z zmianami SQL, który możesz edytować ręcznie przed jego użyciem. Zastosowanie go odbywa się za pomocą osobnego polecenia:

npx drizzle-kit migrate

Dla szybkiego tworzenia prototypów istnieje również skrót:

npx drizzle-kit push

Wysyła schemat TypeScript bezpośrednio do bazy danych, nie tworząc pliku migracji. To szybkie i wygodne na początkowym etapie, ale bez historii migracji nie nadaje się do użycia w środowisku produkcyjnym.

Prisma

W Prismie plik .prisma jest źródłem prawdy, a narzędzie generuje SQL dla każdej zmiany. Zastosowanie zapisanych migracji do bazy danych wymaga:

npx prisma migrate deploy

Podczas rozwoju polecenie prisma migrate dev służy do utworzenia nowej folderu z migracjami zawierającymi skrypty SQL, jej zastosowania oraz aktualizacji tabeli historii. Dzięki temu powstaje uporządkowany, w dużej mierze automatyczny proces pracy.

Istnieje tu również skrót do tworzenia prototypów:

npx prisma db push

aktualizuje bazę danych bezpośrednio, podobnie jak Drizzle przy użyciu mechanizmu push, bez plików migracyjnych, więc nadaje się wyłącznie do użytku poza środowiskiem produkcyjnym.

Transakcje

Oba narzędzia obsługują transakcje, z niewielkimi różnicami w sposobie realizacji.

Drizzle wykorzystuje db.transaction wraz z funkcją callback. Każda zapytanie wewnątrz transakcji albo się udaje, albo zawodzi razem, a dzięki bliskiemu powiązaniu z SQL można dokładnie kontrolować to, co dzieje się w tym bloku:

await db.transaction(async (tx) => {
  const newAuthor = await tx.insert(authors).values({
    name: "Alice Walker",
    bio: "Pulitzer Prize-winning author",
  }).returning();

  await tx.insert(books).values({
    title: "Journey to the Mountains",
    summary: "A story about adventure and discovery.",
    authorId: newAuthor[0].id,
  });
});

Prisma oferuje prisma.$transaction, który przyjmuje albo tablicę z zapytaniami, albo funkcję asynchroniczną. Zatwierdzenie i cofnięcie są automatyczne:

await prisma.$transaction(async (tx) => {
  const newAuthor = await tx.author.create({
    data: {
      name: "Alice Walker",
      bio: "Pulitzer Prize-winning author",
    },
  });

  await tx.book.create({
    data: {
      title: "Journey to the Mountains",
      summary: "A story about adventure and discovery.",
      authorId: newAuthor.id,
    },
  });
});

Wydajność

Wydajność to obszar, w którym kluczowe znaczenie mają leżące u podstaw filozofie.

Drizzle jest lekki, a jego zapytania mapują się niemal bezpośrednio na operacje bazy danych, dzięki czemu koszt abstrakcji jest bardzo niski. Jest to przydatne przy złożonych zapytaniach, operacjach masowych oraz kodzie o wysokiej przepustowości. Dzięki pełnej kontroli nad połączeniami, transakcjami i strukturą zapytań łatwiej jest optymalizować je pod kątem szybkości, ale wymaga to znajomości SQL i projektowania baz danych.

Generowany klient Prismy ukrywa SQL za API o wysokim poziomie abstrakcji. To powoduje niewielką stratę wydajności, choć w większości zwykłych aplikacji różnica jest zaniedbywalna. Relacje, zagnieżdżone zapytania oraz typowane wyniki są dostępne bez dodatkowych kosztów, co przyspiesza rozwój. Taka wygoda może czasami skutkować zapytaniami SQL mniej optymalnymi niż te starannie napisane ręcznie.

Wybór między nimi

Oba to nowoczesne ORM przyjazne dla TypeScript z bezpieczeństwem typów, możliwościami migracji, zarządzaniem relacjami i transakcjami, więc decyzja zależy od priorytetów.

Wybierz Drizzle, jeśli chcesz pozostać blisko SQL, mieć kontrolę nad zapytaniami i migracjami oraz potrzebujesz przewidywalnej wydajności przy dużym lub złożonym obciążeniu. TypeScript pełni rolę jedynego źródła prawdy, a nic nie jest ukrywane.

Wybierz Prisma, jeśli zespół chce działać szybko, skupić się na funkcjonalnościach i polegać na automatyzacji przy migracjach, zapytaniach i relacjach. Model oparty na schemacie oraz generowany klient zapewniają czystą, wysokiego poziomu API przyjazną początkującym.

Krótko mówiąc:

  • Schemat: kod w TypeScript (Drizzle) versus plik .prisma w połączeniu z generowaniem (Prisma).
  • Zapytania: podobne do SQL i wyraźne versus w stylu obiektowym i abstrakcyjne.
  • Zapisy wewnątrz siebie: ręczne w Drizzle, wbudowane w Prisma.
  • Wydajność: bardziej efektywny i łatwiejszy w kontrolowaniu w porównaniu z nieco większym obciążeniem dla większej wygody.