Drizzle ORM v2: типобезопасные миграции для Next.js с RSC
-

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 знает, какие миграции уже были применены в БД (через таблицу миграций с новымnamecolumn), и применяет только те, которых нет.Особенности нового подхода:
- 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 рациональнее.
Здравствуйте! Похоже, вас заинтересовала эта беседа, но у вас ещё нет аккаунта.
Надоело каждый раз пролистывать одни и те же посты? Зарегистрировав аккаунт, вы всегда будете возвращаться на ту же страницу, где были раньше, и сможете выбирать, получать ли уведомления о новых ответах (по электронной почте или в виде push-уведомлений). Вы также сможете сохранять закладки и ставить лайки постам, чтобы выразить свою благодарность другим участникам сообщества.
С вашими комментариями этот пост мог бы стать ещё лучше 💗
Зарегистрироваться Войти© 2024 - 2026 ExLends, Inc. Все права защищены.