Skip to content

fix(testing): auto-init fastify adapter for middleware registration #15385

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged

Conversation

mag123c
Copy link
Contributor

@mag123c mag123c commented Jul 14, 2025

PR Checklist

Please check if your PR fulfills the following requirements:

PR Type

What kind of change does this PR introduce?

  • Bugfix
  • Feature
  • Code style update (formatting, local variables)
  • Refactoring (no functional changes, no api changes)
  • Build related changes
  • CI related changes
  • Other... Please describe:

What is the current behavior?

Issue Number: #15310

When using the FastifyAdapter in NestJS testing scenarios, calling app.use() to register middleware before app.init() throws the following error:

TypeError: this.instance.use is not a function

This occurs because FastifyAdapter requires the @fastify/middie plugin to be registered before middleware can be used, but this registration only happens during the adapter's init() method. In testing environments, users can call app.use() immediately after creating the application but before app.init(), leading to this runtime error.

What is the new behavior?

The TestingModule now automatically calls adapter.init() when creating a NestApplication if the adapter has an init method.
This ensures that adapters like FastifyAdapter are properly initialized before being used.

Does this PR introduce a breaking change?

  • Yes
  • No

@coveralls
Copy link

coveralls commented Jul 14, 2025

Pull Request Test Coverage Report for Build 481bc0ec-33a8-4162-8bf2-c5199bf1f5fa

Details

  • 0 of 0 changed or added relevant lines in 0 files are covered.
  • 238 unchanged lines in 15 files lost coverage.
  • Overall coverage decreased (-0.02%) to 88.918%

Files with Coverage Reduction New Missed Lines %
packages/core/injector/modules-container.ts 1 80.0%
packages/core/injector/internal-core-module/internal-core-module-factory.ts 2 81.82%
packages/microservices/server/server.ts 5 92.86%
packages/websockets/web-sockets-controller.ts 5 87.67%
packages/core/injector/container.ts 8 92.17%
packages/microservices/server/server-kafka.ts 10 86.33%
packages/microservices/server/server-nats.ts 11 75.24%
packages/microservices/server/server-mqtt.ts 12 82.73%
packages/microservices/server/server-tcp.ts 12 75.95%
packages/core/injector/module.ts 17 85.14%
Totals Coverage Status
Change from base Build 883894a2-4ab3-459b-a637-5228b4bad30e: -0.02%
Covered Lines: 7197
Relevant Lines: 8094

💛 - Coveralls

Comment on lines 84 to 95
if (typeof (httpAdapter as any)?.init === 'function') {
const originalInit = (proxy as any).init;
(proxy as any).init = async function (this: any) {
await (httpAdapter as any).init();
if (originalInit) {
return originalInit.call(this);
}
return this;
};
}

return proxy;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we move this to a dedicated method? (patchAppProxyInit?)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extracted to patchAppProxyInit method as requested!

@mag123c
Copy link
Contributor Author

mag123c commented Jul 14, 2025

@kamilmysliwiec I've extracted the logic to patchAppProxyInit method as requested.
However, after further investigation, I found that this approach doesn't solve the original issue.

As the issue reporter @yawhide also noted, the fundamental problem is that:

  • FastifyAdapter's init() must be called to register the middleware plugin
  • But init() is async and createNestApplication() is sync
  • We can't initialize the adapter before returning from createNestApplication()

The current implementation only wraps the proxy's init() method, which means the adapter still isn't initialized when app.use() is called, resulting in the same error.

I apologize for not catching this limitation earlier. The tests are indeed failing with the same error.

Given that making createNestApplication() async would be a breaking change (as the issue reporter mentioned), perhaps the best approach is to:

  1. Document that await app.init() must be called before app.use() when using FastifyAdapter in tests
  2. Consider this for a future major version where breaking changes are acceptable

OR, as an alternative approach without breaking changes:
We could add a new async method that handles initialization automatically:

public async createNestApplicationAsync<T extends INestApplication = INestApplication>(
  httpAdapter?: HttpServer | AbstractHttpAdapter,
  options?: NestApplicationOptions,
): Promise<T> {
  const app = this.createNestApplication(httpAdapter, options);
  const adapter = app.getHttpAdapter();

  // Use the extracted patchAppProxyInit logic here
  if (typeof (adapter as any)?.init === 'function') {
    await (adapter as any).init();
  }

  return app;
}

This would allow users to write cleaner test code:

const app = await module.createNestApplicationAsync(new FastifyAdapter());
app.use(middleware); // Now works without calling app.init() first

What do you think? Should I update the PR with this approach instead?
I've tested this approach and all test cases pass successfully.

@kamilmysliwiec
Copy link
Member

What about modifying the fastify adapter's use method to check whether the use is present on the instance, and if not - logging that down instructing users to call init() first?

@mag123c mag123c force-pushed the fix/fastify-adapter-middleware-init branch from 66c6803 to 8b1dd91 Compare July 15, 2025 01:00
@mag123c
Copy link
Contributor Author

mag123c commented Jul 15, 2025

This change doesn't enable using app.use() without app.init(), but provides clear guidance to developers.

The core issue is that Fastify's middleware registration is inherently asynchronous (requires @fastify/middie plugin initialization), while Express allows synchronous middleware registration. To enable use() before init(), we would need to create async wrapper methods, which would change the API surface.

  1. The current solution improves error messages, but should we consider matching the Fastify test experience with Express? Currently, Express doesn't throw errors when calling app.use() before initialization.

  2. We could also auto-initialize adapters using the createNestApplicationAsync approach I provided earlier. This would improve DX by making Fastify behavior consistent with Express in testing. However, this might be a significant change, so I'm curious about your opinion.

If either of these issues (or both) need to be addressed, I'm happy to create a separate issue to discuss further.

Comment on lines 674 to 685
// Fastify requires @fastify/middie plugin to be registered before middleware can be used.
// Unlike Express, middleware registration in Fastify must happen after initialization.
// We provide a helpful error message to guide developers to call app.init() first.
if (!this.isMiddieRegistered) {
Logger.warn(
'Middleware registration requires the "@fastify/middie" plugin to be registered first. ' +
'Make sure to call app.init() before registering middleware with the Fastify adapter. ' +
'See https://github.com/nestjs/nest/issues/15310 for more details.',
FastifyAdapter.name,
);
throw new TypeError('this.instance.use is not a function');
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in theory we could also lazily register middleware in case middie is not yet registered

example:

  • if use() is called and middie is not registered
  • add the middleware to the internal "middleware" cache (preserve the registration order)
  • once middie is registered (upon the "init()" call), check if that cache is not empty and if so, flush it

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the suggestion! Implemented the lazy middleware registration approach as you recommended.

@kamilmysliwiec
Copy link
Member

Thank you @mag123c! LGTM

@kamilmysliwiec kamilmysliwiec merged commit b34d428 into nestjs:master Jul 16, 2025
3 of 4 checks passed
Copy link

@Noxcheborz Noxcheborz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants