Настройка ORM Drizzle для веб-приложения

Наша компания занимается разработкой, поддержкой и обслуживанием сайтов любой сложности. От простых одностраничных сайтов до масштабных кластерных систем построенных на микро сервисах. Опыт разработчиков подтвержден сертификатами от вендоров.

Разработка и обслуживание любых видов сайтов:

Информационные сайты или веб-приложения
Сайты визитки, landing page, корпоративные сайты, онлайн каталоги, квиз, промо-сайты, блоги, новостные ресурсы, информационные порталы, форумы, агрегаторы
Сайты или веб-приложения электронной коммерции
Интернет-магазины, B2B-порталы, маркетплейсы, онлайн-обменники, кэшбэк-сайты, биржи, дропшиппинг-платформы, парсеры товаров
Веб-приложения для управления бизнес-процессами
CRM-системы, ERP-системы, корпоративные порталы, системы управления производством, парсеры информации
Сайты или веб-приложения электронных услуг
Доски объявлений, онлайн-школы, онлайн-кинотеатры, конструкторы сайтов, порталы предоставления электронных услуг, видеохостинги, тематические порталы

Это лишь некоторые из технических типов сайтов, с которыми мы работаем, и каждый из них может иметь свои специфические особенности и функциональность, а также быть адаптированным под конкретные потребности и цели клиента

Предлагаемые услуги
Показано 1 из 1 услугВсе 2065 услуг
Настройка ORM Drizzle для веб-приложения
Средняя
~1 рабочий день
Часто задаваемые вопросы

Наши компетенции:

Этапы разработки

Последние работы

  • image_website-b2b-advance_0.png
    Разработка сайта компании B2B ADVANCE
    1262
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1171
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    874
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1094
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    831
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Разработка веб-сайта для компании ФИКСПЕР
    851

Настройка ORM Drizzle для веб-приложения

Drizzle — TypeScript ORM, где схема базы данных описывается на TypeScript, а не в отдельном DSL. Нет кодогенерации — типы выводятся напрямую из кода схемы. Это даёт более прозрачный стек: нет промежуточного слоя между кодом и базой, SQL всегда предсказуем.

Установка

# PostgreSQL
npm install drizzle-orm postgres
npm install -D drizzle-kit @types/pg

# MySQL
npm install drizzle-orm mysql2
npm install -D drizzle-kit

# SQLite / Turso (libSQL)
npm install drizzle-orm @libsql/client

Схема

// db/schema.ts
import {
  pgTable, pgEnum, text, varchar, integer, decimal,
  boolean, timestamp, uuid, index, uniqueIndex, primaryKey
} from 'drizzle-orm/pg-core'
import { relations } from 'drizzle-orm'

export const roleEnum = pgEnum('role', ['user', 'moderator', 'admin'])

export const users = pgTable('users', {
  id:        uuid('id').primaryKey().defaultRandom(),
  email:     varchar('email', { length: 255 }).notNull().unique(),
  name:      varchar('name', { length: 255 }).notNull(),
  role:      roleEnum('role').notNull().default('user'),
  createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
  updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(),
}, (table) => ({
  emailIdx: uniqueIndex('users_email_idx').on(table.email),
}))

export const posts = pgTable('posts', {
  id:          uuid('id').primaryKey().defaultRandom(),
  title:       text('title').notNull(),
  content:     text('content'),
  published:   boolean('published').notNull().default(false),
  authorId:    uuid('author_id').notNull().references(() => users.id, { onDelete: 'cascade' }),
  viewCount:   integer('view_count').notNull().default(0),
  publishedAt: timestamp('published_at', { withTimezone: true }),
  createdAt:   timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
}, (table) => ({
  authorIdx: index('posts_author_idx').on(table.authorId),
  publishedIdx: index('posts_published_idx').on(table.published, table.createdAt),
}))

export const tags = pgTable('tags', {
  id:   uuid('id').primaryKey().defaultRandom(),
  name: varchar('name', { length: 100 }).notNull().unique(),
  slug: varchar('slug', { length: 100 }).notNull().unique(),
})

