Введение в Prisma: современный ORM для TypeScript
Prisma — это современный ORM (Object-Relational Mapping) для TypeScript и Node.js, который кардинально меняет подход к работе с базами данных. В отличие от традиционных ORM, таких как Sequelize или TypeORM, Prisma предлагает декларативную модель данных, автоматическую генерацию типов и интуитивно понятный API. Библиотека поддерживает PostgreSQL, MySQL, SQLite, SQL Server, MongoDB и CockroachDB.
Основная цель Prisma — сделать работу с базами данных безопасной, производительной и удобной. Благодаря интеграции с TypeScript, вы получаете автодополнение и проверку типов на уровне компиляции, что значительно снижает количество runtime-ошибок. Prisma состоит из трех ключевых компонентов:
- Prisma Client — автоматически сгенерированный клиент для работы с БД
- Prisma Migrate — система управления миграциями
- Prisma Studio — GUI для просмотра и редактирования данных
Установка Prisma
Установка Prisma в проект на TypeScript выполняется через npm. Для начала создайте новый проект:
mkdir my-prisma-project
cd my-prisma-project
npm init -y
Установите Prisma CLI и Prisma Client:
npm install prisma @prisma/client --save
Инициализируйте Prisma в проекте:
npx prisma init
Эта команда создаст директорию prisma с файлом schema.prisma и файл .env для переменных окружения.
Быстрый старт: пример кода
Рассмотрим создание простого приложения для управления пользователями. Сначала определим модель в prisma/schema.prisma:
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "sqlite"
url = env("DATABASE_URL")
}
model User {
id Int @id @default(autoincrement())
email String @unique
name String?
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
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
Выполните миграцию для создания таблиц в SQLite:
npx prisma migrate dev --name init
Теперь создадим файл index.ts с примером использования:
import { PrismaClient } from '@prisma/client'
const prisma = new PrismaClient()
async function main() {
// Создание пользователя
const user = await prisma.user.create({
data: {
email: 'alice@example.com',
name: 'Alice',
posts: {
create: { title: 'Hello World' }
}
}
})
console.log('Created user:', user)
// Получение всех пользователей с их постами
const users = await prisma.user.findMany({
include: { posts: true }
})
console.log('All users:', JSON.stringify(users, null, 2))
}
main()
.catch(e => console.error(e))
.finally(async () => await prisma.$disconnect())
Запустите код:
npx ts-node index.ts
Основные методы Prisma Client
1. findUnique
Что делает: Находит единственную запись по уникальному идентификатору (id, email и т.д.).
Параметры:
where— объект с условиями поиска (обязательно)include— (опционально) для включения связанных моделейselect— (опционально) для выбора конкретных полей
Пример:
const user = await prisma.user.findUnique({
where: { email: 'alice@example.com' },
include: { posts: true }
})
2. findFirst
Что делает: Находит первую запись, соответствующую условиям. Полезно, когда нужно найти запись по не уникальному полю.
Параметры:
where— объект с условиямиorderBy— сортировкаinclude,select— как в findUnique
Пример:
const firstPost = await prisma.post.findFirst({
where: { published: true },
orderBy: { createdAt: 'desc' }
})
3. findMany
Что делает: Возвращает массив записей, соответствующих условиям. Это основной метод для получения списка данных.
Параметры:
where— условия фильтрацииorderBy— сортировкаskip— количество пропускаемых записей (для пагинации)take— количество возвращаемых записейinclude,select
Пример:
const recentPosts = await prisma.post.findMany({
where: { published: true },
orderBy: { createdAt: 'desc' },
take: 10,
skip: 0,
include: { author: true }
})
4. create
Что делает: Создает новую запись в базе данных.
Параметры:
data— объект с данными для создания (обязательно)include— для включения связанных данных в результат
Пример:
const newUser = await prisma.user.create({
data: {
email: 'bob@example.com',
name: 'Bob',
posts: {
create: [
{ title: 'Post 1', content: 'Content 1' },
{ title: 'Post 2', content: 'Content 2' }
]
}
},
include: { posts: true }
})
5. update
Что делает: Обновляет существующую запись.
Параметры:
where— условие для поиска записиdata— объект с новыми данными
Пример:
const updatedUser = await prisma.user.update({
where: { email: 'alice@example.com' },
data: { name: 'Alice Johnson' }
})
6. upsert
Что делает: Комбинация create и update. Если запись существует — обновляет, если нет — создает новую.
Параметры:
where— условие для поискаupdate— данные для обновленияcreate— данные для создания
Пример:
const upsertedUser = await prisma.user.upsert({
where: { email: 'charlie@example.com' },
update: { name: 'Charlie Updated' },
create: { email: 'charlie@example.com', name: 'Charlie New' }
})
7. delete
Что делает: Удаляет запись из базы данных.
Параметры:
where— условие для поиска удаляемой записи
Пример:
const deletedUser = await prisma.user.delete({
where: { id: 1 }
})
8. deleteMany
Что делает: Удаляет несколько записей, соответствующих условию.
Параметры:
where— условие для фильтрации записей
Пример:
const deletedCount = await prisma.post.deleteMany({
where: { published: false }
})
console.log(`Deleted ${deletedCount.count} posts`)
9. count
Что делает: Возвращает количество записей, соответствующих условию.
Параметры:
where— (опционально) условие фильтрации
Пример:
const publishedPostsCount = await prisma.post.count({
where: { published: true }
})
console.log(`Published posts: ${publishedPostsCount}`)
10. aggregate
Что делает: Выполняет агрегатные функции (sum, avg, min, max, count) над полями.
Параметры:
where— условие фильтрации_count,_sum,_avg,_min,_max— объекты с полями для агрегации
Пример:
const stats = await prisma.post.aggregate({
_count: { id: true },
_avg: { id: true },
where: { published: true }
})
console.log(`Total posts: ${stats._count.id}, Avg ID: ${stats._avg.id}`)
Реальный практический пример: API для блога
Создадим полноценный REST API для блога с использованием Express и Prisma. Начнем с установки дополнительных зависимостей:
npm install express cors
npm install -D @types/express @types/cors ts-node-dev
Создадим файл src/index.ts:
import express from 'express'
import cors from 'cors'
import { PrismaClient } from '@prisma/client'
const app = express()
const prisma = new PrismaClient()
app.use(cors())
app.use(express.json())
// Получение всех постов с авторами
app.get('/posts', async (req, res) => {
try {
const posts = await prisma.post.findMany({
include: { author: { select: { name: true, email: true } } },
orderBy: { createdAt: 'desc' }
})
res.json(posts)
} catch (error) {
res.status(500).json({ error: 'Failed to fetch posts' })
}
})
// Создание поста
app.post('/posts', async (req, res) => {
const { title, content, authorEmail } = req.body
try {
const post = await prisma.post.create({
data: {
title,
content,
author: {
connect: { email: authorEmail }
}
},
include: { author: true }
})
res.status(201).json(post)
} catch (error) {
res.status(400).json({ error: 'Failed to create post' })
}
})
// Обновление поста
app.put('/posts/:id', async (req, res) => {
const { id } = req.params
const { title, content, published } = req.body
try {
const post = await prisma.post.update({
where: { id: Number(id) },
data: { title, content, published }
})
res.json(post)
} catch (error) {
res.status(400).json({ error: 'Failed to update post' })
}
})
// Удаление поста
app.delete('/posts/:id', async (req, res) => {
const { id } = req.params
try {
await prisma.post.delete({ where: { id: Number(id) } })
res.status(204).send()
} catch (error) {
res.status(400).json({ error: 'Failed to delete post' })
}
})
// Статистика блога
app.get('/stats', async (req, res) => {
try {
const [totalPosts, publishedPosts, usersCount] = await Promise.all([
prisma.post.count(),
prisma.post.count({ where: { published: true } }),
prisma.user.count()
])
res.json({
totalPosts,
publishedPosts,
draftPosts: totalPosts -