Skip to content

grammY Middleware in NestJS

Overview

Middleware in grammY is a core concept that allows you to process Telegram updates through a series of functions before they reach your main handlers. In the NestJS wrapper provided by this library, middleware is implemented in a class-based architecture that integrates with NestJS dependency injection system.

Understanding Middleware

In grammY, middleware functions take the form:

typescript
async (ctx, next) => {
  // Do something before downstream middleware
  await next();
  // Do something after downstream middleware
}

The next function invokes the next middleware in the stack. If you don't call next(), the processing stops at the current middleware.

Base Middleware Implementation

The NestKit provides BaseMiddleware as the foundation for all middleware:

typescript
@Injectable()
export class BaseMiddleware implements OnModuleInit {
  // ...

  onModuleInit() {
    this.logger = this.loggerFactory.create({
      scope: this.constructor.name,
    });

    this.logger.debug(`Registering middleware: ${this.constructor.name}`);
    this.bot.use(this.handler);
  }

  get handler() {
    return async (ctx: GrammyConversationContext, next: NextFunction) => {
      await this.run(ctx, next);
    };
  }

  async run(ctx: GrammyConversationContext, next: NextFunction): Promise<void> {
    await ctx.reply('This middleware is not implemented yet');
    await next();
  }
}

The onModuleInit lifecycle hook automatically registers the middleware with the bot instance when the NestJS module initializes.

Creating Custom Middleware

To create custom middleware:

  1. Create a class that extends BaseMiddleware
  2. Override the run method to implement your middleware logic
  3. Make sure to call await next() if you want to continue the middleware chain

Example:

typescript
@Injectable()
export class LoggingMiddleware extends BaseMiddleware {
  async run(ctx: GrammyConversationContext, next: NextFunction): Promise<void> {
    // Do something before downstream middleware
    const before = Date.now();
    
    // Call next middleware in the stack
    await next();
    
    // Do something after downstream middleware
    const after = Date.now();
    this.logger.debug(`Request processed in ${after - before}ms`);
  }
}

Authentication Middleware

The library provides a BaseAuthMiddleware for handling user authentication:

typescript
@Injectable()
export abstract class BaseAuthMiddleware<U extends BaseUser = BaseUser> extends BaseMiddleware<U> {
  async run(ctx: GrammyConversationContext, next: NextFunction): Promise<void> {
    // Authentication logic
    // ...
    
    await next();
  }
  
  // Abstract methods that must be implemented
  abstract getUserByTelegramId(telegramId: number): Promise<MaybeNull<U>>;
  
  // Optional methods with default implementation
  async validateUser(ctx: GrammyConversationContext, user: U): Promise<boolean> {
    return true;
  }
  
  async handleUserNotFound(ctx: GrammyConversationContext): Promise<void> {
    await ctx.reply('Sorry but I can\'t find you in the system.');
  }
}

To implement authentication:

  1. Create a class that extends BaseAuthMiddleware
  2. Implement the abstract getUserByTelegramId method
  3. Optionally override validateUser and handleUserNotFound

Example:

typescript
@Injectable()
export class AuthMiddleware extends BaseAuthMiddleware<User> {
  constructor(
    @Inject(UserService) private userService: UserService,
  ) {
    super();
  }
  
  async getUserByTelegramId(telegramId: number): Promise<User | null> {
    return this.userService.findByTelegramId(telegramId);
  }
  
  async validateUser(ctx: GrammyConversationContext, user: User): Promise<boolean> {
    return user.isActive;
  }
}

Registering Middleware

Middleware is automatically registered when you add it as a provider to your bot module:

typescript
@Module({
  imports: [
    UsersModule,
  ],
  providers: [
    // Middlewares
    AuthMiddleware,
    LoggingMiddleware,
    
    // ...other providers...
  ],
})
export class BotModule {}

You don't need to manually call bot.use() as the onModuleInit hook in the BaseMiddleware class handles this for you.

For conversation middleware specifically, you also need to provide them in the GRAMMY_CONVERSATION_PLUGINS token:

typescript
@Module({
  providers: [
    // Middlewares
    AuthMiddleware,
    
    // Register middleware for conversations
    {
      provide: GRAMMY_CONVERSATION_PLUGINS,
      useValue: [
        AuthMiddleware,
      ],
    },
    
    // ...other providers...
  ],
})
export class BotModule {}

Important Notes

  1. Always await next(): If you call next() without awaiting, several issues can occur:

    • Middleware stack execution in wrong order
    • Data loss
    • Messages not being sent
    • Random crashes
    • Unhandled Promise rejection warnings
  2. Middleware Order Matters: Middleware is executed in the order it's registered. Register more general middleware (like authentication) before specific ones.

  3. Dependency Injection: All middleware classes can use NestJS dependency injection to access services.

Performance Monitoring Example

Here's a complete example of a middleware that monitors bot response times:

typescript
@Injectable()
export class ResponseTimeMiddleware extends BaseMiddleware {
  async run(ctx: GrammyConversationContext, next: NextFunction): Promise<void> {
    const before = Date.now();
    
    await next();
    
    const after = Date.now();
    this.logger.debug(`Response time: ${after - before}ms`);
  }
}

Register this middleware early in your application to measure the full processing time.

Created by DeepVision Software.