Пошук уроків, статей та іншого контенту
Застосуєте OnModuleInit, OnModuleDestroy та пов’язані хуки для ініціалізації й завершення роботи провайдерів.
Провайдери NestJS часто потребують спеціальної логіки:
підключитися до зовнішнього сервісу після створення модуля;
завантажити початкові дані;
запустити фоновий процес;
коректно закрити з’єднання;
зупинити таймери або черги перед завершенням застосунку.
Для цього NestJS надає хуки життєвого циклу. Провайдер може реалізувати спеціальний інтерфейс, а NestJS автоматично викличе відповідний метод у потрібний момент.
Основні хуки:
OnModuleInit — після ініціалізації модуля;
OnApplicationBootstrap — після ініціалізації всього застосунку;
OnModuleDestroy — під час завершення роботи модуля;
BeforeApplicationShutdown — перед завершенням застосунку;
OnApplicationShutdown — під час завершення застосунку.
Хуки можуть бути асинхронними: NestJS дочекається завершення методу, якщо він повертає Promise.
OnModuleInitІнтерфейс OnModuleInit містить метод:
onModuleInit(): void | Promise<void>NestJS викликає його після того, як усі залежності провайдера будуть створені та модуль буде ініціалізований.
import { Injectable, OnModuleInit } from '@nestjs/common';
@Injectable()
export class ConfigurationService implements OnModuleInit {
private isReady = false;
async onModuleInit(): Promise<void> {
// Імітація завантаження конфігурації із зовнішнього джерела
await new Promise((resolve) => setTimeout(resolve, 500));
this.isReady = true;
console.log('Конфігурацію завантажено');
}
getStatus(): string {
return this.isReady ? 'ready' : 'not ready';
}
}Цей хук підходить для:
перевірки конфігурації;
підготовки кешу;
завантаження довідкових даних;
ініціалізації клієнта зовнішнього сервісу;
виконання асинхронної підготовки провайдера.
Якщо один провайдер залежить від іншого, NestJS спочатку створить залежність. Після цього буде викликано відповідні хуки.
import { Injectable, OnModuleInit } from '@nestjs/common';
@Injectable()
export class DatabaseService implements OnModuleInit {
async onModuleInit(): Promise<void> {
console.log('Підключення до бази даних встановлено');
}
}
@Injectable()
export class UserService implements OnModuleInit {
constructor(private readonly databaseService: DatabaseService) {}
onModuleInit(): void {
console.log('UserService готовий до роботи');
}
}UserService отримує DatabaseService через dependency injection. Однак не варто покладатися на випадковий порядок хуків між незалежними провайдерами. Якщо порядок важливий, явну залежність потрібно описати через конструктор.
OnApplicationBootstrapOnApplicationBootstrap викликається після завершення ініціалізації всіх модулів застосунку.
import {
Injectable,
OnApplicationBootstrap,
} from '@nestjs/common';
@Injectable()
export class StartupService implements OnApplicationBootstrap {
async onApplicationBootstrap(): Promise<void> {
// Цей код виконається після ініціалізації всіх модулів
console.log('Застосунок повністю ініціалізований');
}
}Різниця між двома хуками:
onModuleInit() стосується конкретного модуля;
onApplicationBootstrap() викликається після підготовки всього застосунку.
OnApplicationBootstrap зручно використовувати, коли дія залежить від кількох модулів. Наприклад, коли потрібно запустити фонову синхронізацію лише після того, як готові база даних, конфігурація та інші провайдери.
OnModuleDestroyOnModuleDestroy використовується для очищення ресурсів під час завершення роботи модуля.
import {
Injectable,
OnModuleDestroy,
} from '@nestjs/common';
@Injectable()
export class CacheService implements OnModuleDestroy {
onModuleDestroy(): void {
console.log('Ресурси CacheService звільнено');
}
}Типові завдання для цього хука:
зупинити таймери;
завершити фонові операції;
закрити з’єднання;
звільнити ресурси;
припинити обробку нових задач.
Метод також може бути асинхронним:
import {
Injectable,
OnModuleDestroy,
} from '@nestjs/common';
@Injectable()
export class ExternalClientService implements OnModuleDestroy {
async onModuleDestroy(): Promise<void> {
await this.disconnect();
console.log('З’єднання із зовнішнім сервісом закрито');
}
private async disconnect(): Promise<void> {
await new Promise((resolve) => setTimeout(resolve, 200));
}
}BeforeApplicationShutdownBeforeApplicationShutdown викликається перед завершенням застосунку. Метод отримує назву сигналу, якщо завершення було спричинене операційною системою.
import {
BeforeApplicationShutdown,
Injectable,
} from '@nestjs/common';
@Injectable()
export class ShutdownPreparationService
implements BeforeApplicationShutdown
{
async beforeApplicationShutdown(signal?: string): Promise<void> {
console.log(`Підготовка до завершення. Сигнал: ${signal ?? 'невідомий'}`);
// Тут можна заборонити приймання нових задач
await new Promise((resolve) => setTimeout(resolve, 100));
}
}Цей хук зручний для підготовчих дій:
припинити приймання нових задач;
повідомити внутрішні компоненти про завершення;
дочекатися коротких операцій;
підготувати ресурси до остаточного закриття.
OnApplicationShutdownOnApplicationShutdown викликається під час завершення всього застосунку.
import {
Injectable,
OnApplicationShutdown,
} from '@nestjs/common';
@Injectable()
export class ShutdownService implements OnApplicationShutdown {
onApplicationShutdown(signal?: string): void {
console.log(`Застосунок завершує роботу. Сигнал: ${signal ?? 'невідомий'}`);
}
}За потреби можна поєднувати кілька хуків в одному провайдері:
import {
Injectable,
OnApplicationBootstrap,
OnApplicationShutdown,
} from '@nestjs/common';
@Injectable()
export class WorkerService
implements OnApplicationBootstrap, OnApplicationShutdown
{
private intervalId?: NodeJS.Timeout;
onApplicationBootstrap(): void {
this.intervalId = setInterval(() => {
console.log('Фоновий процес виконує роботу');
}, 5000);
console.log('Фоновий процес запущено');
}
onApplicationShutdown(): void {
if (this.intervalId) {
clearInterval(this.intervalId);
this.intervalId = undefined;
}
console.log('Фоновий процес зупинено');
}
}Важливо зберігати ідентифікатор таймера. Якщо викликати setInterval, але не зберегти його результат, зупинити конкретний таймер під час завершення буде складніше.
Щоб NestJS обробляв сигнали операційної системи, наприклад SIGTERM або SIGINT, у main.ts потрібно викликати enableShutdownHooks().
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
async function bootstrap(): Promise<void> {
const app = await NestFactory.create(AppModule);
// Дозволяє запускати хуки завершення за сигналами ОС
app.enableShutdownHooks();
await app.listen(3000);
}
bootstrap();Після цього натискання Ctrl+C або надсилання процесу сигналу завершення активує відповідні хуки.
У середовищі контейнерів найчастіше використовується SIGTERM. Обробка цього сигналу дає змогу застосунку:
припинити приймання нової роботи;
завершити поточні операції;
закрити підключення;
коректно завершити процес.
Нижче наведено приклад провайдера, який запускає фоновий процес після повної ініціалізації застосунку та зупиняє його перед завершенням.
worker.service.tsimport {
Injectable,
OnApplicationBootstrap,
OnApplicationShutdown,
} from '@nestjs/common';
@Injectable()
export class WorkerService
implements OnApplicationBootstrap, OnApplicationShutdown
{
private intervalId: NodeJS.Timeout | undefined;
private isShuttingDown = false;
onApplicationBootstrap(): void {
this.intervalId = setInterval(() => {
if (!this.isShuttingDown) {
console.log(`[${new Date().toISOString()}] Обробка черги`);
}
}, 2000);
console.log('WorkerService: фоновий процес запущено');
}
async onApplicationShutdown(signal?: string): Promise<void> {
this.isShuttingDown = true;
if (this.intervalId) {
clearInterval(this.intervalId);
this.intervalId = undefined;
}
// Імітація очікування завершення поточної операції
await new Promise((resolve) => setTimeout(resolve, 300));
console.log(
`WorkerService: процес зупинено, сигнал: ${signal ?? 'невідомий'}`,
);
}
}app.module.tsimport { Module } from '@nestjs/common';
import { WorkerService } from './worker.service';
@Module({
providers: [WorkerService],
})
export class AppModule {}main.tsimport { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
async function bootstrap(): Promise<void> {
const app = await NestFactory.create(AppModule);
// Увімкнення обробки SIGINT і SIGTERM
app.enableShutdownHooks();
await app.listen(3000);
console.log('Застосунок запущено на порту 3000');
}
bootstrap();Після запуску застосунку в консолі кожні дві секунди з’являтиметься повідомлення про обробку черги. Якщо натиснути Ctrl+C, NestJS викличе onApplicationShutdown(), таймер буде очищено, а застосунок завершить роботу.
Під час завершення застосунку NestJS виконує хуки в такому загальному порядку:
onModuleDestroy();
beforeApplicationShutdown();
закриття серверів і ресурсів NestJS;
onApplicationShutdown().
Конкретний порядок між незалежними провайдерами не варто використовувати як приховану логіку. Якщо один компонент має чекати інший, залежність між ними повинна бути явною.
Конструктор призначений для отримання залежностей і простої синхронної підготовки об’єкта.
constructor(
private readonly configService: ConfigService,
) {}Не варто виконувати в конструкторі:
асинхронні операції;
підключення до бази даних;
запуск таймерів;
запуск фонових процесів;
логіку, яка потребує повністю ініціалізованого застосунку.
Для цього використовуйте onModuleInit() або onApplicationBootstrap().
onModuleInit(): void {
setInterval(() => {
console.log('Робота');
}, 1000);
}Такий таймер може продовжувати виконання під час завершення застосунку. Ідентифікатор потрібно зберегти й очистити:
private intervalId?: NodeJS.Timeout;
onModuleInit(): void {
this.intervalId = setInterval(() => {
console.log('Робота');
}, 1000);
}
onModuleDestroy(): void {
if (this.intervalId) {
clearInterval(this.intervalId);
}
}enableShutdownHooksЯкщо в main.ts не викликати:
app.enableShutdownHooks();хуки завершення можуть не запускатися під час завершення процесу через сигнал операційної системи.
awaitЯкщо хук виконує асинхронну операцію, потрібно повернути Promise або оголосити метод як async:
async onModuleInit(): Promise<void> {
await this.connect();
}Помилковий варіант:
onModuleInit(): void {
this.connect();
}У цьому випадку NestJS не очікує завершення connect() перед переходом до наступних етапів.
onModuleInit() не означає, що всі модулі застосунку вже готові. Якщо логіка потребує всього застосунку, використовуйте onApplicationBootstrap().
Методи очищення краще робити безпечними для повторного виклику: перевіряти, чи ресурс існує, і після звільнення встановлювати посилання в undefined або null.
OnModuleInit використовується для ініціалізації провайдера після створення його залежностей.
OnApplicationBootstrap викликається після готовності всіх модулів застосунку.
OnModuleDestroy призначений для звільнення ресурсів модуля.
BeforeApplicationShutdown дає змогу підготувати застосунок до завершення.
OnApplicationShutdown використовується для фінальної логіки завершення.
Асинхронні хуки повинні повертати Promise, щоб NestJS міг дочекатися їх виконання.
Для реагування на SIGINT і SIGTERM потрібно викликати app.enableShutdownHooks().
Таймери, підключення та фонові процеси потрібно явно зупиняти у відповідному хуку.