Пошук уроків, статей та іншого контенту
Керуватимете запуском і завершенням застосунку через хуки, graceful shutdown та сигнали операційної системи.
Життєвий цикл NestJS-застосунку складається з етапів:
створення модулів і провайдерів;
ініціалізації залежностей;
запуску застосунку;
обробки запитів;
завершення роботи та звільнення ресурсів.
NestJS надає спеціальні хуки, за допомогою яких можна виконати код на кожному з цих етапів. Це корисно для:
ініціалізації підключень;
запуску фонових процесів;
перевірки готовності залежностей;
закриття підключень до бази даних;
коректної зупинки черг і споживачів повідомлень;
обробки сигналів операційної системи.
Хуки реалізуються як методи відповідних інтерфейсів NestJS.
OnModuleInitМетод onModuleInit() викликається після того, як NestJS створив модуль і розв’язав його залежності.
import {
Injectable,
OnModuleInit,
} from '@nestjs/common';
@Injectable()
export class AppService implements OnModuleInit {
onModuleInit() {
console.log('Модуль ініціалізовано');
}
}Метод може бути асинхронним:
async onModuleInit() {
await this.loadConfiguration();
console.log('Конфігурацію завантажено');
}Цей хук підходить для ініціалізації конкретного провайдера. Наприклад, сервіс може підготувати внутрішній кеш або виконати початкову перевірку зовнішнього ресурсу.
OnApplicationBootstrapМетод onApplicationBootstrap() викликається після ініціалізації всіх модулів застосунку.
import {
Injectable,
OnApplicationBootstrap,
} from '@nestjs/common';
@Injectable()
export class AppService implements OnApplicationBootstrap {
onApplicationBootstrap() {
console.log('Застосунок повністю ініціалізовано');
}
}Цей хук зручний, коли дія залежить не від одного модуля, а від готовності всього застосунку.
Наприклад, запуск фонової задачі можна виконати після того, як усі необхідні провайдери вже доступні:
async onApplicationBootstrap() {
await this.startBackgroundWorker();
}NestJS підтримує три основні хуки завершення:
OnModuleDestroy;
BeforeApplicationShutdown;
OnApplicationShutdown.
OnModuleDestroyМетод onModuleDestroy() викликається під час знищення модулів.
import {
Injectable,
OnModuleDestroy,
} from '@nestjs/common';
@Injectable()
export class AppService implements OnModuleDestroy {
onModuleDestroy() {
console.log('Модуль знищується');
}
}Його можна використовувати для локального очищення ресурсів, які належать конкретному провайдеру.
async onModuleDestroy() {
await this.worker.stop();
}BeforeApplicationShutdownМетод beforeApplicationShutdown() викликається після onModuleDestroy(), але до остаточного завершення застосунку.
Метод отримує назву сигналу операційної системи, якщо завершення було спричинене сигналом:
import {
Injectable,
BeforeApplicationShutdown,
} from '@nestjs/common';
@Injectable()
export class AppService implements BeforeApplicationShutdown {
async beforeApplicationShutdown(signal?: string) {
console.log(`Підготовка до завершення. Сигнал: ${signal ?? 'невідомий'}`);
await this.flushPendingJobs();
}
private async flushPendingJobs() {
// Очікування завершення вже розпочатих задач
}
}Цей хук підходить для дій, які потрібно виконати перед закриттям з'єднань і ресурсів застосунку.
OnApplicationShutdownМетод onApplicationShutdown() викликається останнім серед хуків завершення.
import {
Injectable,
OnApplicationShutdown,
} from '@nestjs/common';
@Injectable()
export class AppService implements OnApplicationShutdown {
onApplicationShutdown(signal?: string) {
console.log(`Застосунок завершено. Сигнал: ${signal ?? 'невідомий'}`);
}
}Його можна використовувати для фінального журналювання або очищення ресурсів, які мають бути закриті в самому кінці.
Для запуску типовий порядок має такий вигляд:
створення класів і розв’язання залежностей;
onModuleInit();
onApplicationBootstrap();
запуск обробки запитів.
Для завершення порядок такий:
onModuleDestroy();
beforeApplicationShutdown();
закриття ресурсів застосунку;
onApplicationShutdown().
Якщо хук повертає Promise, NestJS очікує його виконання перед переходом до наступного етапу. Тому асинхронне очищення потрібно повертати або очікувати через await.
Graceful shutdown — це контрольоване завершення застосунку, під час якого він:
припиняє приймати нову роботу;
завершує вже розпочаті операції;
закриває підключення;
зупиняє фонові процеси;
лише після цього завершує процес.
Без graceful shutdown процес може завершитися одразу. У такому випадку поточний HTTP-запит, транзакція або повідомлення з черги можуть залишитися необробленими.
За замовчуванням NestJS не підписується на сигнали операційної системи для запуску хуків завершення. Це потрібно явно увімкнути в main.ts:
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
// Дозволяє NestJS реагувати на сигнали завершення процесу
app.enableShutdownHooks();
await app.listen(3000);
}
bootstrap();Після цього NestJS зможе запускати lifecycle hooks, коли процес отримує сигнали завершення, наприклад:
SIGINT — зазвичай надходить після натискання Ctrl+C;
SIGTERM — часто використовується Docker, Kubernetes і менеджерами процесів.
Виклик app.close() також запускає механізм завершення застосунку.
Нижче наведено приклад сервісу, який:
виконує підготовку під час запуску модуля;
запускає фоновий процес після запуску застосунку;
зупиняє його під час завершення;
обробляє сигнал операційної системи.
app.service.tsimport {
BeforeApplicationShutdown,
Injectable,
OnApplicationBootstrap,
OnModuleDestroy,
OnModuleInit,
} from '@nestjs/common';
@Injectable()
export class AppService
implements
OnModuleInit,
OnApplicationBootstrap,
OnModuleDestroy,
BeforeApplicationShutdown
{
private workerInterval?: NodeJS.Timeout;
private workerStarted = false;
async onModuleInit() {
console.log('Ініціалізація сервісу');
await this.checkDependencies();
}
async onApplicationBootstrap() {
console.log('Застосунок готовий до роботи');
this.workerStarted = true;
this.workerInterval = setInterval(() => {
console.log('Фоновий процес виконує роботу');
}, 5000);
}
async onModuleDestroy() {
console.log('Зупинка фонового процесу');
if (this.workerInterval) {
clearInterval(this.workerInterval);
this.workerInterval = undefined;
}
this.workerStarted = false;
}
async beforeApplicationShutdown(signal?: string) {
console.log(
`Підготовка до завершення. Сигнал: ${signal ?? 'невідомий'}`,
);
if (this.workerStarted) {
await this.waitForCurrentJob();
}
}
private async checkDependencies() {
// Перевірка доступності залежностей застосунку
}
private async waitForCurrentJob() {
await new Promise((resolve) => setTimeout(resolve, 500));
console.log('Поточну фонову задачу завершено');
}
}app.module.tsimport { Module } from '@nestjs/common';
import { AppService } from './app.service';
@Module({
providers: [AppService],
})
export class AppModule {}main.tsimport { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
// Увімкнення обробки SIGINT і SIGTERM
app.enableShutdownHooks();
await app.listen(3000);
console.log('HTTP-сервер запущено на порту 3000');
}
bootstrap();Після запуску застосунку в консолі можна побачити повідомлення про ініціалізацію. Якщо натиснути Ctrl+C, NestJS виконає хуки завершення в правильному порядку.
Під час завершення часто потрібно закрити асинхронні ресурси:
підключення до бази даних;
клієнт Redis;
споживач повідомлень;
WebSocket-з'єднання;
фоновий worker.
Такі операції потрібно виконувати в асинхронних хуках:
async onModuleDestroy() {
await this.database.disconnect();
}Якщо асинхронний метод не повертає Promise або не очікує операцію через await, NestJS може перейти до наступного етапу раніше, ніж ресурс буде закрито.
Орієнтуйтеся на відповідальність операції:
onModuleInit() — підготувати конкретний провайдер;
onApplicationBootstrap() — виконати дію після готовності всього застосунку;
onModuleDestroy() — локально зупинити або звільнити ресурс модуля;
beforeApplicationShutdown() — завершити поточну роботу перед фінальним закриттям;
onApplicationShutdown() — виконати фінальні дії після завершення застосунку.
Один клас може реалізовувати кілька інтерфейсів, як у наведеному прикладі. Водночас краще зберігати в кожному хуку лише відповідальність, пов'язану з його етапом життєвого циклу.
Якщо в main.ts немає:
app.enableShutdownHooks();сигнал SIGTERM або SIGINT може не запустити хуки завершення NestJS.
process.exit() усередині хукаНе варто примусово завершувати процес у onModuleDestroy() або beforeApplicationShutdown():
process.exit(0);Такий виклик може перервати асинхронне очищення і не дозволити іншим хукам виконатися.
Якщо worker обробляє повідомлення, недостатньо лише встановити прапорець зупинки. Потрібно дочекатися завершення вже розпочатої операції, якщо це можливо.
Хуки запуску можуть виконуватися під час повторної ініціалізації застосунку в тестах або спеціальних сценаріях. Ініціалізаційний код має коректно поводитися, якщо ресурс уже створено.
Graceful shutdown не повинен зависати назавжди. Для зовнішніх систем варто передбачати обмеження часу на завершення операцій і окремо контролювати завислі ресурси.
NestJS надає хуки для керування запуском і завершенням застосунку.
onModuleInit() виконується після ініціалізації залежностей модуля.
onApplicationBootstrap() виконується після готовності всіх модулів.
onModuleDestroy(), beforeApplicationShutdown() і onApplicationShutdown() використовуються для контрольованого завершення.
Для реакції на SIGINT і SIGTERM потрібно викликати app.enableShutdownHooks().
Асинхронне очищення ресурсів потрібно виконувати через async і await.
Graceful shutdown допомагає завершити поточні операції та коректно закрити ресурси перед завершенням процесу.