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.
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.