typescriptnestjsfastifynestjs-fastifyfastify-cookie

Apply fastifySession Middleware in nest js - Using the MiddlewareConsumer in the AppModule


I have a Nestjs app with Fastify. I want to apply the fastifySession middleware using the MiddlewareConsumer. Usually, it works like below:

configure(consumer: MiddlewareConsumer) {
    consumer
      .apply(
        fastifySession,
      )
      .forRoutes({
        path: '*',
        method: RequestMethod.ALL,
      });
  }
}

The problem is that fastifySession needs an options object. In a regular app, it is called using the register method like below:

app.register(fastifySession, {
   secret: '',
   cookie: {
     secure: false,
     domain: 'localhost',
   },
  store: new SessionStore(new SessionService()),
});

I don't want to use the register method in main.ts as I want to make use of the Nestjs dependency injection. So, I need to apply the middleware in the AppModule. Is there a way to do this?

UPDATE

I thought I can develop a Nestjs middleware to register the fastify plugins that I need.

I created this middleware:


@Injectable()
class FastifySession implements NestMiddleware {
  private options;
  private fastifyPassport;

  constructor(
    private adapterHost: HttpAdapterHost,
    private sessionStore: SessionStore,
    private userService: UserService,
  ) {
    this.fastifyPassport = new Authenticator();

    this.options = {
      cookie: {
        secure: false,
        maxAge: 50000,
        path: '/',
        httpOnly: true,
        sameSite: false,
        domain: 'localhost',
      },
      store: this.sessionStore,
    };
  }

  use(req: any, res: any, next: (error?: any) => void) {
    const httpAdapter = this.adapterHost.httpAdapter;
    const instance = httpAdapter.getInstance();

    instance.register(fastifyCookie);
    instance.register(fastifySession, this.options);

    instance.register(this.fastifyPassport.initialize());
    instance.register(this.fastifyPassport.secureSession());

    this.fastifyPassport.registerUserSerializer(async (user: User, request) => {
      console.log(user.id);
      return user.id;
    });

    this.fastifyPassport.registerUserDeserializer(async (id, request) => {
      const user = await this.userService.getUser(+id);
      console.log('user ', user);
      return user;
    });

    next();
  }
}

I added the middleware to my AppModule

export class AppModule implements NestModule {
  constructor() {
  }

  configure(consumer: MiddlewareConsumer) {
    consumer
      .apply(FastifySession)
      .forRoutes({
        path: '*',
        method: RequestMethod.ALL,
      });
  }
}

But I get his error

ERROR [ExceptionsHandler] Root plugin has already booted
AvvioError: Root plugin has already booted

I came across this GitHub Issue

https://github.com/nestjs/nest/issues/1462

As stated in the GitHub issue, I think It is not possible to register Fastify plugins outside main.ts.

I will appreciate anyone helping me! or at least guide me in the right direction.


Solution

  • To register a plugin inside a module, I would use onModuleInit and a module that injects the HttpAdapterHost. Something like this:

    @Module(moduleMetadata)
    export class AppModule implements OnMoudleInit {
      constructor(private readonly httpAdapterHost: HttpAdapterHost) {}
    
      async onModuleInit() {
        const adapterInstance = this.httpAdapterHost.httpAdapter.getInstance();
        await adapterInstance.register(fastifySession, fastifySessionOptions)
      }
    }
    

    This way, the adapter registers the middleware at application startup and you don't accidentally end up registering it multiple times on each route call (use will be called on each matching route and you end up calling instance.register() a lot with the current code in the question)