diff --git a/integration/hello-world/e2e/fastify-middleware-before-init.spec.ts b/integration/hello-world/e2e/fastify-middleware-before-init.spec.ts new file mode 100644 index 00000000000..cf22878852e --- /dev/null +++ b/integration/hello-world/e2e/fastify-middleware-before-init.spec.ts @@ -0,0 +1,135 @@ +import { + Controller, + Get, + Injectable, + MiddlewareConsumer, + Module, + NestModule, +} from '@nestjs/common'; +import { + FastifyAdapter, + NestFastifyApplication, +} from '@nestjs/platform-fastify'; +import { Test } from '@nestjs/testing'; +import { expect } from 'chai'; + +describe('Middleware before init (FastifyAdapter)', () => { + let app: NestFastifyApplication; + + @Injectable() + class TestService { + getData(): string { + return 'test_data'; + } + } + + @Controller() + class TestController { + constructor(private readonly testService: TestService) {} + + @Get('test') + test() { + return { data: this.testService.getData() }; + } + + @Get('health') + health() { + return { status: 'ok' }; + } + } + + @Module({ + controllers: [TestController], + providers: [TestService], + }) + class TestModule implements NestModule { + configure(consumer: MiddlewareConsumer) { + consumer + .apply((req, res, next) => { + res.setHeader('x-middleware', 'applied'); + next(); + }) + .forRoutes('*'); + } + } + + describe('should queue middleware when registered before init', () => { + beforeEach(async () => { + const module = await Test.createTestingModule({ + imports: [TestModule], + }).compile(); + + app = module.createNestApplication( + new FastifyAdapter(), + ); + + // Register middleware before init - should be queued + app.use((req, res, next) => { + res.setHeader('x-global-middleware', 'applied'); + next(); + }); + + // Now init the app - queued middleware should be registered + await app.init(); + await app.getHttpAdapter().getInstance().ready(); + }); + + it('should apply queued middleware after init', () => { + return app + .inject({ + method: 'GET', + url: '/test', + }) + .then(({ statusCode, payload, headers }) => { + expect(statusCode).to.equal(200); + expect(JSON.parse(payload)).to.deep.equal({ data: 'test_data' }); + // Verify both module-level and global middleware were applied + expect(headers['x-middleware']).to.equal('applied'); + expect(headers['x-global-middleware']).to.equal('applied'); + }); + }); + + afterEach(async () => { + await app.close(); + }); + }); + + describe('should work when app is initialized before middleware registration', () => { + beforeEach(async () => { + const module = await Test.createTestingModule({ + imports: [TestModule], + }).compile(); + + app = module.createNestApplication( + new FastifyAdapter(), + ); + + // Initialize app first + await app.init(); + + // Now middleware registration should work + app.use((req, res, next) => { + res.setHeader('x-global-middleware', 'applied'); + next(); + }); + + await app.getHttpAdapter().getInstance().ready(); + }); + + it('should register middleware successfully after init', () => { + return app + .inject({ + method: 'GET', + url: '/test', + }) + .then(({ statusCode, payload }) => { + expect(statusCode).to.equal(200); + expect(JSON.parse(payload)).to.deep.equal({ data: 'test_data' }); + }); + }); + + afterEach(async () => { + await app.close(); + }); + }); +}); diff --git a/packages/platform-fastify/adapters/fastify-adapter.ts b/packages/platform-fastify/adapters/fastify-adapter.ts index 370a4e4c26f..c2650d22736 100644 --- a/packages/platform-fastify/adapters/fastify-adapter.ts +++ b/packages/platform-fastify/adapters/fastify-adapter.ts @@ -146,6 +146,7 @@ export class FastifyAdapter< private _isParserRegistered: boolean; private isMiddieRegistered: boolean; + private pendingMiddlewares: Array<{ args: any[] }> = []; private versioningOptions?: VersioningOptions; private readonly versionConstraint = { name: 'version', @@ -256,6 +257,14 @@ export class FastifyAdapter< return; } await this.registerMiddie(); + + // Register any pending middlewares that were added before init + if (this.pendingMiddlewares.length > 0) { + for (const { args } of this.pendingMiddlewares) { + (this.instance.use as any)(...args); + } + this.pendingMiddlewares = []; + } } public listen(port: string | number, callback?: () => void): void; @@ -670,6 +679,16 @@ export class FastifyAdapter< return 'fastify'; } + public use(...args: any[]) { + // Fastify requires @fastify/middie plugin to be registered before middleware can be used. + // If middie is not registered yet, we queue the middleware and register it later during init. + if (!this.isMiddieRegistered) { + this.pendingMiddlewares.push({ args }); + return this; + } + return (this.instance.use as any)(...args); + } + protected registerWithPrefix( factory: | FastifyPluginCallback