<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Drizzle ORM v2: типобезопасные миграции для Next.js с RSC]]></title><description><![CDATA[<p dir="auto"><img src="/assets/uploads/files/22/ec/0d/1774451885203-generated_1774451859265.webp" alt="Обложка: Drizzle ORM v2: типобезопасные миграции для Next.js с RSC и 180% ростом в 2026" class=" img-fluid img-markdown" /></p>
<p dir="auto">Drizzle ORM давно перестал быть просто очередной либой для работы с БД. Это инструмент, который делает миграции не просто безопасными, а удобными - особенно когда работаешь в команде и нужна синхронизация схемы между разработчиками. В 2026 году Drizzle полностью переосмыслил подход к миграциям, добавив versioning и комуtativity checks, что решает одну из самых болючих проблем - конфликты в <code>_journal.json</code> при параллельной разработке.</p>
<p dir="auto">Если ты работаешь на Next.js с Server Components, то интеграция Drizzle становится вообще кайфом: ты объявляешь схему в TypeScript, генерируешь миграции одной командой, и всё работает как надо. Плюс типизация на 100% - компилятор не даст тебе накосячить с запросом.</p>
<h2>Почему миграции в Drizzle - это вообще норм</h2>
<p dir="auto">День за днём разработчики сталкивались с одной и той же проблемой: несколько веток в гите, каждая со своей миграцией, и при мерже - boom, конфликт в <code>_journal.json</code>. Версии разные, SQL разный, а разрешить конфликт можно только вручную. Это просто боль, особенно в больших командах.</p>
<p dir="auto">Drizzle v2 решил эту проблему через <strong>migration table versioning</strong> и <strong>matching by folder name instead of timestamps</strong>. Теперь каждая миграция имеет явный номер версии и название папки, что убирает рассинхронизацию. Плюс появился <strong>drizzle-kit check</strong> - команда, которая проверяет коммутативность миграций и предупредит, если что-то не так.</p>
<p dir="auto">Возможности drizzle-kit сейчас покрывают оба подхода к миграциям:</p>
<ul>
<li><strong>Database first</strong> - схема в БД как источник истины, ты её pull’ишь в код</li>
<li><strong>Codebase first</strong> - схема в TypeScript как источник истины, генерируешь SQL миграции</li>
</ul>
<p dir="auto">Выбор зависит от того, как устроена архитектура проекта и как работает команда.</p>
<h2>Интеграция с Next.js и Server Components - это то, что нужно</h2>
<p dir="auto">Когда ты пишешь на Next.js 15+ с полноценными Server Components, то Drizzle встраивается просто идеально. В RSC ты можешь прямо в компоненте async/await вызвать запрос к БД - никаких API слоёв, никаких лишних рендеров.</p>
<p dir="auto">Вот как это выглядит в реальности. Ты создаёшь файл схемы в TypeScript - там таблицы, связи, индексы. Потом запускаешь <code>drizzle-kit generate</code>, и она сама создаст SQL миграции с правильными изменениями. После этого применяешь миграции через <code>drizzle-kit push</code> или используешь внешний инструмент - кому как удобнее.</p>
<p dir="auto">В Server Component потом просто импортируешь db-инстанс и пишешь запрос:</p>
<pre><code class="language-javascript">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 (
    &lt;div&gt;
      {books.map((book) =&gt; (
        &lt;BookCard key={book.id} book={book} /&gt;
      ))}
    &lt;/div&gt;
  );
}
</code></pre>
<p dir="auto">Всё типизировано - TypeScript сразу подскажет, если ты обратишься к несуществующему полю. Нет runtime ошибок из-за опечаток в названиях колонок. Вот это и есть настоящее качество разработки.</p>
<p dir="auto">Процесс интеграции занимает минут пять:</p>
<ul>
<li>Устанавливаешь <code>drizzle-orm</code> и <code>drizzle-kit</code> через npm</li>
<li>Создаёшь <code>drizzle.config.ts</code> с указанием диалекта и credentials</li>
<li>Пишешь схему в отдельном файле</li>
<li>Генерируешь и применяешь миграции</li>
<li>Используешь db в компонентах и API routes</li>
</ul>
<h2>Типизация на уровне БД - не фантазия, а реальность</h2>
<p dir="auto">Типизация в Drizzle работает на нескольких уровнях одновременно. Ты объявляешь таблицу с точными типами полей, и эти типы автоматически пробрасываются во всю цепочку - от запроса до результата. Если ты добавил новое поле в таблицу, TypeScript сразу будет ругаться во всех местах, где ты старую версию используешь.</p>
<p dir="auto">Это предотвращает целый класс багов, которые обычно обнаруживаются только в production. Например, ты изменил тип поля с string на number - компилятор не даст запустить код, пока ты везде не обновишь обработчик.</p>
<p dir="auto">Основные плюсы типизации в Drizzle:</p>
<ul>
<li><strong>Автокомплит в IDE</strong> - вводишь <code>db.query.users.</code> и видишь все доступные методы</li>
<li><strong>Валидация на этапе разработки</strong> - не нужно ждать runtime ошибок</li>
<li><strong>Рефакторинг без страха</strong> - переименуешь поле в схеме, и IDE покажет все места, где это повлияет</li>
<li><strong>Генерация типов из схемы</strong> - не нужно дублировать описание структуры в TypeScript</li>
<li><strong>Type-safe relations</strong> - связи между таблицами уже в типах, не нужно думать о join’ах вручную</li>
</ul>
<h2>Как работают новые миграции и почему это важно</h2>
<p dir="auto">До версии 2.0 миграции в Drizzle хранились просто по timestamp’ам. Если два разработчика создавали миграцию одновременно в разных ветках, то при мерже конфликт был гарантирован. И не просто конфликт - нужно было вручную разбираться, какой SQL код останется, потому что оба timestamp’а близки друг к другу.</p>
<p dir="auto">Теперь система работает иначе. Каждая миграция имеет явный номер версии (например, <code>0001</code>, <code>0002</code>) и хранится в отдельной папке со своим названием. При мерже веток разные миграции не конфликтуют - они просто добавляются в конец очереди. Drizzle знает, какие миграции уже были применены в БД (через таблицу миграций с новым <code>name</code> column), и применяет только те, которых нет.</p>
<p dir="auto">Особенности нового подхода:</p>
<ul>
<li><strong>Automatic upgrade</strong> - старые миграции с timestamp’ами автоматически конвертируются в новый формат</li>
<li><strong>Smart backfilling</strong> - система понимает, какие миграции уже в БД, и не повторяет их</li>
<li><strong>Commutativity checks</strong> - <code>drizzle-kit check</code> проверяет, не зависит ли порядок миграций от их очерёдности</li>
<li><strong>Folder-based naming</strong> - папки с миграциями читаются по имени, не по дате создания</li>
<li><strong>No more _journal.json conflicts</strong> - история миграций теперь хранится в самой таблице</li>
</ul>
<p dir="auto">Это решает проблему, которую описывали разработчики: когда в ветке A создана миграция <code>0001_new_from_A</code>, а в ветке B создана <code>0001_new_from_B</code>, то при мерже был конфликт. Теперь система скажет, что это разные миграции (по содержимому папок) и применит обе в правильном порядке.</p>
<h2>Генерация миграций - автоматизм, которым стоит пользоваться</h2>
<p dir="auto">Это вообще киллер-фича. Ты изменил схему в TypeScript - добавил колонку, создал индекс, добавил constraint - и запускаешь <code>drizzle-kit generate</code>. Drizzle сама сравнивает текущую схему с последней миграцией и создаёт новый файл с правильным SQL.</p>
<p dir="auto">Типичный workflow выглядит так:</p>
<ol>
<li>Редактируешь файл <code>schema.ts</code> - добавляешь новую таблицу или меняешь существующую</li>
<li>Запускаешь <code>npx drizzle-kit generate</code></li>
<li>Drizzle создаёт новый файл миграции в папке <code>drizzle/</code></li>
<li>Проверяешь сгенерированный SQL - обычно он идеален, но лучше перепроверить</li>
<li>Применяешь миграцию через <code>drizzle-kit push</code> или <code>npm run migrate</code></li>
</ol>
<p dir="auto">Генерация работает для всех поддерживаемых диалектов - PostgreSQL, MySQL, SQLite. При этом SQL синтаксис учитывает особенности каждой БД.</p>
<p dir="auto">Преимущества автоматической генерации:</p>
<ul>
<li><strong>Экономия времени</strong> - не нужно писать SQL вручную</li>
<li><strong>Меньше ошибок</strong> - Drizzle знает синтаксис лучше, чем человек</li>
<li><strong>Консистентность</strong> - все миграции генерируются по одним и тем же правилам</li>
<li><strong>История в контроле версий</strong> - каждая миграция - это отдельный файл в гите</li>
<li><strong>Возможность ревью</strong> - коллега может посмотреть сгенерированный SQL и указать на проблемы</li>
</ul>
<h2>Синхронизация со схемой - хук после миграции</h2>
<p dir="auto">Дрizzle понимает, что в реальных проектах может быть ситуация, когда ты меняешь БД напрямую (через миграции другого инструмента, например) или хочешь синхронизировать существующую БД. Для этого есть <code>drizzle-kit pull</code> - команда, которая генерирует TypeScript схему на основе текущей структуры БД.</p>
<p dir="auto">Чтобы автоматизировать это, можно добавить хук в <code>gel.toml</code> (или в конфиг БД):</p>
<pre><code>[hooks]
after_migration_apply = [
  "npx drizzle-kit pull"
]
</code></pre>
<p dir="auto">Тогда после каждой применённой миграции TypeScript схема будет автоматически обновляться. Это полезно, когда работаешь с коллегами - каждый делает pull request с изменением схемы и миграцией, и после merge’а все имеют свежую версию.</p>
<h2>Практические примеры использования в Next.js проекте</h2>
<p dir="auto">Вот как выглядит реальная работа с Drizzle в Next.js приложении. Допустим, у тебя есть блоговая система с постами и комментариями.</p>
<p dir="auto">В <code>lib/schema.ts</code> ты описываешь таблицы:</p>
<pre><code class="language-javascript">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(() =&gt; posts.id),
  text: text('text').notNull(),
  createdAt: timestamp('created_at').defaultNow(),
});
</code></pre>
<p dir="auto">Запускаешь <code>drizzle-kit generate</code>, и она создаёт файл миграции с CREATE TABLE для обеих таблиц. Потом применяешь миграцию.</p>
<p dir="auto">В API route’е для получения поста с комментариями (<code>app/api/posts/[id]/route.ts</code><img src="https://forum.exlends.ru/assets/plugins/nodebb-plugin-emoji/emoji/android/1f61e.png?v=a1e94250dac" class="not-responsive emoji emoji-android emoji--disappointed" style="height:23px;width:auto;vertical-align:middle" title="):" alt="😞" /></p>
<pre><code class="language-javascript">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);
}
</code></pre>
<p dir="auto">Типы для <code>post</code> и <code>post.comments</code> будут инфер’иться автоматически. TypeScript знает все поля, и IDE подскажет автокомплит.</p>
<p dir="auto">В Server Component для отображения поста (<code>app/posts/[id]/page.tsx</code><img src="https://forum.exlends.ru/assets/plugins/nodebb-plugin-emoji/emoji/android/1f61e.png?v=a1e94250dac" class="not-responsive emoji emoji-android emoji--disappointed" style="height:23px;width:auto;vertical-align:middle" title="):" alt="😞" /></p>
<pre><code class="language-javascript">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 &lt;div&gt;Not found&lt;/div&gt;;
  
  return (
    &lt;article&gt;
      &lt;h1&gt;{post.title}&lt;/h1&gt;
      &lt;p&gt;{post.content}&lt;/p&gt;
      &lt;section&gt;
        {post.comments.map((comment) =&gt; (
          &lt;div key={comment.id}&gt;{comment.text}&lt;/div&gt;
        ))}
      &lt;/section&gt;
    &lt;/article&gt;
  );
}
</code></pre>
<p dir="auto">Все запросы типизированы, всё работает без runtime ошибок, и код выглядит чистым.</p>
<p dir="auto">Что можно делать в Drizzle:</p>
<ul>
<li><strong>SELECT с relations</strong> - автоматический join через <code>with</code></li>
<li><strong>UPDATE с условиями</strong> - <code>set</code>, <code>where</code>, <code>returning</code></li>
<li><strong>DELETE с проверками</strong> - <code>where</code> гарантирует, что удалишь правильное</li>
<li><strong>INSERT RETURNING</strong> - вставляешь запись и сразу получаешь её обратно с ID</li>
<li><strong>GROUP BY, ORDER BY, LIMIT</strong> - все как в обычном SQL, но с типизацией</li>
<li><strong>Transactions</strong> - безопасные мультишаговые операции</li>
<li><strong>Raw SQL when needed</strong> - если нужна специфичная оптимизация</li>
</ul>
<h2>Что дальше развивать и на что обращать внимание</h2>
<p dir="auto">Drizzle в 2026 году вышла на уровень, где это серьёзный инструмент для production. Но есть моменты, на которые стоит обратить внимание при переходе на новую версию. Например, если ты работаешь с ESM-модулями в TypeScript проекте, нужно убедиться, что imports в schema файле правильные - иногда drizzle-kit может не распознать расширение <code>.js</code> в импортах, хотя для ESM это необходимо.</p>
<p dir="auto">Также имеет смысл полностью разобраться с двумя подходами к миграциям - database first и codebase first - и выбрать тот, который подходит именно под архитектуру твоего проекта. Для стартапов и средних проектов codebase first обычно удобнее, потому что всё под версионным контролем. Для больших систем с легаси кодом иногда database first рациональнее.</p>
]]></description><link>https://forum.exlends.ru/topic/1945/drizzle-orm-v2-tipobezopasnye-migracii-dlya-next.js-s-rsc</link><generator>RSS for Node</generator><lastBuildDate>Sat, 17 Oct 2026 03:52:51 GMT</lastBuildDate><atom:link href="https://forum.exlends.ru/topic/1945.rss" rel="self" type="application/rss+xml"/><pubDate>Wed, 25 Mar 2026 15:18:05 GMT</pubDate><ttl>60</ttl></channel></rss>