
Drizzle ORM давно перестал быть просто очередной либой для работы с БД. Это инструмент, который делает миграции не просто безопасными, а удобными - особенно когда работаешь в команде и нужна синхронизация схемы между разработчиками. В 2026 году Drizzle полностью переосмыслил подход к миграциям, добавив versioning и комуtativity checks, что решает одну из самых болючих проблем - конфликты в _journal.json при параллельной разработке.
Если ты работаешь на Next.js с Server Components, то интеграция Drizzle становится вообще кайфом: ты объявляешь схему в TypeScript, генерируешь миграции одной командой, и всё работает как надо. Плюс типизация на 100% - компилятор не даст тебе накосячить с запросом.
Почему миграции в Drizzle - это вообще норм
День за днём разработчики сталкивались с одной и той же проблемой: несколько веток в гите, каждая со своей миграцией, и при мерже - boom, конфликт в _journal.json. Версии разные, SQL разный, а разрешить конфликт можно только вручную. Это просто боль, особенно в больших командах.
Drizzle v2 решил эту проблему через migration table versioning и matching by folder name instead of timestamps. Теперь каждая миграция имеет явный номер версии и название папки, что убирает рассинхронизацию. Плюс появился drizzle-kit check - команда, которая проверяет коммутативность миграций и предупредит, если что-то не так.
Возможности drizzle-kit сейчас покрывают оба подхода к миграциям:
- Database first - схема в БД как источник истины, ты её pull’ишь в код
- Codebase first - схема в TypeScript как источник истины, генерируешь SQL миграции
Выбор зависит от того, как устроена архитектура проекта и как работает команда.
Интеграция с Next.js и Server Components - это то, что нужно
Когда ты пишешь на Next.js 15+ с полноценными Server Components, то Drizzle встраивается просто идеально. В RSC ты можешь прямо в компоненте async/await вызвать запрос к БД - никаких API слоёв, никаких лишних рендеров.
Вот как это выглядит в реальности. Ты создаёшь файл схемы в TypeScript - там таблицы, связи, индексы. Потом запускаешь drizzle-kit generate, и она сама создаст SQL миграции с правильными изменениями. После этого применяешь миграции через drizzle-kit push или используешь внешний инструмент - кому как удобнее.
В Server Component потом просто импортируешь db-инстанс и пишешь запрос:
import { db } from '@/db';
import BookCard from '@/components/BookCard';
export default async function BooksPage() {
const books = await db.query.book.findMany({
with: { notes: true },
});
return (
<div>
{books.map((book) => (
<BookCard key={book.id} book={book} />
))}
</div>
);
}
Всё типизировано - TypeScript сразу подскажет, если ты обратишься к несуществующему полю. Нет runtime ошибок из-за опечаток в названиях колонок. Вот это и есть настоящее качество разработки.
Процесс интеграции занимает минут пять:
- Устанавливаешь
drizzle-orm и drizzle-kit через npm
- Создаёшь
drizzle.config.ts с указанием диалекта и credentials
- Пишешь схему в отдельном файле
- Генерируешь и применяешь миграции
- Используешь db в компонентах и API routes
Типизация на уровне БД - не фантазия, а реальность
Типизация в Drizzle работает на нескольких уровнях одновременно. Ты объявляешь таблицу с точными типами полей, и эти типы автоматически пробрасываются во всю цепочку - от запроса до результата. Если ты добавил новое поле в таблицу, TypeScript сразу будет ругаться во всех местах, где ты старую версию используешь.
Это предотвращает целый класс багов, которые обычно обнаруживаются только в production. Например, ты изменил тип поля с string на number - компилятор не даст запустить код, пока ты везде не обновишь обработчик.
Основные плюсы типизации в Drizzle:
- Автокомплит в IDE - вводишь
db.query.users. и видишь все доступные методы
- Валидация на этапе разработки - не нужно ждать runtime ошибок
- Рефакторинг без страха - переименуешь поле в схеме, и IDE покажет все места, где это повлияет
- Генерация типов из схемы - не нужно дублировать описание структуры в TypeScript
- Type-safe relations - связи между таблицами уже в типах, не нужно думать о join’ах вручную
Как работают новые миграции и почему это важно
До версии 2.0 миграции в Drizzle хранились просто по timestamp’ам. Если два разработчика создавали миграцию одновременно в разных ветках, то при мерже конфликт был гарантирован. И не просто конфликт - нужно было вручную разбираться, какой SQL код останется, потому что оба timestamp’а близки друг к другу.
Теперь система работает иначе. Каждая миграция имеет явный номер версии (например, 0001, 0002) и хранится в отдельной папке со своим названием. При мерже веток разные миграции не конфликтуют - они просто добавляются в конец очереди. Drizzle знает, какие миграции уже были применены в БД (через таблицу миграций с новым name column), и применяет только те, которых нет.
Особенности нового подхода:
- Automatic upgrade - старые миграции с timestamp’ами автоматически конвертируются в новый формат
- Smart backfilling - система понимает, какие миграции уже в БД, и не повторяет их
- Commutativity checks -
drizzle-kit check проверяет, не зависит ли порядок миграций от их очерёдности
- Folder-based naming - папки с миграциями читаются по имени, не по дате создания
- No more _journal.json conflicts - история миграций теперь хранится в самой таблице
Это решает проблему, которую описывали разработчики: когда в ветке A создана миграция 0001_new_from_A, а в ветке B создана 0001_new_from_B, то при мерже был конфликт. Теперь система скажет, что это разные миграции (по содержимому папок) и применит обе в правильном порядке.
Генерация миграций - автоматизм, которым стоит пользоваться
Это вообще киллер-фича. Ты изменил схему в TypeScript - добавил колонку, создал индекс, добавил constraint - и запускаешь drizzle-kit generate. Drizzle сама сравнивает текущую схему с последней миграцией и создаёт новый файл с правильным SQL.
Типичный workflow выглядит так:
- Редактируешь файл
schema.ts - добавляешь новую таблицу или меняешь существующую
- Запускаешь
npx drizzle-kit generate
- Drizzle создаёт новый файл миграции в папке
drizzle/
- Проверяешь сгенерированный SQL - обычно он идеален, но лучше перепроверить
- Применяешь миграцию через
drizzle-kit push или npm run migrate
Генерация работает для всех поддерживаемых диалектов - PostgreSQL, MySQL, SQLite. При этом SQL синтаксис учитывает особенности каждой БД.
Преимущества автоматической генерации:
- Экономия времени - не нужно писать SQL вручную
- Меньше ошибок - Drizzle знает синтаксис лучше, чем человек
- Консистентность - все миграции генерируются по одним и тем же правилам
- История в контроле версий - каждая миграция - это отдельный файл в гите
- Возможность ревью - коллега может посмотреть сгенерированный SQL и указать на проблемы
Синхронизация со схемой - хук после миграции
Дрizzle понимает, что в реальных проектах может быть ситуация, когда ты меняешь БД напрямую (через миграции другого инструмента, например) или хочешь синхронизировать существующую БД. Для этого есть drizzle-kit pull - команда, которая генерирует TypeScript схему на основе текущей структуры БД.
Чтобы автоматизировать это, можно добавить хук в gel.toml (или в конфиг БД):
[hooks]
after_migration_apply = [
"npx drizzle-kit pull"
]
Тогда после каждой применённой миграции TypeScript схема будет автоматически обновляться. Это полезно, когда работаешь с коллегами - каждый делает pull request с изменением схемы и миграцией, и после merge’а все имеют свежую версию.
Практические примеры использования в Next.js проекте
Вот как выглядит реальная работа с Drizzle в Next.js приложении. Допустим, у тебя есть блоговая система с постами и комментариями.
В lib/schema.ts ты описываешь таблицы:
import { pgTable, serial, text, timestamp } from 'drizzle-orm/pg-core';
export const posts = pgTable('posts', {
id: serial('id').primaryKey(),
title: text('title').notNull(),
content: text('content').notNull(),
createdAt: timestamp('created_at').defaultNow(),
});
export const comments = pgTable('comments', {
id: serial('id').primaryKey(),
postId: serial('post_id').references(() => posts.id),
text: text('text').notNull(),
createdAt: timestamp('created_at').defaultNow(),
});
Запускаешь drizzle-kit generate, и она создаёт файл миграции с CREATE TABLE для обеих таблиц. Потом применяешь миграцию.
В API route’е для получения поста с комментариями (app/api/posts/[id]/route.ts
import { db } from '@/lib/db';
import { posts } from '@/lib/schema';
import { eq } from 'drizzle-orm';
export async function GET(request, { params }) {
const post = await db.query.posts.findFirst({
where: eq(posts.id, parseInt(params.id)),
with: { comments: true },
});
return Response.json(post);
}
Типы для post и post.comments будут инфер’иться автоматически. TypeScript знает все поля, и IDE подскажет автокомплит.
В Server Component для отображения поста (app/posts/[id]/page.tsx
import { db } from '@/lib/db';
import { posts } from '@/lib/schema';
import { eq } from 'drizzle-orm';
export default async function PostPage({ params }) {
const post = await db.query.posts.findFirst({
where: eq(posts.id, parseInt(params.id)),
with: { comments: true },
});
if (!post) return <div>Not found</div>;
return (
<article>
<h1>{post.title}</h1>
<p>{post.content}</p>
<section>
{post.comments.map((comment) => (
<div key={comment.id}>{comment.text}</div>
))}
</section>
</article>
);
}
Все запросы типизированы, всё работает без runtime ошибок, и код выглядит чистым.
Что можно делать в Drizzle:
- SELECT с relations - автоматический join через
with
- UPDATE с условиями -
set, where, returning
- DELETE с проверками -
where гарантирует, что удалишь правильное
- INSERT RETURNING - вставляешь запись и сразу получаешь её обратно с ID
- GROUP BY, ORDER BY, LIMIT - все как в обычном SQL, но с типизацией
- Transactions - безопасные мультишаговые операции
- Raw SQL when needed - если нужна специфичная оптимизация
Что дальше развивать и на что обращать внимание
Drizzle в 2026 году вышла на уровень, где это серьёзный инструмент для production. Но есть моменты, на которые стоит обратить внимание при переходе на новую версию. Например, если ты работаешь с ESM-модулями в TypeScript проекте, нужно убедиться, что imports в schema файле правильные - иногда drizzle-kit может не распознать расширение .js в импортах, хотя для ESM это необходимо.
Также имеет смысл полностью разобраться с двумя подходами к миграциям - database first и codebase first - и выбрать тот, который подходит именно под архитектуру твоего проекта. Для стартапов и средних проектов codebase first обычно удобнее, потому что всё под версионным контролем. Для больших систем с легаси кодом иногда database first рациональнее.