Пошук уроків, статей та іншого контенту
Розберете ORM-підхід, створите схему Prisma та виконуватимете типізовані запити в Next.js.
ORM (Object-Relational Mapping) — це підхід, який дає змогу працювати з базою даних через об’єкти та методи мови програмування.
Без ORM SQL-запит може виглядати так:
SELECT * FROM Task WHERE completed = false;З ORM той самий запит описується через JavaScript або TypeScript:
const tasks = await prisma.task.findMany({
where: {
completed: false,
},
});ORM бере на себе:
перетворення об’єктів на SQL-запити;
перетворення результатів SQL у JavaScript-об’єкти;
перевірку структури даних;
типізацію запитів;
частину роботи зі схемою бази даних.
У цьому уроці використаємо Prisma — ORM для Node.js і TypeScript, який добре інтегрується з Next.js.
Припустімо, що у вас уже є Next.js-проєкт із TypeScript.
Встановіть Prisma та Prisma Client:
npm install @prisma/client
npm install -D prismaprisma — CLI-інструмент для створення схеми, міграцій і генерації клієнта;
@prisma/client — бібліотека, через яку код Next.js звертається до бази даних.
Ініціалізуйте Prisma з SQLite:
npx prisma init --datasource-provider sqliteПісля цього з’являться:
папка prisma;
файл prisma/schema.prisma;
файл .env.
SQLite зручно використовувати під час навчання, тому що це база даних у вигляді локального файлу. Для неї не потрібно запускати окремий сервер.
Відкрийте prisma/schema.prisma і замініть його вміст:
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "sqlite"
url = env("DATABASE_URL")
}
model Task {
id String @id @default(cuid())
title String
completed Boolean @default(false)
createdAt DateTime @default(now())
}Схема описує структуру бази даних.
datasource db {
provider = "sqlite"
url = env("DATABASE_URL")
}provider = "sqlite" означає, що використовується SQLite;
env("DATABASE_URL") бере адресу бази даних зі змінної середовища.
У .env має бути такий рядок:
DATABASE_URL="file:./dev.db"Файл dev.db буде створений автоматично під час застосування міграції.
model Task {
id String @id @default(cuid())
title String
completed Boolean @default(false)
createdAt DateTime @default(now())
}Модель Task перетвориться на таблицю в базі даних.
Її поля:
id — унікальний ідентифікатор;
title — назва завдання;
completed — ознака виконання;
createdAt — дата створення.
Символ ? після типу означав би, що поле може бути null. Наприклад:
description String?Після опису моделі створіть міграцію:
npx prisma migrate dev --name create_taskPrisma:
порівняє схему з поточною структурою бази;
створить SQL-міграцію;
застосує її до бази даних;
згенерує Prisma Client.
Міграція зберігає історію змін структури бази даних. Якщо в майбутньому змінити модель Task, наприклад додати поле description, потрібно буде створити нову міграцію:
npx prisma migrate dev --name add_task_descriptionPrisma Client — це згенерований TypeScript-клієнт, який містить методи для роботи з моделями з schema.prisma.
Зазвичай він генерується автоматично після migrate dev. Його також можна згенерувати вручну:
npx prisma generateПісля цього TypeScript розумітиме доступні моделі, поля та параметри запитів.
Наприклад, якщо модель називається Task, у клієнта з’явиться властивість:
prisma.taskА якщо модель перейменувати або змінити її поля, TypeScript покаже помилки у відповідному коді.
Створіть файл src/lib/prisma.ts:
import { PrismaClient } from "@prisma/client";
const globalForPrisma = globalThis as unknown as {
prisma?: PrismaClient;
};
export const prisma =
globalForPrisma.prisma ??
new PrismaClient();
if (process.env.NODE_ENV !== "production") {
globalForPrisma.prisma = prisma;
}Тепер у застосунку можна імпортувати один об’єкт prisma:
import { prisma } from "@/lib/prisma";У режимі розробки Next.js може перезавантажувати модулі. Якщо під час кожного перезавантаження створювати новий PrismaClient, можна отримати надто багато підключень до бази даних.
Збереження клієнта в globalThis допомагає повторно використовувати його під час розробки.
Prisma потрібно використовувати в серверному коді:
у Route Handlers;
у Server Components;
у Server Actions;
в інших серверних модулях.
Не імпортуйте prisma у компонент із директивою "use client", оскільки код доступу до бази даних не повинен потрапляти в браузер.
const tasks = await prisma.task.findMany();Результатом буде масив об’єктів Task.
Щоб додати сортування:
const tasks = await prisma.task.findMany({
orderBy: {
createdAt: "desc",
},
});Щоб отримати лише невиконані завдання:
const tasks = await prisma.task.findMany({
where: {
completed: false,
},
});Якщо потрібно знайти завдання за унікальним id, використовуйте findUnique:
const task = await prisma.task.findUnique({
where: {
id: taskId,
},
});Якщо запису не існує, результатом буде null.
const task = await prisma.task.create({
data: {
title: "Вивчити Prisma",
},
});Поле completed можна не передавати, оскільки для нього задано значення за замовчуванням false.
const task = await prisma.task.update({
where: {
id: taskId,
},
data: {
completed: true,
},
});await prisma.task.delete({
where: {
id: taskId,
},
});Усі ці методи є асинхронними, тому їх потрібно викликати з await усередині async-функції.
У Next.js із App Router серверний API-маршрут можна створити у файлі:
src/app/api/tasks/route.tsЦей маршрут підтримуватиме:
GET /api/tasks — отримання завдань;
POST /api/tasks — створення завдання.
import { NextResponse } from "next/server";
import { prisma } from "@/lib/prisma";
export async function GET() {
const tasks = await prisma.task.findMany({
orderBy: {
createdAt: "desc",
},
});
return NextResponse.json(tasks);
}
export async function POST(request: Request) {
const body: unknown = await request.json();
if (
typeof body !== "object" ||
body === null ||
!("title" in body) ||
typeof body.title !== "string" ||
body.title.trim() === ""
) {
return NextResponse.json(
{ error: "Поле title є обов'язковим" },
{ status: 400 },
);
}
const task = await prisma.task.create({
data: {
title: body.title.trim(),
},
});
return NextResponse.json(task, { status: 201 });
}Тепер можна виконати запит:
curl http://localhost:3000/api/tasksДля створення завдання:
curl -X POST http://localhost:3000/api/tasks \
-H "Content-Type: application/json" \
-d '{"title":"Прочитати документацію Prisma"}'Метод POST:
читає JSON із запиту;
перевіряє, що title є непорожнім рядком;
створює запис через prisma.task.create;
повертає створене завдання у відповіді.
Prisma генерує типи на основі schema.prisma.
Наприклад, у такого запиту:
const task = await prisma.task.create({
data: {
title: "Нове завдання",
completed: false,
},
});TypeScript знає:
що модель task існує;
що поле title має бути рядком;
що поле completed має бути логічним значенням;
які поля є обов’язковими;
які поля можуть генеруватися автоматично.
Помилка буде виявлена ще під час розробки:
await prisma.task.create({
data: {
title: 123,
},
});title має тип String, тому число 123 не відповідає схемі.
Типізація не замінює перевірку даних від користувача. Дані з HTTP-запиту все одно потрібно перевіряти під час виконання програми, як у прикладі з POST.
Для перегляду записів у базі даних можна запустити Prisma Studio:
npx prisma studioPrisma Studio відкриє інтерфейс, у якому можна переглядати, створювати, редагувати та видаляти записи.
Це зручно під час навчання та перевірки результатів роботи API.
Якщо TypeScript не знаходить модель або її методи, запустіть:
npx prisma generateПісля зміни схеми зазвичай достатньо виконати:
npx prisma migrate dev --name describe_the_changeDATABASE_URLПеревірте, що у .env є змінна:
DATABASE_URL="file:./dev.db"Після зміни .env перезапустіть сервер Next.js.
Не використовуйте PrismaClient у файлі, де є:
"use client";Запити до бази даних мають виконуватися на сервері. Клієнтський компонент може звертатися до вашого API-маршруту через fetch.
schema.prisma, але база не зміниласяРедагування схеми саме по собі не змінює базу даних. Потрібно створити й застосувати міграцію:
npx prisma migrate dev --name describe_the_changeДля моделі:
model Task {
// ...
}у Prisma Client використовується:
prisma.taskНазва властивості починається з малої літери.
ORM дає змогу працювати з базою даних через об’єкти та методи TypeScript.
Prisma описує структуру бази даних у файлі schema.prisma.
Міграції синхронізують схему Prisma з реальною базою даних.
Prisma Client генерує типізований API для моделей.
У Next.js Prisma потрібно використовувати на сервері.
Основні методи Prisma Client: findMany, findUnique, create, update і delete.
Перед записом даних із HTTP-запиту їх потрібно перевіряти під час виконання програми.