How to Use Middleware in Telego Handlers: A Complete Guide
Telego implements middleware as Handler functions that intercept updates via ctx.Next(update), allowing you to compose reusable logic like logging, recovery, and timeouts that execute before your main handlers.
The Telego library (mymmrac/telego) provides a robust handler system for building Telegram bots in Go. Its middleware architecture follows a chain-of-responsibility pattern where each middleware can modify the context, terminate execution, or pass control to the next layer.
Understanding the Middleware Pattern in Telego
In Telego, middleware is not a separate interface but rather the same Handler type used for final update processing. This unified design simplifies the API while enabling powerful composition patterns.
The Handler Signature
Every middleware function conforms to the Handler signature defined in telegohandler/handler_group.go:
func(ctx *telegohandler.Context, update telego.Update) error
The *Context parameter provides access to the bot instance, the current update, and control methods for the execution chain. When your middleware finishes its work, it returns an error to signal whether processing should continue or halt.
The Chain of Responsibility
Middleware executes sequentially based on registration order. The critical mechanism that enables this chaining is the ctx.Next(update) method found in telegohandler/context.go.
When your middleware calls ctx.Next(update), it yields control to the next middleware in the chain or the final handler. If your middleware returns without invoking Next, the chain terminates immediately—subsequent middlewares and handlers never execute. This behavior allows middleware to act as gatekeepers, short-circuiting requests that fail authentication, rate limiting, or validation checks.
Registering Middleware with Use
You attach middleware to the handler system through the Use method available on BotHandler and handler groups. According to telegohandler/bot_handler.go and telegohandler/handler_group.go, this method appends middleware to an ordered slice that the execution engine traverses during update processing.
The Use signature accepts variadic Handler arguments:
func (h *BotHandler) Use(middlewares ...Handler)
Global Middleware Registration
To apply middleware to every incoming update, register it on the base BotHandler instance:
handler, _ := telegohandler.NewBotHandler(bot, updates)
// Apply to all updates
handler.Use(
telegohandler.PanicRecovery(),
CustomLoggingMiddleware,
)
Registration order determines execution order. In this example, PanicRecovery executes first, then CustomLoggingMiddleware, and finally the matched handler.
Built-in Middleware Options
Telego provides production-ready middleware implementations in telegohandler/middleware.go that handle common cross-cutting concerns.
PanicRecovery
The PanicRecovery middleware catches panics within the handler chain, logs the stack trace, and prevents the bot from crashing. It returns the panic as an error to the caller, allowing the update processing loop to continue with subsequent updates.
handler.Use(telegohandler.PanicRecovery())
This is essential for production deployments where unhandled panics in one update handler could otherwise terminate the entire bot process.
Timeout
The Timeout middleware enforces a maximum execution duration for the entire handler chain. It wraps the context with a deadline, ensuring that slow handlers or hanging external calls do not block the update processing pipeline indefinitely.
handler.Use(telegohandler.Timeout(5 * time.Second))
When the timeout expires, the context cancellation propagates through the chain, allowing handlers to abort database queries or HTTP requests gracefully.
Creating Custom Middleware
Custom middleware follows the same Handler signature as final handlers but utilizes ctx.Next to continue the chain. This pattern enables you to inject logging, metrics, authentication, or transformation logic.
Logging and Timeout Middleware Example
The following implementation demonstrates a middleware that logs update IDs and applies a 2-second timeout to subsequent processing, adapted from the patterns in telegohandler/context.go:
func LogAndTimeout(next telegohandler.Handler) telegohandler.Handler {
return func(ctx *telegohandler.Context, upd telego.Update) error {
// Log incoming update
ctx.Bot().Logger().Infof("Processing update %d", upd.UpdateID)
// Apply timeout to the rest of the chain
ctx, cancel := ctx.WithTimeout(2 * time.Second)
defer cancel()
// Continue to next middleware or handler
return next(ctx, upd)
}
}
Register this middleware using the Use method to apply it globally, or attach it to specific handler groups for targeted protection.
Conditional Short-Circuiting
Middleware can terminate the chain early by returning without calling next. This is useful for rate limiting or access control:
func RateLimit(next telegohandler.Handler) telegohandler.Handler {
return func(ctx *telegohandler.Context, upd telego.Update) error {
if isRateLimited(upd.Message.From.ID) {
// Stop chain execution; handler never runs
return nil
}
return next(ctx, upd)
}
}
Scoped Middleware with Handler Groups
Telego supports hierarchical handler groups that allow middleware to apply only to specific subsets of handlers. The Group method, defined in telegohandler/handler_group.go, creates a new HandlerGroup that inherits middleware from its parent but can register its own.
Creating Isolated Groups
// Create a group for admin commands
adminGroup := handler.Group()
adminGroup.Use(AdminAuthMiddleware)
// This handler runs with AdminAuthMiddleware
adminGroup.Handle(func(ctx *telegohandler.Context, upd telego.Update) error {
// Admin-only logic
return ctx.Bot().SendMessage(telego.SendMessageParams{
ChatID: upd.Message.Chat.ID,
Text: "Admin command executed",
})
})
Middleware Execution Order
When processing an update, the execution order follows the group's nesting hierarchy:
- Global middleware (registered on base
BotHandler) - Parent group middleware
- Child group middleware
- Final handler
This cascading structure allows you to compose reusable security, logging, and transformation layers that apply only where needed without polluting the global namespace.
Summary
- Middleware is a Handler: Telego treats middleware as standard
Handlerfunctions with the signaturefunc(ctx *telegohandler.Context, update telego.Update) error, unifying the API surface. - Chaining via Next: Middleware controls execution flow through
ctx.Next(update); omitting this call short-circuits the chain, preventing subsequent handlers from running. - Registration with Use: Attach middleware globally or to specific groups using the
Usemethod, which appends handlers to an ordered slice processed sequentially. - Built-in Protection: Leverage
PanicRecoveryandTimeoutfromtelegohandler/middleware.goto harden production bots against crashes and hanging operations. - Hierarchical Scoping: Create isolated middleware layers using
Group()to apply authentication, logging, or rate limiting only to specific handler subsets without affecting global routing.
Frequently Asked Questions
How does middleware execution order work in Telego?
Middleware executes in the exact order it was registered via the Use method. When an update arrives, the Context.Next method iterates through this ordered slice, invoking each middleware sequentially. If a middleware calls ctx.Next(update), control passes to the next element; if it returns without calling Next, the chain terminates immediately and no subsequent middleware or handlers execute.
What is the difference between a middleware and a handler in Telego?
There is no structural difference—both conform to the Handler signature func(ctx *telegohandler.Context, update telego.Update) error. The distinction is behavioral: middleware is designed to intercept updates and delegate to the next layer via ctx.Next(update), while final handlers typically process the update directly without calling Next. This unified design allows the same function to act as middleware in one context and a final handler in another.
Can I apply middleware to only specific commands or message types?
Yes, through the handler group hierarchy. Instead of registering middleware globally on the BotHandler instance, create a subgroup using the Group() method and attach middleware to that specific group using Use. Only handlers registered within that group or its descendants will execute that middleware. This pattern is ideal for isolating authentication, administrative checks, or specialized logging to specific command subsets.
How do I prevent a handler from running if middleware validation fails?
Return from the middleware without invoking ctx.Next(update). When middleware returns nil or an error without calling Next, the Context.Next execution engine stops traversing the chain immediately. Subsequent middlewares and the final handler never receive the update. This short-circuit pattern is commonly used for rate limiting, IP blocking, or permission denial, where the middleware consumes the update and optionally sends an error message to the user.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →