Prisma 7 ORM для работы с базой данных
Контекст
Платформа нуждается в ORM-решении для работы с базой данных в backend-приложениях на Nuxt 4 Server. Необходимо выбрать подход, который:
- Обеспечивает type-safety для всех операций с базой данных
- Интегрируется с TypeScript и auto-imports в Nuxt
- Поддерживает миграции и версионирование схемы
- Генерирует типы, которые можно использовать на frontend
Решение
Использовать Prisma 7 в качестве ORM с генерацией клиента в локальную директорию и singleton-паттерном для development.
Ключевые изменения в Prisma 7
Prisma 7 вводит breaking changes по сравнению с предыдущими версиями:
| Было (Prisma 5/6) | Стало (Prisma 7) |
|---|---|
provider = "prisma-client-js" | provider = "prisma-client" |
import { PrismaClient } from '@prisma/client' | import { PrismaClient } from './generated/prisma' |
| Глобальный node_modules | Локальная директория (обязательно) |
url = env("DATABASE_URL") в schema | url в prisma.config.ts |
| Встроенный Rust engine | Driver adapters (опционально) |
Конфигурация: prisma.config.ts
В Prisma 7 конфигурация datasource url вынесена в отдельный файл:
// prisma.config.ts
import 'dotenv/config'
import { defineConfig, env } from 'prisma/config'
export default defineConfig({
schema: 'prisma/schema.prisma',
migrations: {
path: 'prisma/migrations'
},
datasource: {
url: env('DATABASE_URL')
}
})
Конфигурация схемы
// prisma/schema.prisma
generator client {
provider = "prisma-client"
output = "../server/generated/prisma"
}
datasource db {
provider = "postgresql"
// url теперь в prisma.config.ts, здесь только provider
}
model User {
id String @id @default(cuid())
email String @unique
name String?
role Role @default(USER)
posts Post[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model Post {
id String @id @default(cuid())
title String
content String?
published Boolean @default(false)
author User @relation(fields: [authorId], references: [id])
authorId String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
enum Role {
USER
ADMIN
}
Driver Adapters (engineType = "client")
Prisma 7 поддерживает driver adapters — режим без Rust engine, где Prisma работает напрямую с JS-драйвером базы данных. Это опционально, но полезно для edge runtime и уменьшения размера бандла.
Шаг 1: Настройка generator
// prisma/schema.prisma
generator client {
provider = "prisma-client"
output = "../server/generated/prisma"
engineType = "client" // Включает режим driver adapters
}
Шаг 2: Установка adapter
# Для SQLite
pnpm add @prisma/adapter-better-sqlite3 better-sqlite3
# Для PostgreSQL
pnpm add @prisma/adapter-pg pg
# Для MySQL
pnpm add @prisma/adapter-mysql2 mysql2
Шаг 3: Инициализация с adapter
// server/utils/prisma.ts (с driver adapter)
import { PrismaClient } from '../generated/prisma'
import { PrismaBetterSqlite3 } from '@prisma/adapter-better-sqlite3'
import path from 'node:path'
const globalForPrisma = globalThis as unknown as {
prisma: PrismaClient | undefined
}
function createPrismaClient() {
// SQLite пример
const dbPath = path.resolve(process.cwd(), 'prisma/dev.db')
const adapter = new PrismaBetterSqlite3({ url: dbPath })
return new PrismaClient({ adapter })
}
export const prisma = globalForPrisma.prisma ?? createPrismaClient()
if (process.env.NODE_ENV !== 'production') {
globalForPrisma.prisma = prisma
}
Когда использовать driver adapters:
| Сценарий | Рекомендация |
|---|---|
| Стандартный Node.js сервер | Без adapter (проще) |
| Edge runtime (Cloudflare, Vercel Edge) | С adapter (обязательно) |
| Минимизация бандла | С adapter |
| Максимальная производительность | Без adapter (Rust engine быстрее) |
Singleton Pattern для Development
Без singleton-паттерна каждый hot reload создаёт новое подключение к БД:
// server/utils/prisma.ts
import { PrismaClient } from '../generated/prisma'
// Prevent multiple instances during hot reload
const globalForPrisma = globalThis as unknown as {
prisma: PrismaClient | undefined
}
export const prisma = globalForPrisma.prisma ?? new PrismaClient()
if (process.env.NODE_ENV !== 'production') {
globalForPrisma.prisma = prisma
}
Использование в API-эндпоинтах
Благодаря auto-import из server/utils/, Prisma доступна во всех эндпоинтах:
// server/api/users.get.ts
export default defineEventHandler(async (event) => {
// prisma доступна без импорта
const users = await prisma.user.findMany({
select: {
id: true,
email: true,
name: true,
role: true,
createdAt: true
}
})
return users
})
// server/api/users.post.ts
export default defineEventHandler(async (event) => {
const body = await readBody(event)
const user = await prisma.user.create({
data: {
email: body.email,
name: body.name,
role: body.role
}
})
return user
})
// server/api/users/[id].get.ts
export default defineEventHandler(async (event) => {
const id = getRouterParam(event, 'id')
const user = await prisma.user.findUnique({
where: { id },
include: {
posts: {
where: { published: true },
orderBy: { createdAt: 'desc' }
}
}
})
if (!user) {
throw createError({
statusCode: 404,
statusMessage: 'User not found'
})
}
return user
})
Обоснование
Рассмотренные варианты
| Вариант | Плюсы | Минусы |
|---|---|---|
| Prisma 7 | Type-safety, миграции, Prisma Studio, активное развитие | Новый синтаксис генератора |
| Drizzle ORM | Лёгкий, SQL-like синтаксис | Менее зрелый, меньше документации |
| TypeORM | Декораторы, ActiveRecord | Сложная настройка, хуже типизация |
| Kysely | Query builder, type-safe | Нет миграций, ручное управление схемой |
| Выбран: Prisma 7 | Полный набор инструментов | Требует изучения нового синтаксиса |
Почему Prisma 7
| Критерий | Обоснование |
|---|---|
| Type-safety | Полная типизация всех запросов и результатов |
| Auto-completion | IDE подсказки для полей, отношений, фильтров |
| Миграции | Версионирование схемы с prisma migrate |
| Prisma Studio | Визуальный редактор данных |
| Экосистема | Документация, сообщество, интеграции |
| Генерация типов | Типы для переиспользования на frontend |
Структура проекта
app-name/
├── prisma/
│ ├── schema.prisma ← Схема базы данных
│ ├── migrations/ ← Файлы миграций
│ │ ├── 20250128_init/
│ │ │ └── migration.sql
│ │ └── migration_lock.toml
│ └── seed.ts ← Наполнение тестовыми данными
├── server/
│ ├── generated/
│ │ └── prisma/ ← Сгенерированный Prisma Client
│ │ ├── index.ts
│ │ └── ... (generated files)
│ ├── utils/
│ │ └── prisma.ts ← Singleton instance
│ └── api/
│ └── ...
└── package.json
Команды разработки
Генерация клиента
# Сгенерировать Prisma Client после изменения схемы
pnpm prisma generate
Миграции
# Создать миграцию (development)
pnpm prisma migrate dev --name add_posts_table
# Применить миграции (production)
pnpm prisma migrate deploy
# Сбросить базу и применить миграции заново
pnpm prisma migrate reset
Работа с данными
# Открыть Prisma Studio
pnpm prisma studio
# Запустить seed
pnpm prisma db seed
Настройка seed
// prisma/seed.ts
import { PrismaClient } from '../server/generated/prisma'
const prisma = new PrismaClient()
async function main() {
// Создать тестовых пользователей
const admin = await prisma.user.upsert({
where: { email: 'admin@example.com' },
update: {},
create: {
email: 'admin@example.com',
name: 'Admin User',
role: 'ADMIN',
posts: {
create: [
{
title: 'Welcome Post',
content: 'Welcome to the platform!',
published: true
}
]
}
}
})
console.log({ admin })
}
main()
.catch(console.error)
.finally(() => prisma.$disconnect())
// package.json
{
"prisma": {
"seed": "tsx prisma/seed.ts"
}
}
Генерация типов для Frontend
Prisma генерирует типы, которые можно экспортировать для frontend:
// server/types/models.ts
// Re-export Prisma types for frontend usage
export type { User, Post, Role } from '../generated/prisma'
// Custom types for API responses
export type UserWithPosts = Prisma.UserGetPayload<{
include: { posts: true }
}>
export type PublicUser = Pick<User, 'id' | 'name' | 'email'>
// app/composables/useApi.ts
import type { User, Post, PublicUser } from '~/server/types/models'
export function useUsers() {
return useFetch<PublicUser[]>('/api/users')
}
export function useUser(id: string) {
return useFetch<User>(`/api/users/${id}`)
}
Gotchas (Важные нюансы)
1. Обязательный output путь в Prisma 7
// ПРАВИЛЬНО (Prisma 7)
generator client {
provider = "prisma-client"
output = "../server/generated/prisma" // ОБЯЗАТЕЛЬНО!
}
// НЕПРАВИЛЬНО — приведёт к ошибке
generator client {
provider = "prisma-client"
// output не указан — ошибка!
}
2. Импорт из локальной директории
// ПРАВИЛЬНО (Prisma 7)
import { PrismaClient } from '../generated/prisma'
// НЕПРАВИЛЬНО — старый синтаксис
import { PrismaClient } from '@prisma/client'
3. Добавить generated в .gitignore
# .gitignore
server/generated/
4. Regenerate после изменения схемы
После любого изменения schema.prisma необходимо:
# 1. Сгенерировать новый клиент
pnpm prisma generate
# 2. Если изменилась структура — создать миграцию
pnpm prisma migrate dev --name description
5. Connection pooling для production
// server/utils/prisma.ts
import { PrismaClient } from '../generated/prisma'
const globalForPrisma = globalThis as unknown as {
prisma: PrismaClient | undefined
}
export const prisma = globalForPrisma.prisma ?? new PrismaClient({
// Логирование в development
log: process.env.NODE_ENV === 'development'
? ['query', 'error', 'warn']
: ['error'],
})
if (process.env.NODE_ENV !== 'production') {
globalForPrisma.prisma = prisma
}
Environment Variables
# .env
# PostgreSQL
DATABASE_URL="postgresql://user:password@localhost:5432/dbname?schema=public"
# MySQL
# DATABASE_URL="mysql://user:password@localhost:3306/dbname"
# SQLite (для разработки)
# DATABASE_URL="file:./dev.db"
Последствия
Положительные
- Полная type-safety для всех операций с БД
- Auto-completion в IDE ускоряет разработку
- Миграции версионируют изменения схемы
- Prisma Studio упрощает отладку данных
- Генерируемые типы используются на frontend
- Singleton-паттерн предотвращает утечки соединений
Отрицательные
- Breaking changes по сравнению с Prisma 5/6
- Требуется регенерация клиента после изменений схемы
- Сгенерированный код увеличивает размер репозитория (если не в .gitignore)
- Сложные raw SQL запросы требуют отдельного подхода
Связанные решения
- ADR-012: Nuxt 4 Server Backend — backend framework
- ADR-013: Socket.io Real-time — real-time коммуникации
- Standard: Prisma Schema Conventions — конвенции схемы
- Standard: Type Generation — генерация типов