Пошук уроків, статей та іншого контенту
Реалізуємо створення, читання, оновлення й видалення записів через Prisma Client у NestJS.
Створимо REST API для сутності Task з такими операціями:
POST /tasks — створення задачі;
GET /tasks — отримання всіх задач;
GET /tasks/:id — отримання однієї задачі;
PATCH /tasks/:id — оновлення задачі;
DELETE /tasks/:id — видалення задачі.
Prisma Client відповідатиме за виконання запитів до бази даних, а NestJS — за контролери, модулі та структуру застосунку.
Встановимо Prisma CLI та Prisma Client:
npm install prisma @prisma/client
npx prisma init --datasource-provider sqliteКоманда prisma init створить:
файл prisma/schema.prisma;
файл .env.
Для прикладу використаємо SQLite. У файлі .env вкажемо:
DATABASE_URL="file:./dev.db"SQLite зберігатиме базу даних у файлі dev.db, тому для цього прикладу не потрібно запускати окремий сервер бази даних.
Відкриємо prisma/schema.prisma і додамо модель Task:
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())
updatedAt DateTime @updatedAt
}Модель має такі поля:
id — унікальний числовий ідентифікатор;
title — назва задачі;
completed — ознака виконання;
createdAt — дата створення;
updatedAt — дата останньої зміни.
Створимо таблицю та згенеруємо Prisma Client:
npx prisma migrate dev --name create-task
npx prisma generateПісля виконання команди migrate dev Prisma:
створить міграцію;
застосує її до бази даних;
оновить Prisma Client.
NestJS має працювати з одним екземпляром PrismaClient. Для цього створимо сервіс, який наслідує PrismaClient.
Файл src/prisma/prisma.service.ts:
import {
Injectable,
OnModuleDestroy,
OnModuleInit,
} from '@nestjs/common';
import { PrismaClient } from '@prisma/client';
@Injectable()
export class PrismaService
extends PrismaClient
implements OnModuleInit, OnModuleDestroy
{
async onModuleInit(): Promise<void> {
await this.$connect();
}
async onModuleDestroy(): Promise<void> {
await this.$disconnect();
}
}Метод $connect() встановлює з’єднання з базою даних під час запуску модуля.
Метод $disconnect() закриває з’єднання під час завершення роботи застосунку.
Створимо модуль для Prisma у файлі src/prisma/prisma.module.ts:
import { Global, Module } from '@nestjs/common';
import { PrismaService } from './prisma.service';
@Global()
@Module({
providers: [PrismaService],
exports: [PrismaService],
})
export class PrismaModule {}Декоратор @Global() робить PrismaService доступним в інших модулях без повторного імпорту PrismaModule.
Підключимо модуль у AppModule:
import { Module } from '@nestjs/common';
import { PrismaModule } from './prisma/prisma.module';
@Module({
imports: [PrismaModule],
})
export class AppModule {}Створимо модуль, сервіс і контролер:
nest g module tasks
nest g service tasks
nest g controller tasksУ результаті з’являться файли:
src/tasks/tasks.module.ts
src/tasks/tasks.service.ts
src/tasks/tasks.controller.tsDTO описують дані, які API приймає від клієнта.
Створимо файл src/tasks/dto/create-task.dto.ts:
export class CreateTaskDto {
title: string;
completed?: boolean;
}Поле completed необов’язкове. Якщо його не передати, Prisma використає значення false, визначене в схемі.
Створимо файл src/tasks/dto/update-task.dto.ts:
export class UpdateTaskDto {
title?: string;
completed?: boolean;
}Під час оновлення обидва поля необов’язкові: клієнт може змінити тільки назву, тільки статус або обидва значення.
Файл src/tasks/tasks.service.ts:
import { Injectable, NotFoundException } from '@nestjs/common';
import { PrismaService } from '../prisma/prisma.service';
import { CreateTaskDto } from './dto/create-task.dto';
import { UpdateTaskDto } from './dto/update-task.dto';
@Injectable()
export class TasksService {
constructor(private readonly prisma: PrismaService) {}
async create(createTaskDto: CreateTaskDto) {
return this.prisma.task.create({
data: {
title: createTaskDto.title,
completed: createTaskDto.completed,
},
});
}
async findAll() {
return this.prisma.task.findMany({
orderBy: {
createdAt: 'desc',
},
});
}
async findOne(id: number) {
const task = await this.prisma.task.findUnique({
where: { id },
});
if (!task) {
throw new NotFoundException(`Задачу з id ${id} не знайдено`);
}
return task;
}
async update(id: number, updateTaskDto: UpdateTaskDto) {
await this.findOne(id);
return this.prisma.task.update({
where: { id },
data: {
title: updateTaskDto.title,
completed: updateTaskDto.completed,
},
});
}
async remove(id: number) {
await this.findOne(id);
return this.prisma.task.delete({
where: { id },
});
}
}Для створення використовується метод create:
return this.prisma.task.create({
data: {
title: createTaskDto.title,
completed: createTaskDto.completed,
},
});task — це назва моделі Prisma. Вона відповідає моделі Task у файлі schema.prisma.
Об’єкт data містить значення, які будуть записані в таблицю.
Для отримання всіх записів використовується findMany:
return this.prisma.task.findMany({
orderBy: {
createdAt: 'desc',
},
});Параметр orderBy сортує задачі від найновішої до найстарішої.
Для отримання одного запису використовується findUnique:
const task = await this.prisma.task.findUnique({
where: { id },
});Оскільки запис із таким id може не існувати, після запиту потрібно перевірити результат і повернути HTTP-помилку 404 Not Found.
Для оновлення використовується метод update:
return this.prisma.task.update({
where: { id },
data: {
title: updateTaskDto.title,
completed: updateTaskDto.completed,
},
});Перед оновленням сервіс перевіряє наявність задачі через findOne. Якщо задача не знайдена, клієнт отримає помилку 404.
Для видалення використовується метод delete:
return this.prisma.task.delete({
where: { id },
});Метод видаляє один запис, який відповідає переданому ідентифікатору.
Файл src/tasks/tasks.controller.ts:
import {
Body,
Controller,
Delete,
Get,
Param,
ParseIntPipe,
Patch,
Post,
} from '@nestjs/common';
import { TasksService } from './tasks.service';
import { CreateTaskDto } from './dto/create-task.dto';
import { UpdateTaskDto } from './dto/update-task.dto';
@Controller('tasks')
export class TasksController {
constructor(private readonly tasksService: TasksService) {}
@Post()
create(@Body() createTaskDto: CreateTaskDto) {
return this.tasksService.create(createTaskDto);
}
@Get()
findAll() {
return this.tasksService.findAll();
}
@Get(':id')
findOne(@Param('id', ParseIntPipe) id: number) {
return this.tasksService.findOne(id);
}
@Patch(':id')
update(
@Param('id', ParseIntPipe) id: number,
@Body() updateTaskDto: UpdateTaskDto,
) {
return this.tasksService.update(id, updateTaskDto);
}
@Delete(':id')
remove(@Param('id', ParseIntPipe) id: number) {
return this.tasksService.remove(id);
}
}ParseIntPipe перетворює параметр маршруту з рядка на число:
@Param('id', ParseIntPipe) id: numberHTTP-параметри завжди надходять у вигляді рядків. Без ParseIntPipe значення id мало б тип string, хоча Prisma очікує число.
Якщо передати некоректне значення, наприклад /tasks/abc, NestJS автоматично поверне помилку 400 Bad Request.
Файл src/tasks/tasks.module.ts:
import { Module } from '@nestjs/common';
import { TasksController } from './tasks.controller';
import { TasksService } from './tasks.service';
@Module({
controllers: [TasksController],
providers: [TasksService],
})
export class TasksModule {}Додамо TasksModule до AppModule:
import { Module } from '@nestjs/common';
import { PrismaModule } from './prisma/prisma.module';
import { TasksModule } from './tasks/tasks.module';
@Module({
imports: [PrismaModule, TasksModule],
})
export class AppModule {}Тепер NestJS знає про контролер задач, а TasksService може отримати PrismaService через dependency injection.
Запустимо застосунок:
npm run start:devcurl -X POST http://localhost:3000/tasks \
-H "Content-Type: application/json" \
-d '{"title":"Вивчити Prisma"}'Приклад відповіді:
{
"id": 1,
"title": "Вивчити Prisma",
"completed": false,
"createdAt": "2026-09-01T10:00:00.000Z",
"updatedAt": "2026-09-01T10:00:00.000Z"
}Можна одразу передати статус:
curl -X POST http://localhost:3000/tasks \
-H "Content-Type: application/json" \
-d '{"title":"Створити CRUD","completed":true}'curl http://localhost:3000/taskscurl http://localhost:3000/tasks/1Якщо задачі з таким ідентифікатором немає, API поверне відповідь зі статусом 404.
curl -X PATCH http://localhost:3000/tasks/1 \
-H "Content-Type: application/json" \
-d '{"completed":true}'Можна оновити тільки назву:
curl -X PATCH http://localhost:3000/tasks/1 \
-H "Content-Type: application/json" \
-d '{"title":"Повторити CRUD через Prisma"}'curl -X DELETE http://localhost:3000/tasks/1У відповідь буде повернено видалений запис.
Після реалізації функціональність має мати приблизно таку структуру:
prisma/
└── schema.prisma
src/
├── prisma/
│ ├── prisma.module.ts
│ └── prisma.service.ts
├── tasks/
│ ├── dto/
│ │ ├── create-task.dto.ts
│ │ └── update-task.dto.ts
│ ├── tasks.controller.ts
│ ├── tasks.module.ts
│ └── tasks.service.ts
└── app.module.tsПісля зміни schema.prisma потрібно застосувати міграцію та згенерувати клієнт:
npx prisma migrate dev --name update-task
npx prisma generateІнакше в коді може бути відсутня нова модель або поле.
id передається як рядокНеправильний варіант:
findOne(@Param('id') id: number) {
return this.tasksService.findOne(id);
}TypeScript-тип number не перетворює фактичне значення HTTP-параметра. Для перетворення потрібно використовувати ParseIntPipe:
findOne(@Param('id', ParseIntPipe) id: number) {
return this.tasksService.findOne(id);
}findUniquefindUnique може повернути null. Якщо передати цей результат без перевірки, API може повернути некоректну відповідь або помилку під час подальшої обробки.
Правильний варіант:
const task = await this.prisma.task.findUnique({
where: { id },
});
if (!task) {
throw new NotFoundException('Задачу не знайдено');
}
return task;PrismaService має бути доданий до providers і exports PrismaModule:
@Global()
@Module({
providers: [PrismaService],
exports: [PrismaService],
})
export class PrismaModule {}Інакше NestJS не зможе створити залежність у TasksService.
DTO описують форму вхідних даних, але самі по собі не перевіряють їх значення. Наприклад, оголошення title: string не гарантує, що клієнт справді передав непорожній рядок.
У production-застосунках DTO зазвичай доповнюють декораторами валідації та глобальним ValidationPipe.
У цьому уроці ми:
описали модель Task у Prisma Schema;
створили міграцію бази даних;
підключили PrismaClient до NestJS;
винесли роботу з базою даних у TasksService;
реалізували CRUD-операції через Prisma;
створили контролер із REST-маршрутами;
використали ParseIntPipe для перетворення ідентифікатора;
додали обробку ситуації, коли запис не знайдено.
Основні методи Prisma Client для CRUD:
prisma.task.create()
prisma.task.findMany()
prisma.task.findUnique()
prisma.task.update()
prisma.task.delete()