InteractiveFrameworks

Asynchronous Providers

Open a connection and load seed data in async factory providers, so Nest waits for both before the first request is served.

What you'll learn

  • Write an async factory provider and explain what Nest waits for before it constructs dependents and listens
  • Chain async providers through inject so one waits for another
  • Tell startup-time work, which belongs in an async provider, from per-request work, which does not

Some things an application needs are not ready when it starts. A database connection has to be opened, a configuration file read, a remote service asked for its schema. If handlers can run before those have finished, the first requests fail in ways that never happen again, which is the worst kind of bug to reproduce. Nest's answer is that a provider may be asynchronous: its factory returns a Promise, Nest waits for it, and nothing that depends on the provider is constructed, and the application does not listen, until the Promise has resolved.

An async factory

The syntax is the factory provider from the last lesson with async in front:

{
  provide: 'ASYNC_CONNECTION',
  useFactory: async () => {
    const connection = await createConnection(options);
    return connection;
  },
}

That is the whole feature. The factory can await anything. What it resolves to is the provider, and @Inject('ASYNC_CONNECTION') receives the resolved value, never the Promise. Classes that inject it are constructed only after it resolved, so their constructors can use it at once.

Waiting is transitive

Async providers compose through inject. A factory that needs a resolved async provider lists its token, and Nest resolves the chain in order:

{
  provide: 'SCHEMA',
  useFactory: async (connection: Connection) => connection.loadSchema(),
  inject: ['ASYNC_CONNECTION'],
},
{
  provide: Repository,
  useFactory: (connection: Connection, schema: Schema) => new Repository(connection, schema),
  inject: ['ASYNC_CONNECTION', 'SCHEMA'],
},

SCHEMA waits for the connection; Repository waits for both. Independent async providers resolve concurrently, so two slow factories do not add up. The console shows the effect: Nest application successfully started prints only after the last one resolved, and the boot time it reports includes the waiting.

What it is for, and what it is not

An async provider runs its factory once, when the module initialises. It is the place for work that must happen before the first request and never again: connecting, loading, warming a cache. It is not the place for anything per request, which belongs in a handler or a request-scoped provider, and not for work that can fail and should be retried while the application keeps serving. Lesson 9 shows onModuleInit, a lifecycle hook that can do the same waiting inside a class, which is the better fit when the class already exists as a provider and only needs a moment to get ready.

Your task

The cats now sit behind a Connection that has to be opened, and the shelter starts with cats that loadSeed() delivers late. Both are asynchronous, and the starter treats neither as such.

  1. The CONNECTION factory hands the connection over unopened, so every request answers 503. Open it in the factory, and make Nest wait for that before anything uses it.
  2. Provide the seed under SEED_CATS, loaded with loadSeed(), and give it to the repository's factory so the shelter starts with those cats.

Watch the console: the connection's own line prints before Nest reports the application started, because the start waited for it.

When it fails

  • Every request answers 503 the connection to sqlite://cats is not open: the factory returned the connection without awaiting open(), or awaited it without async, which TypeScript rejects.
  • GET /cats lists no cats at first: the repository was built with an empty seed. The factory needs the seed's token in inject, and the seed itself needs a provider.
  • Nest can't resolve dependencies of the CatsRepository (CONNECTION, Symbol(CATS_CONFIG), ?): the factory lists SEED_CATS in inject but nothing provides it yet.
  • The repository receives a Promise: the seed factory is not async, so it returned loadSeed()'s Promise as a plain value and Nest did not wait. Only a factory marked async (or returning a Promise Nest can see) is awaited.

Remember

  • An async factory's resolved value is the provider; Nest waits before constructing anything that depends on it, and before listening.
  • Async providers chain through inject, and independent ones resolve concurrently.
  • One run at startup: connect, load, warm. Not per request.
Stuck? Show a hint

Make the CONNECTION factory async: create the connection, await its open(), return it. Add a provider for 'SEED_CATS' whose async factory returns loadSeed(). Then add 'SEED_CATS' to the repository factory's inject list and pass the third argument on to the constructor.