Appearance
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:
- Create a class that extends
BaseMiddleware
- Override the
run
method to implement your middleware logic - 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:
- Create a class that extends
BaseAuthMiddleware
- Implement the abstract
getUserByTelegramId
method - Optionally override
validateUser
andhandleUserNotFound
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
Always
await next()
: If you callnext()
without awaiting, several issues can occur:- Middleware stack execution in wrong order
- Data loss
- Messages not being sent
- Random crashes
- Unhandled Promise rejection warnings
Middleware Order Matters: Middleware is executed in the order it's registered. Register more general middleware (like authentication) before specific ones.
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.