ORM: Prisma обзор
Prisma — современный ORM (Object-Relational Mapper) для Node.js и TypeScript, использующий декларативную схему schema.prisma для определения моделей и генерирующий типобезопасный клиент для работы с БД.
Зачем нужно
Prisma устраняет необходимость писать SQL вручную и получать нетипизированные объекты. Она генерирует TypeScript-типы из схемы, что даёт автодополнение и type-checking прямо в IDE. В отличие от Sequelize, Prisma имеет отдельный инструмент миграций (prisma migrate) и интроспекцию существующей БД.
Где используется
- REST и GraphQL API на Node.js/TypeScript
- Проекты с PostgreSQL, MySQL, SQLite, MongoDB
- Команды, которым важна типобезопасность запросов
- В связке с NestJS, Express, Fastify
Основной контент
Установка
npm install @prisma/client
npm install --save-dev prisma
npx prisma init # создаёт prisma/schema.prisma и .env
Схема (prisma/schema.prisma)
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
generator client {
provider = "prisma-client-js"
}
model User {
id Int @id @default(autoincrement)
email String @unique
name String?
role Role @default(USER)
posts Post
createdAt DateTime @default(now)
updatedAt DateTime @updatedAt
}
model Post {
id Int @id @default(autoincrement)
title String
content String?
published Boolean @default(false)
author User @relation(fields: [authorId], references: [id])
authorId Int
}
enum Role {
USER
ADMIN
}
Миграции
# Создать и применить миграцию
npx prisma migrate dev --name init
# Применить в production (без создания новых)
npx prisma migrate deploy
# Обновить Prisma Client после изменения схемы
npx prisma generate
# Открыть Prisma Studio (GUI для БД)
npx prisma studio
Prisma Client — CRUD
// db/prisma.js — singleton
const { PrismaClient } = require('@prisma/client');
const prisma = new PrismaClient();
module.exports = prisma;
// --- Использование ---
const prisma = require('./db/prisma');
// Create
const user = await prisma.user.create({
data: { email: 'alice@example.com', name: 'Alice' }
});
// Read
const users = await prisma.user.findMany({
where: { role: 'ADMIN' },
orderBy: { createdAt: 'desc' },
take: 10,
skip: 20,
select: { id: true, email: true, name: true } // проекция
});
const user = await prisma.user.findUnique({ where: { email: 'alice@example.com' } });
const user = await prisma.user.findFirst({ where: { name: { contains: 'ali' } } });
// Update
const updated = await prisma.user.update({
where: { id: 1 },
data: { name: 'Alice Updated' }
});
// Delete
await prisma.user.delete({ where: { id: 1 } });
// Upsert
await prisma.user.upsert({
where: { email: 'bob@example.com' },
create: { email: 'bob@example.com', name: 'Bob' },
update: { name: 'Bob Updated' }
});
Связи и include
// Загрузить пользователя с его постами
const user = await prisma.user.findUnique({
where: { id: 1 },
include: {
posts: {
where: { published: true },
orderBy: { createdAt: 'desc' }
}
}
});
// Создать пост с привязкой к пользователю
await prisma.post.create({
data: {
title: 'Hello Prisma',
author: { connect: { id: 1 } }
}
});
Транзакции
// Атомарная операция
const [user, post] = await prisma.$transaction([
prisma.user.create({ data: { email: 'x@x.com' } }),
prisma.post.create({ data: { title: 'First', authorId: 1 } })
]);
// Интерактивная транзакция
await prisma.$transaction(async (tx) => {
const sender = await tx.user.update({ where: { id: 1 }, data: { balance: { decrement: 100 } } });
if (sender.balance < 0) throw new Error('Insufficient funds');
await tx.user.update({ where: { id: 2 }, data: { balance: { increment: 100 } } });
});
Частые ошибки
- Один PrismaClient на запрос — создавать singleton, иначе исчерпание соединений с БД
- Не вызывать
prisma.$disconnect— в скриптах и тестах нужно закрывать соединение явно - Забыть
npx prisma generate— после изменения схемы клиент не обновится автоматически findUniqueможет вернутьnull— всегда проверять результат или использоватьfindUniqueOrThrow