Async Providers

Wait for a Dependency to Resolve Before App Starts

Async Providers

A useFactory can return a Promise. Nest awaits it before the app accepts traffic — the canonical pattern for database connections.

4 min read Level 3/5 #nestjs#di#async
What you'll learn
  • Return a Promise from useFactory
  • Understand how Nest blocks bootstrap on async deps
  • Apply the pattern to DB pools and SDK clients

Some dependencies aren’t ready synchronously. A database needs to connect. A secrets manager needs an API call. Nest handles this by letting useFactory return a Promise — the framework awaits it before bootstrapping finishes.

A Promise From useFactory

The only difference from a normal factory: the function is async.

@Module({
  imports: [ConfigModule],
  providers: [
    {
      provide: 'DB_POOL',
      useFactory: async (cfg: ConfigService) => {
        const pool = new Pool({ connectionString: cfg.get('DB_URL') });
        await pool.query('SELECT 1');   // verify connectivity
        return pool;
      },
      inject: [ConfigService],
    },
  ],
  exports: ['DB_POOL'],
})
export class DatabaseModule {}

await NestFactory.create(AppModule) won’t resolve — and the HTTP server won’t start listening — until every async provider’s Promise has settled.

Why This Matters

If you start the HTTP server before the DB is ready, the first few requests land before you can serve them. Async providers move the wait to before the listening socket opens, so by the time you accept traffic, everything is connected.

// main.ts
async function bootstrap() {
  const app = await NestFactory.create(AppModule); // waits on async providers
  await app.listen(3000);
  console.log('ready'); // every dep is initialized at this point
}
bootstrap();

A Secrets-Fetching Provider

The same pattern works for any one-time init, like loading secrets from a vault.

{
  provide: 'STRIPE',
  useFactory: async (cfg: ConfigService) => {
    const key = await fetchSecret(cfg.get('STRIPE_SECRET_ARN'));
    return new Stripe(key, { apiVersion: '2025-04-30' });
  },
  inject: [ConfigService],
}

Cleanup With OnModuleDestroy

If your async provider opens connections, close them on shutdown by implementing OnModuleDestroy on a wrapper service, or returning an object with a close method and pairing it with a module-level onModuleDestroy hook.

@Injectable()
export class DatabaseLifecycle implements OnModuleDestroy {
  constructor(@Inject('DB_POOL') private readonly pool: Pool) {}

  async onModuleDestroy() {
    await this.pool.end();
  }
}

Register DatabaseLifecycle in the same module as 'DB_POOL', and Nest will call its hook on app.close() — clean startup, clean shutdown.

Circular Dependencies →