Пошук уроків, статей та іншого контенту
Створите Prisma Client, опишете моделі та виконуватимете типобезпечні CRUD-операції з PostgreSQL.
Prisma — ORM для Node.js і TypeScript, який дає змогу:
описувати структуру бази даних у файлі Prisma Schema;
створювати міграції;
генерувати типізований Prisma Client;
виконувати CRUD-операції з PostgreSQL;
отримувати підказки та помилки під час написання коду.
Prisma складається з кількох основних частин:
Prisma Schema — опис моделей, зв’язків і підключення до бази даних;
Prisma Migrate — інструмент для створення та застосування міграцій;
Prisma Client — згенерований клієнт для роботи з базою даних у коді.
У цьому уроці використаємо TypeScript, PostgreSQL і дві пов’язані моделі: User та Post.
Створіть Node.js-проєкт:
mkdir prisma-example
cd prisma-example
npm init -yВстановіть Prisma та TypeScript-залежності:
npm install @prisma/client
npm install --save-dev prisma typescript tsx @types/nodeІніціалізуйте Prisma з PostgreSQL:
npx prisma init --datasource-provider postgresqlКоманда створить:
директорію prisma;
файл prisma/schema.prisma;
файл .env.
Приклад структури проєкту:
prisma-example/
├── prisma/
│ └── schema.prisma
├── src/
│ └── index.ts
├── .env
├── package.json
└── tsconfig.jsonУ файлі .env задайте рядок підключення:
DATABASE_URL="postgresql://postgres:password@localhost:5432/prisma_example?schema=public"Значення складається з таких частин:
postgresql://КОРИСТУВАЧ:ПАРОЛЬ@ХОСТ:ПОРТ/НАЗВА_БАЗИ?schema=publicНаприклад:
користувач — postgres;
пароль — password;
хост — localhost;
порт — 5432;
база даних — prisma_example.
База даних prisma_example має існувати в PostgreSQL до запуску міграції.
Відкрийте prisma/schema.prisma:
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], onDelete: Cascade)
createdAt DateTime @default(now())
}Usermodel User {
id Int @id @default(autoincrement())
email String @unique
name String
createdAt DateTime @default(now())
posts Post[]
}id — первинний ключ;
@default(autoincrement()) — автоматичне збільшення числового ідентифікатора;
email — електронна адреса користувача;
@unique — значення не може повторюватися;
createdAt — дата створення з поточним значенням за замовчуванням;
posts — зв’язок «один користувач має багато публікацій».
Postmodel Post {
id Int @id @default(autoincrement())
title String
content String?
published Boolean @default(false)
authorId Int
author User @relation(fields: [authorId], references: [id], onDelete: Cascade)
createdAt DateTime @default(now())
}content String? — необов’язкове поле;
published Boolean — логічне поле зі значенням false за замовчуванням;
authorId — зовнішній ключ;
author — зв’язок із моделлю User;
onDelete: Cascade — під час видалення користувача його публікації також видаляються.
Після зміни схеми створіть міграцію:
npx prisma migrate dev --name initЦя команда:
створить SQL-міграцію;
застосує її до PostgreSQL;
синхронізує структуру бази даних;
згенерує Prisma Client.
Prisma Client можна згенерувати окремо:
npx prisma generateСтворіть tsconfig.json:
{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"outDir": "dist"
},
"include": ["src"]
}У package.json додайте команду запуску:
{
"scripts": {
"dev": "tsx src/index.ts",
"build": "tsc",
"start": "node dist/index.js"
}
}У файлі src/index.ts створіть екземпляр клієнта:
import { PrismaClient } from "@prisma/client";
const prisma = new PrismaClient();PrismaClient генерується на основі моделей із schema.prisma. Тому для моделі User він матиме властивість prisma.user, а для моделі Post — prisma.post.
CRUD складається з чотирьох типів операцій:
Create — створення;
Read — читання;
Update — оновлення;
Delete — видалення.
Створимо користувача:
const user = await prisma.user.create({
data: {
email: "olena@example.com",
name: "Олена",
},
});Prisma перевіряє типи полів під час компіляції. Наприклад, email і name мають бути рядками, а id передавати не потрібно, оскільки він генерується базою даних.
Створити користувача разом із публікацією можна за допомогою вкладеної операції:
const userWithPost = await prisma.user.create({
data: {
email: "taras@example.com",
name: "Тарас",
posts: {
create: {
title: "Моя перша публікація",
content: "Текст публікації",
published: true,
},
},
},
include: {
posts: true,
},
});Властивість include вказує Prisma додати пов’язані записи до результату.
Для пошуку за первинним ключем використовуйте findUnique:
const user = await prisma.user.findUnique({
where: {
email: "taras@example.com",
},
include: {
posts: true,
},
});findUnique можна використовувати для полів, які є унікальними. У нашій моделі такими полями є id та email.
Результат може бути null, якщо запис не знайдено:
if (!user) {
console.log("Користувача не знайдено");
} else {
console.log(user.name);
}Для пошуку за довільними умовами використовуйте findFirst:
const user = await prisma.user.findFirst({
where: {
name: "Тарас",
},
});Для отримання списку записів використовуйте findMany:
const users = await prisma.user.findMany({
orderBy: {
createdAt: "desc",
},
include: {
posts: true,
},
});Можна додати фільтрацію:
const publishedPosts = await prisma.post.findMany({
where: {
published: true,
},
orderBy: {
createdAt: "desc",
},
});Оновимо ім’я користувача:
const updatedUser = await prisma.user.update({
where: {
email: "taras@example.com",
},
data: {
name: "Тарас Коваленко",
},
});Оновлення публікації:
const updatedPost = await prisma.post.update({
where: {
id: 1,
},
data: {
published: true,
},
});Для оновлення кількох записів використовуйте updateMany:
const result = await prisma.post.updateMany({
where: {
published: false,
},
data: {
published: true,
},
});
console.log(`Оновлено записів: ${result.count}`);Результат updateMany містить кількість змінених записів у властивості count.
Видалимо одну публікацію:
const deletedPost = await prisma.post.delete({
where: {
id: 1,
},
});Видалимо кілька непублікованих публікацій:
const result = await prisma.post.deleteMany({
where: {
published: false,
},
});
console.log(`Видалено записів: ${result.count}`);Видалення користувача також видалить його публікації, оскільки в схемі вказано:
onDelete: Cascadeawait prisma.user.delete({
where: {
email: "taras@example.com",
},
});Операції deleteMany та updateMany потрібно використовувати обережно. Якщо не вказати where, операція може змінити або видалити всі записи відповідної моделі.
selectЗа замовчуванням Prisma повертає всі поля моделі. Щоб отримати лише потрібні поля, використовуйте select:
const users = await prisma.user.findMany({
select: {
id: true,
name: true,
email: true,
},
});У результаті не буде createdAt та posts.
select і include не можна використовувати одночасно на одному рівні запиту. Потрібно обрати один із цих підходів.
Наведений приклад створює користувача з публікацією, читає його, оновлює ім’я, а потім видаляє користувача.
Файл src/index.ts:
import { PrismaClient } from "@prisma/client";
const prisma = new PrismaClient();
async function main(): Promise<void> {
// Створюємо користувача разом із першою публікацією
const createdUser = await prisma.user.create({
data: {
email: "anna@example.com",
name: "Анна",
posts: {
create: {
title: "Знайомство з Prisma",
content: "Prisma спрощує типобезпечну роботу з базою даних.",
published: false,
},
},
},
include: {
posts: true,
},
});
console.log("Створено користувача:", createdUser);
// Отримуємо користувача за унікальною електронною адресою
const foundUser = await prisma.user.findUnique({
where: {
email: "anna@example.com",
},
include: {
posts: true,
},
});
if (!foundUser) {
throw new Error("Користувача не знайдено");
}
console.log("Знайдено користувача:", foundUser);
// Оновлюємо ім’я користувача
const updatedUser = await prisma.user.update({
where: {
id: foundUser.id,
},
data: {
name: "Анна Петренко",
},
});
console.log("Оновлено користувача:", updatedUser);
// Видаляємо користувача та пов’язані публікації
const deletedUser = await prisma.user.delete({
where: {
id: updatedUser.id,
},
});
console.log("Видалено користувача:", deletedUser);
}
main()
.catch((error: unknown) => {
console.error("Помилка виконання:", error);
process.exitCode = 1;
})
.finally(async () => {
// Закриваємо з’єднання з базою даних
await prisma.$disconnect();
});Запустіть приклад:
npm run devPrisma Client генерує типи на основі моделей. Наприклад, у такому коді TypeScript знає, які поля можна передати:
await prisma.user.create({
data: {
email: "user@example.com",
name: "Користувач",
},
});Помилка типів виникне ще до запуску програми:
await prisma.user.create({
data: {
email: 123,
name: "Користувач",
},
});Поле email очікує значення типу string, але отримує number.
Так само Prisma перевіряє назви полів і структуру вкладених операцій:
await prisma.user.findMany({
select: {
email: true,
unknownField: true,
},
});У цьому випадку TypeScript повідомить, що unknownField не існує в моделі User.
Типобезпека не замінює перевірку даних, отриманих від користувача. Дані з HTTP-запитів усе одно потрібно перевіряти до передачі в Prisma Client.
Після зміни schema.prisma створюйте нову міграцію:
npx prisma migrate dev --name add_user_statusНазва міграції має описувати зміну, наприклад:
npx prisma migrate dev --name add_post_published_atДля локальної розробки зазвичай використовують:
npx prisma migrate devДля застосування вже створених міграцій в іншому середовищі, наприклад у production, використовують:
npx prisma migrate deployНе редагуйте вручну застосовані міграції в проєкті, якщо база даних уже синхронізована з ними. Зміни схеми краще оформлювати новими міграціями.
Якщо після зміни схеми виникають помилки щодо відсутніх моделей або полів, згенеруйте клієнт:
npx prisma generateDATABASE_URLПеревірте:
назву бази даних;
ім’я користувача;
пароль;
порт PostgreSQL;
доступність сервера PostgreSQL.
findUnique для неунікального поляfindUnique працює лише з полями, позначеними як унікальні:
email String @uniqueДля пошуку за звичайним полем використовуйте findFirst або findMany:
const user = await prisma.user.findFirst({
where: {
name: "Анна",
},
});nullfindUnique і findFirst можуть повернути null. Перед використанням результату перевіряйте, чи запис існує:
const user = await prisma.user.findUnique({
where: {
id: 1,
},
});
if (!user) {
throw new Error("Користувача не знайдено");
}
console.log(user.name);Якщо створити двох користувачів з однаковим email, база даних відхилить другу операцію. У прикладному коді такі помилки потрібно обробляти, особливо під час реєстрації користувачів.
deleteManyТакий код видалить усі публікації:
await prisma.post.deleteMany();Завжди перевіряйте умову where, якщо операція не має застосовуватися до всієї таблиці.
У цьому уроці ви:
встановили Prisma для Node.js-проєкту;
підключили PostgreSQL через DATABASE_URL;
описали моделі User і Post;
створили зв’язок «один до багатьох»;
застосували міграцію;
згенерували Prisma Client;
виконали типобезпечні операції створення, читання, оновлення та видалення;
використали include, select, findUnique, findMany, updateMany і deleteMany;
налаштували каскадне видалення пов’язаних записів.