Не всё происходит внутри запроса: что-то готовится на старте асинхронно, что-то идёт по расписанию, что-то выносится в фон. NestJS даёт инструменты для каждого случая, но главный вопрос не «как», а «где»: важная фоновая работа должна жить вне процесса приложения, а не «вдогонку» после ответа.
Async-провайдеры
Иногда провайдер нельзя создать синхронно — нужно дождаться соединения или загрузить конфигурацию. Тогда используют асинхронную фабрику.
{
provide: 'CACHE',
useFactory: async (config: ConfigService) => {
const client = createClient({ url: config.getOrThrow('REDIS_URL') });
await client.connect();
return client;
},
inject: [ConfigService],
}
NestJS дождётся завершения фабрики, прежде чем поднять зависящие от неё компоненты. Закрытие такого ресурса вешают на lifecycle-хук.
Расписание
Регулярные задачи по времени — через @nestjs/schedule: ScheduleModule.forRoot() в модуле и декораторы @Cron/@Interval на методах провайдера.
import { Injectable } from '@nestjs/common';
import { Cron } from '@nestjs/schedule';
@Injectable()
export class ReportTasks {
@Cron('0 3 * * *')
async buildDailyReport() { /* ... */ }
}
Здесь важна ловушка масштабирования: если приложение запущено в нескольких экземплярах, @Cron сработает в каждом — задача выполнится столько раз, сколько реплик. Для одно-экземплярных вспомогательных задач это терпимо; для важных регулярных задач расписание выносят наружу (CronJob оркестратора или выделенный планировщик), чтобы оно было в одном месте.
Очереди: надёжный фон
Когда фоновая работа важна (её нельзя терять), тяжела или должна повторяться при сбое, её выносят в очередь. Стандарт для NestJS — BullMQ (через @nestjs/bullmq, на Redis): приложение кладёт задачу, отдельный воркер её берёт, выполняет, при сбое повторяет.
import { Injectable } from '@nestjs/common';
import { InjectQueue } from '@nestjs/bullmq';
import { Queue } from 'bullmq';
@Injectable()
export class ImportService {
constructor(@InjectQueue('imports') private readonly queue: Queue) {}
async enqueue(fileId: string): Promise<void> {
await this.queue.add('process-import', { fileId });
}
}
Обработчик задачи — отдельный процессор (воркер), а не код в обработчике запроса. Контроллер, поставив задачу, отвечает сразу — честным 202 Accepted («принято в обработку»), а не 200 («сделано»). За надёжность отвечает очередь: переживёт рестарт, повторит при сбое.
Граница: фон — отдельный путь
Главное правило для методологии: фоновая работа — это отдельный путь, а не спрятанный хвост обработчика запроса. У неё своя точка входа (постановка в очередь), свои гарантии (durable, с повтором), своя наблюдаемость. Прятать важную операцию «после ответа» в процессе приложения — значит менять надёжность на скорость молча, и однажды это всплывёт потерянными задачами.
Поэтому выбор делают осознанно: лёгкое и необязательное можно сделать в фоне процесса; важное, тяжёлое или повторяемое — в очередь. Это то же инфраструктурное решение «в памяти процесса или в durable-системе», что и при работе с брокерами в Spring-биндинге, и принимает его продукт-инженер, а не оставляет на «как получилось».