export const postsToTags = pgTable('posts_to_tags', {
  postId: uuid('post_id').notNull().references(() => posts.id, { onDelete: 'cascade' }),
  tagId:  uuid('tag_id').notNull().references(() => tags.id, { onDelete: 'cascade' }),
}, (table) => ({
  pk: primaryKey({ columns: [table.postId, table.tagId] })
}))

// Relations — только для query builder
export const usersRelations = relations(users, ({ many }) => ({
  posts: many(posts),
}))

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

Конфигурация миграций

// drizzle.config.ts
import type { Config } from 'drizzle-kit'

export default {
  schema: './db/schema.ts',
  out: './drizzle',
  driver: 'pg',
  dbCredentials: {
    connectionString: process.env.DATABASE_URL!
  },
  verbose: true,
  strict: true,
} satisfies Config
# Генерация SQL миграции
npx drizzle-kit generate:pg

# Применить миграции
npx drizzle-kit push:pg   # для dev
npx drizzle-kit migrate   # через миграционный файл

# Проверить текущий статус
npx drizzle-kit check:pg

Инициализация подключения

// db/index.ts
import { drizzle } from 'drizzle-orm/postgres-js'
import postgres from 'postgres'
import * as schema from './schema'

const connectionString = process.env.DATABASE_URL!

// Для миграций — один коннект
const migrationClient = postgres(connectionString, { max: 1 })

// Для приложения — пул
const queryClient = postgres(connectionString, {
  max: 20,
  idle_timeout: 30,
  connect_timeout: 10,
})

export const db = drizzle(queryClient, { schema, logger: process.env.NODE_ENV === 'development' })

Запросы

import { db } from '@/db'
import { users, posts, tags, postsToTags } from '@/db/schema'
import { eq, and, desc, ilike, sql, count, inArray } from 'drizzle-orm'

// Простой select
const user = await db.query.users.findFirst({
  where: eq(users.email, '[email protected]'),
  with: {
    posts: {
      where: eq(posts.published, true),
      limit: 5,
      orderBy: [desc(posts.createdAt)]
    }
  }
})

// Insert с возвратом
const [newPost] = await db
  .insert(posts)
  .values({ title, content, authorId: userId })
  .returning()

// Update
await db
  .update(posts)
  .set({ published: true, publishedAt: new Date() })
  .where(and(eq(posts.id, postId), eq(posts.authorId, userId)))

// Поиск с пагинацией
async function searchPosts(query: string, page: number, limit = 20) {
  const offset = (page - 1) * limit

  const [items, [{ total }]] = await Promise.all([
    db.select({
      id: posts.id,
      title: posts.title,
      createdAt: posts.createdAt,
      authorName: users.name
    })
    .from(posts)
    .innerJoin(users, eq(posts.authorId, users.id))
    .where(and(
      eq(posts.published, true),
      ilike(posts.title, `%${query}%`)
    ))
    .orderBy(desc(posts.createdAt))
    .limit(limit)
    .offset(offset),

    db.select({ total: count() })
    .from(posts)
    .where(and(eq(posts.published, true), ilike(posts.title, `%${query}%`)))
  ])

  return { items, total, pages: Math.ceil(total / limit) }
}

// Сырой SQL для сложных случаев
const stats = await db.execute(sql`
  SELECT
    date_trunc('day', created_at) AS day,
    count(*) AS post_count
  FROM posts
  WHERE published = true
    AND created_at >= now() - interval '30 days'
  GROUP BY 1
  ORDER BY 1
`)

Транзакции

const result = await db.transaction(async (tx) => {
  const [user] = await tx
    .insert(users)
    .values({ email, name })
    .returning()

  await tx.insert(profiles).values({ userId: user.id })

  return user
})

Сравнение с Prisma

Drizzle ближе к SQL: JOIN-запросы более явные, нет магии include. Это плюс, если важна предсказуемость генерируемого SQL и максимальная производительность. Prisma удобнее для команд, которым важна скорость написания CRUD без глубокого знания SQL. Drizzle хорошо работает с Edge Runtime (Cloudflare Workers, Vercel Edge) — Prisma там ограничена.

Сроки

Настройка Drizzle с нуля (схема, миграции, типизированный клиент): 1 день. Полная интеграция с репозиторием и тестами: 1–2 дня. Портирование с Prisma на Drizzle в существующем проекте: 2–4 дня.