Антон Ларичев

Введение
При разработке серверных приложений на TypeScript выбор инструментов для работы с базой данных критически важен. PostgreSQL — одна из самых надёжных реляционных СУБД, а Prisma — современный ORM, который делает взаимодействие с ней удобным и полностью типобезопасным.
В этой статье мы пройдём путь от установки до выполнения сложных запросов: настроим Prisma, опишем схему данных, выполним миграции и научимся читать, создавать и обновлять записи в PostgreSQL из TypeScript-кода.
Установка и инициализация проекта
Для начала создадим новый Node.js проект и установим необходимые зависимости.
mkdir prisma-pg-demo && cd prisma-pg-demo
npm init -y
npm install typescript ts-node @types/node --save-dev
npx tsc --init
npm install @prisma/client
npm install prisma --save-dev
Инициализируем Prisma:
npx prisma init
Команда создаст директорию prisma/ со файлом schema.prisma и файл .env с переменной подключения.
Настройка подключения к PostgreSQL
Откройте файл .env и укажите строку подключения к вашей PostgreSQL базе данных:
DATABASE_URL="postgresql://user:password@localhost:5432/mydb?schema=public"
Замените user, password и mydb на реальные значения вашей БД. Если PostgreSQL запущен локально через Docker, строка может выглядеть так:
# Запуск PostgreSQL контейнера
docker run --name pg-demo -e POSTGRES_PASSWORD=secret -e POSTGRES_DB=mydb -p 5432:5432 -d postgres
Тогда строка подключения будет:
DATABASE_URL="postgresql://postgres:secret@localhost:5432/mydb?schema=public"
Описание схемы данных
Файл prisma/schema.prisma — сердце Prisma. Здесь вы описываете модели, которые станут таблицами в PostgreSQL.
// Указываем провайдер базы данных
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
// Модель пользователя
model User {
id Int @id @default(autoincrement())
email String @unique
name String?
createdAt DateTime @default(now())
posts Post[] // Связь один-ко-многим
}
// Модель публикации
model Post {
id Int @id @default(autoincrement())
title String
content String?
published Boolean @default(false)
authorId Int
author User @relation(fields: [authorId], references: [id])
createdAt DateTime @default(now())
}
Здесь мы создали две связанные модели: User и Post. Поле posts в User и поле author в Post описывают связь один-ко-многим.
Миграции
После описания схемы нужно применить её к базе данных через миграцию:
npx prisma migrate dev --name init
Prisma создаст SQL-миграцию, применит её к БД и сгенерирует типизированный клиент. Теперь TypeScript знает о всех ваших моделях.
Для просмотра содержимого базы данных через графический интерфейс используйте:
npx prisma studio
Работа с данными через Prisma Client
Создадим файл src/index.ts и подключим клиент:
import { PrismaClient } from '@prisma/client';
// Создаём единственный экземпляр клиента
const prisma = new PrismaClient();
async function main() {
// Создание нового пользователя
const user = await prisma.user.create({
data: {
email: 'ivan@example.com',
name: 'Иван Петров',
},
});
console.log('Создан пользователь:', user);
// Создание публикации для пользователя
const post = await prisma.post.create({
data: {
title: 'Мой первый пост',
content: 'Содержимое статьи',
authorId: user.id,
},
});
console.log('Создана публикация:', post);
// Получение всех пользователей вместе с их публикациями
const users = await prisma.user.findMany({
include: {
posts: true, // Включаем связанные записи
},
});
console.log('Все пользователи:', JSON.stringify(users, null, 2));
}
main()
.catch(console.error)
.finally(async () => {
// Закрываем соединение после завершения работы
await prisma.$disconnect();
});
Фильтрация, сортировка и пагинация
Prisma предоставляет богатый API для запросов:
// Поиск опубликованных постов с фильтрацией и сортировкой
const publishedPosts = await prisma.post.findMany({
where: {
published: true,
title: {
contains: 'TypeScript', // Поиск по вхождению строки
mode: 'insensitive', // Без учёта регистра
},
},
orderBy: {
createdAt: 'desc', // Сортировка по дате, новые первые
},
skip: 0, // Пропустить записей (для пагинации)
take: 10, // Взять не более 10 записей
include: {
author: {
select: {
name: true, // Выбираем только имя автора
email: true,
},
},
},
});
Обновление и удаление записей
// Обновление публикации по id
const updatedPost = await prisma.post.update({
where: { id: 1 },
data: { published: true },
});
// Удаление пользователя
const deletedUser = await prisma.user.delete({
where: { id: 1 },
});
Транзакции
Для атомарного выполнения нескольких операций используйте транзакции:
// Обе операции выполнятся или обе откатятся
const [newUser, newPost] = await prisma.$transaction([
prisma.user.create({
data: { email: 'test@test.com', name: 'Тест' },
}),
prisma.post.create({
data: { title: 'Транзакционный пост', authorId: 2 },
}),
]);
Частые ошибки
Забывают закрыть соединение. Prisma Client держит пул соединений с БД. В скриптах всегда вызывайте prisma.$disconnect() в блоке finally. В веб-приложениях создавайте один глобальный экземпляр клиента.
Несколько экземпляров PrismaClient при hot-reload. В Next.js или при разработке с nodemon каждый перезапуск создаёт новый экземпляр. Решение — сохранять клиент в глобальной переменной:
// lib/prisma.ts — правильный паттерн для Next.js
const globalForPrisma = global as unknown as { prisma: PrismaClient };
export const prisma = globalForPrisma.prisma || new PrismaClient();
if (process.env.NODE_ENV !== 'production') {
globalForPrisma.prisma = prisma;
}
Забывают выполнить миграцию после изменения схемы. После каждого изменения schema.prisma необходимо запускать npx prisma migrate dev и npx prisma generate для обновления типов клиента.
Использование findUnique вместо findFirst. findUnique работает только с уникальными полями (@id, @unique). Для произвольных условий используйте findFirst.
Заключение
Prisma в связке с PostgreSQL предоставляет мощный и удобный инструментарий для работы с данными в TypeScript-проектах. Декларативная схема, автоматические миграции и полная типизация делают разработку быстрой и безопасной.
Вы научились инициализировать проект, описывать модели с отношениями, выполнять CRUD-операции, фильтровать и сортировать данные, а также использовать транзакции. Это базовый фундамент, на котором строится большинство серверных приложений. Следующий шаг — изучить более продвинутые возможности: агрегации, полнотекстовый поиск и оптимизацию запросов через select вместо include.






Комментарии
0