Пошук уроків, статей та іншого контенту
Реалізуємо PrismaService із життєвим циклом NestJS та використаємо його в інших сервісах.
PrismaServicePrismaClient відповідає за підключення до бази даних і виконання запитів. У NestJS його зручно обгорнути у власний сервіс:
NestJS зможе створювати PrismaService через dependency injection;
підключення до бази даних відбуватиметься під час запуску модуля;
з’єднання коректно закриватиметься під час завершення роботи застосунку;
інші сервіси зможуть отримувати PrismaService через конструктор.
Для цього сервіс реалізує lifecycle hooks NestJS:
OnModuleInit — викликається після ініціалізації модуля;
OnModuleDestroy — викликається під час знищення модуля.
PrismaServiceСтворимо файл prisma.service.ts:
import { Injectable, OnModuleDestroy, OnModuleInit } from '@prisma/client';У цьому імпорті є помилка: lifecycle-інтерфейси потрібно імпортувати з @nestjs/common, а PrismaClient — з @prisma/client.
Правильна реалізація:
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();
}
}PrismaService наслідує PrismaClient, тому має всі його методи та властивості:
this.prisma.user.findMany();
this.prisma.user.create();
this.prisma.user.update();Метод onModuleInit() викликається NestJS після створення модуля. У ньому виконується підключення до бази даних:
await this.$connect();Метод onModuleDestroy() викликається під час завершення роботи модуля. Він закриває підключення:
await this.$disconnect();@Injectable() повідомляє NestJS, що цей клас можна створювати та передавати через dependency injection.
PrismaServiceЩоб використовувати сервіс в інших модулях, створимо PrismaModule:
import { Module } from '@nestjs/common';
import { PrismaService } from './prisma.service';
@Module({
providers: [PrismaService],
exports: [PrismaService],
})
export class PrismaModule {}providersМасив providers реєструє PrismaService у контейнері залежностей NestJS.
providers: [PrismaService]Після цього NestJS зможе створити екземпляр сервісу та передати його в інші класи.
exportsМасив exports робить сервіс доступним для модулів, які імпортують PrismaModule:
exports: [PrismaService]Якщо не додати PrismaService до exports, інші модулі не зможуть отримати його через dependency injection.
Припустімо, у Prisma schema описана модель User:
model User {
id Int @id @default(autoincrement())
email String @unique
name String?
createdAt DateTime @default(now())
}Після зміни schema потрібно згенерувати Prisma Client і застосувати міграцію:
npx prisma migrate dev --name add-user
npx prisma generateТепер створимо UsersService:
import { Injectable } from '@nestjs/common';
import { PrismaService } from '../prisma/prisma.service';
@Injectable()
export class UsersService {
constructor(private readonly prisma: PrismaService) {}
async findAll() {
return this.prisma.user.findMany({
orderBy: {
createdAt: 'desc',
},
});
}
async findById(id: number) {
return this.prisma.user.findUnique({
where: { id },
});
}
async create(email: string, name?: string) {
return this.prisma.user.create({
data: {
email,
name,
},
});
}
}NestJS побачить параметр конструктора:
constructor(private readonly prisma: PrismaService) {}і передасть у нього екземпляр PrismaService.
PrismaModuleUsersModule має імпортувати PrismaModule:
import { Module } from '@nestjs/common';
import { PrismaModule } from '../prisma/prisma.module';
import { UsersService } from './users.service';
@Module({
imports: [PrismaModule],
providers: [UsersService],
exports: [UsersService],
})
export class UsersModule {}Тепер залежності мають такий зв’язок:
UsersModule імпортує PrismaModule.
PrismaModule експортує PrismaService.
UsersService отримує PrismaService через конструктор.
Методи UsersService виконують запити через Prisma Client.
onModuleDestroy() викликається, коли NestJS знищує модулі. Щоб застосунок також коректно обробляв сигнали завершення процесу, наприклад SIGTERM, у main.ts можна ввімкнути shutdown hooks:
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.enableShutdownHooks();
await app.listen(3000);
}
bootstrap();Тепер під час коректного завершення застосунку NestJS викличе onModuleDestroy(), а Prisma закриє з’єднання з базою даних.
prisma.service.tsimport { 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();
}
}prisma.module.tsimport { Module } from '@nestjs/common';
import { PrismaService } from './prisma.service';
@Module({
providers: [PrismaService],
exports: [PrismaService],
})
export class PrismaModule {}users.service.tsimport { Injectable } from '@nestjs/common';
import { PrismaService } from '../prisma/prisma.service';
@Injectable()
export class UsersService {
constructor(private readonly prisma: PrismaService) {}
async findAll() {
return this.prisma.user.findMany();
}
}users.module.tsimport { Module } from '@nestjs/common';
import { PrismaModule } from '../prisma/prisma.module';
import { UsersService } from './users.service';
@Module({
imports: [PrismaModule],
providers: [UsersService],
exports: [UsersService],
})
export class UsersModule {}app.module.tsimport { Module } from '@nestjs/common';
import { PrismaModule } from './prisma/prisma.module';
import { UsersModule } from './users/users.module';
@Module({
imports: [PrismaModule, UsersModule],
})
export class AppModule {}PrismaService до providersЯкщо сервіс не зареєстрований у модулі, NestJS не зможе його створити:
@Module({
providers: [],
})
export class PrismaModule {}Потрібно додати його:
@Module({
providers: [PrismaService],
})
export class PrismaModule {}PrismaServiceЯкщо сервіс потрібен в іншому модулі, він має бути в exports:
@Module({
providers: [PrismaService],
exports: [PrismaService],
})
export class PrismaModule {}PrismaModuleМодуль, у якому використовується PrismaService, має імпортувати модуль, що його експортує:
@Module({
imports: [PrismaModule],
})
export class UsersModule {}PrismaClient вручну в кожному сервісіНе варто робити так:
import { PrismaClient } from '@prisma/client';
export class UsersService {
private readonly prisma = new PrismaClient();
}У такому випадку кожен сервіс може створити власне підключення до бази даних. Краще мати один PrismaService і передавати його через dependency injection.
Після зміни Prisma schema типи та методи клієнта можуть бути застарілими. Виконайте:
npx prisma generatePrismaService наслідує PrismaClient.
OnModuleInit використовується для підключення до бази даних.
OnModuleDestroy використовується для закриття підключення.
@Injectable() дозволяє використовувати сервіс через dependency injection.
PrismaModule має додати PrismaService до providers і exports.
Інші модулі отримують доступ до Prisma через imports.
Інші сервіси використовують Prisma через конструктор:
constructor(private readonly prisma: PrismaService) {}