Налаштування 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) }
}

// Raw 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
})

Drizzle vs 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 дні.