Пошук уроків, статей та іншого контенту
Підключите Prisma до Server Actions і налаштуєте доступ до бази даних на сервері.
Server Actions — це асинхронні функції, які виконуються виключно на сервері. Їх можна викликати з форми або іншого серверного коду Next.js.
Це зручно для роботи з Prisma, оскільки:
Prisma Client не потрапляє у браузерний бандл;
підключення до бази даних залишається на сервері;
не потрібно створювати окремий API route для простої операції;
Server Action може безпосередньо читати та змінювати дані в базі.
Prisma Client не можна імпортувати у Client Component. Код, який працює з базою даних, має залишатися на сервері.
Встановіть Prisma та Prisma Client:
npm install prisma @prisma/client
npx prisma initКоманда prisma init створить файл prisma/schema.prisma і файл .env.
Для прикладу використаємо SQLite. У файлі .env вкажіть:
DATABASE_URL="file:./dev.db"У prisma/schema.prisma опишіть модель завдань:
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "sqlite"
url = env("DATABASE_URL")
}
model Task {
id Int @id @default(autoincrement())
title String
completed Boolean @default(false)
createdAt DateTime @default(now())
}Створіть базу даних і виконайте міграцію:
npx prisma migrate dev --name initПісля цього Prisma створить SQLite-файл і згенерує Prisma Client.
Створіть файл src/lib/prisma.ts:
import { PrismaClient } from "@prisma/client";
const globalForPrisma = globalThis as unknown as {
prisma: PrismaClient | undefined;
};
export const prisma =
globalForPrisma.prisma ??
new PrismaClient();
if (process.env.NODE_ENV !== "production") {
// Зберігаємо клієнт під час гарячого перезавантаження в режимі розробки
globalForPrisma.prisma = prisma;
}У режимі розробки Next.js може багаторазово перезавантажувати модулі. Якщо щоразу створювати новий PrismaClient, можна отримати надто багато підключень до бази даних.
Глобальне збереження клієнта дозволяє повторно використовувати той самий екземпляр під час hot reload. У production створюється один екземпляр на процес Node.js.
Створіть файл src/app/tasks/actions.ts:
"use server";
import { revalidatePath } from "next/cache";
import { prisma } from "@/lib/prisma";
export async function createTask(formData: FormData) {
const titleValue = formData.get("title");
if (typeof titleValue !== "string") {
throw new Error("Назва завдання має бути рядком");
}
const title = titleValue.trim();
if (title.length === 0) {
throw new Error("Назва завдання не може бути порожньою");
}
if (title.length > hundred) {
throw new Error("Назва завдання занадто довга");
}
await prisma.task.create({
data: {
title,
},
});
revalidatePath("/tasks");
}У цьому прикладі є помилка в числовому літералі hundred. Правильна версія Server Action:
"use server";
import { revalidatePath } from "next/cache";
import { prisma } from "@/lib/prisma";
export async function createTask(formData: FormData) {
const titleValue = formData.get("title");
if (typeof titleValue !== "string") {
throw new Error("Назва завдання має бути рядком");
}
const title = titleValue.trim();
if (title.length === 0) {
throw new Error("Назва завдання не може бути порожньою");
}
if (title.length > 100) {
throw new Error("Назва завдання занадто довга");
}
await prisma.task.create({
data: {
title,
},
});
revalidatePath("/tasks");
}Директива "use server" на початку файлу повідомляє Next.js, що експортовані функції є Server Actions і повинні виконуватися на сервері.
formData.get("title") отримує значення поля форми з атрибутом name="title".
Перед записом у базу даних потрібно перевіряти вхідні дані:
значення може бути відсутнім;
значення може мати неочікуваний тип;
рядок може бути порожнім;
довжина значення може перевищувати допустиму.
Створіть сторінку src/app/tasks/page.tsx:
import { prisma } from "@/lib/prisma";
import { createTask } from "./actions";
export default async function TasksPage() {
const tasks = await prisma.task.findMany({
orderBy: {
createdAt: "desc",
},
});
return (
<main>
<h1>Завдання</h1>
<form action={createTask}>
<label htmlFor="title">Нове завдання</label>
<input
id="title"
name="title"
type="text"
required
maxLength={100}
/>
<button type="submit">Додати</button>
</form>
<ul>
{tasks.map((task) => (
<li key={task.id}>
{task.title}
</li>
))}
</ul>
</main>
);
}Сторінка є Server Component за замовчуванням, тому вона може напряму викликати Prisma:
const tasks = await prisma.task.findMany();Передавання Server Action у властивість action форми:
<form action={createTask}>означає, що після відправлення форми Next.js передасть її дані функції createTask. Функція виконається на сервері, а не в браузері.
Після створення запису викликається:
revalidatePath("/tasks");Це повідомляє Next.js, що дані для маршруту /tasks потрібно оновити. Після завершення Server Action сторінка отримає актуальний список завдань.
У результаті приклад може мати таку структуру:
prisma/
schema.prisma
src/
app/
tasks/
actions.ts
page.tsx
lib/
prisma.ts
.envЗапустіть застосунок:
npm run devПісля цього відкрийте маршрут /tasks.
Імпорт Prisma має бути тільки у серверних файлах:
import { prisma } from "@/lib/prisma";Не імпортуйте цей модуль у компонент із директивою "use client":
"use client";
import { prisma } from "@/lib/prisma";Так робити не можна, тому що Prisma Client призначений для Node.js-середовища та не повинен потрапляти до браузера.
Якщо інтерфейсу потрібна взаємодія на клієнті, Client Component може відправляти дані до Server Action. При цьому сама робота з Prisma залишається у Server Action.
"use client";
import { createTask } from "./actions";
export function TaskForm() {
return (
<form action={createTask}>
<input name="title" required />
<button type="submit">Додати</button>
</form>
);
}Server Action при цьому все одно виконується на сервері.
Server Action можна оголосити у файлі з "use server":
"use server";
export async function createTask(formData: FormData) {
// ...
}Також Server Action можна оголосити безпосередньо всередині Server Component:
import { revalidatePath } from "next/cache";
import { prisma } from "@/lib/prisma";
export default async function TasksPage() {
async function createTask(formData: FormData) {
"use server";
const title = formData.get("title");
if (typeof title !== "string" || title.trim() === "") {
return;
}
await prisma.task.create({
data: {
title: title.trim(),
},
});
revalidatePath("/tasks");
}
const tasks = await prisma.task.findMany();
return (
<form action={createTask}>
<input name="title" />
<button type="submit">Додати</button>
</form>
);
}Для повторного використання краще зберігати Server Actions в окремому файлі actions.ts. Так код сторінки залишається компактнішим, а дії можна імпортувати в різні компоненти.
Помилки під час роботи з базою даних потрібно обробляти на сервері. Наприклад:
"use server";
import { revalidatePath } from "next/cache";
import { prisma } from "@/lib/prisma";
export async function createTask(formData: FormData) {
const titleValue = formData.get("title");
if (typeof titleValue !== "string") {
return { error: "Некоректна назва завдання" };
}
const title = titleValue.trim();
if (!title) {
return { error: "Введіть назву завдання" };
}
try {
await prisma.task.create({
data: { title },
});
revalidatePath("/tasks");
return { success: true };
} catch {
return { error: "Не вдалося створити завдання" };
}
}Server Action може повертати результат, який потім можна використати для відображення повідомлення у формі. Важливо не повертати користувачу внутрішні деталі помилок бази даних, наприклад SQL-запити або конфігурацію підключення.
Не створюйте клієнт безпосередньо всередині Server Action:
"use server";
import { PrismaClient } from "@prisma/client";
export async function createTask(formData: FormData) {
const prisma = new PrismaClient();
// Робота з базою даних
}Використовуйте спільний модуль src/lib/prisma.ts з єдиним екземпляром PrismaClient.
nameServer Action отримує дані форми за іменем поля:
<input name="title" />Якщо name не вказати, formData.get("title") поверне null.
Prisma повинен використовуватися у Server Component або Server Action. Не додавайте "use client" до файлу, який напряму імпортує Prisma.
Після зміни schema.prisma потрібно оновити базу даних:
npx prisma migrate dev --name add_task_fieldІнакше схема бази даних може не відповідати схемі Prisma.
Після зміни даних сторінка може показувати попередній результат. Для маршруту, який потрібно оновити, викличте:
revalidatePath("/tasks");Дані форми надходять від користувача, тому не можна без перевірки передавати їх до Prisma. Перевіряйте тип, порожні значення, довжину та інші обмеження моделі.
Prisma Client можна безпосередньо використовувати у Server Actions.
Файл із "use server" містить функції, які виконуються на сервері.
Server Action можна передати у action HTML-форми.
Дані форми надходять до функції через FormData.
Prisma Client варто створювати в окремому модулі та повторно використовувати.
Prisma не можна імпортувати у Client Components.
Після зміни даних використовуйте revalidatePath, щоб оновити сторінку.
Вхідні дані потрібно перевіряти до виконання запиту до бази даних.