Middleware and `next()` in grammY
Welcome to a new module! In the last few lessons, we mastered building complex interactive UIs using grammY's powerful menu plugin. You saw how a single line, bot.use(mainMenu), could replace a significant amount of manual logic for handling callback queries and message edits. That single line is your entry point into one of the most powerful concepts in grammY and modern web frameworks: middleware.
Today, we begin our exploration of advanced control flow. This lesson focuses on the theoretical foundation you'll need for the rest of the module. We will dissect grammY's middleware execution model and the pivotal role of the next() function. Given your extensive background in front-end development, you've likely encountered similar middleware patterns in frameworks like Express.js or Koa.js. Our goal is to map that existing knowledge to grammY's specific implementation, giving you a deep understanding of how the framework processes updates from start to finish.
What Is Middleware? The 30,000-Foot View
At its core, middleware is a chain of functions that a request, or in our case, a Telegram Update, passes through on its journey from being received by your bot to a response being sent back. Each function in the chain gets a chance to inspect the update, modify it, add data to it, or even stop it in its tracks.
A great way to visualize this is to think of airport security. A passenger (the update) doesn't go straight to the plane (your final logic). They first go through ticketing, then baggage check, then security screening. Each checkpoint is a piece of middleware.

This concept is almost universal in modern back-end frameworks. The following video explains middleware in the context of Express.js, and the core ideas are directly transferable to grammY.
Why Every Developer Needs to Understand Middleware
Watch this short segment from the video "Why Every Developer Needs to Understand Middleware" for a crisp, high-level analogy.
Focus on the airport analogy to solidify the concept of middleware as a series of processing layers.
In grammY, almost everything you've used so far is middleware. Handlers you register with bot.command("start", ...) or bot.on("message:text", ...) are just middleware functions attached to specific filters. The menu plugin you installed with bot.use(mainMenu) is also a sophisticated piece of middleware.
The Middleware Function: ctx and next
A grammY middleware function has a specific signature. Let's look at the formal definition from the documentation.
type MiddlewareFn = (ctx: Context, next: NextFunction) => MaybePromise<unknown>;
type NextFunction = () => Promise<void>;
You are already well-acquainted with the first parameter, ctx, the all-important Context object that holds the update, API methods, and other useful data.
The second parameter, next, is the key to understanding the execution flow. It is a function that, when called, passes control to the next middleware in the processing chain. If a middleware function doesn't call next(), the chain is broken, and no further middleware will be executed for that update. This allows middleware to act as a gatekeeper or to handle the update completely.
The following reading from the official grammY documentation formally introduces the middleware stack and the next function.
This guide explains how grammY organizes middleware into a stack and the importance of the order in which you register your handlers.
Read the introduction and the section on "The Middleware Stack". Pay close attention to the MiddlewareFn type definition and the explanation of how next is used to invoke downstream middleware. The example showing why the order of bot.on(":text") and bot.command("start") matters is crucial.
As you can see, the order of registration defines the order of execution. A bot.use() handler registered at the top will see every update first, making it ideal for cross-cutting concerns like logging, session management, or authentication.
The "Russian Doll" Execution Model
Here is where the real power of middleware becomes apparent. The next() function returns a Promise, which means you should always await it. This async/await structure creates an execution model that can be visualized as nested Russian dolls or an onion.
A piece of middleware can execute code before passing control down the chain, and it can also execute code after the entire downstream chain has completed.

Let's break down the flow:
- An update enters
Middleware A. Middleware Aruns its "pre-processing" code.Middleware Acallsawait next(), passing control toMiddleware B.Middleware Bruns its "pre-processing" code.Middleware Bcallsawait next(), passing control to your main handler (e.g.,bot.command).- Your main handler runs and completes.
- Control returns to
Middleware B, which now executes its "post-processing" code (the code afterawait next()). - When
Middleware Bcompletes, control returns toMiddleware A, which runs its "post-processing" code.
This pattern is extremely useful. For example, you can start a timer before await next() and stop it after, allowing you to measure the response time of your handlers.
Let's see this in action by studying a practical example and a critical warning from the grammY documentation.
This part of the documentation provides a perfect example of the "Russian Doll" pattern and contains a vital warning about promise handling.
First, carefully read the section "Writing Custom Middleware". The responseTime function is a canonical example of this execution model. Notice how it takes a timestamp, calls await next(), and then takes another timestamp to calculate the duration. Next, and this is extremely important, read the DANGER box titled "Always Make Sure to await next!". As an experienced TypeScript developer, you understand the hazards of unhandled promises, and this section explains the severe and unpredictable consequences of forgetting to await the next() call in grammY.
This before-and-after execution pattern is a common feature in many middleware systems. The following video explains it very clearly in the context of Express.js, and the logic is identical.
Learn Express Middleware In 14 Minutes
This video on Express middleware has a great visual demonstration of the execution flow around the next() call.
Watch the segment from after to before. It shows how code placed before the next() call runs first, and code placed after runs once the downstream middleware has finished, perfectly illustrating the "Russian Doll" model we discussed.
Conclusion
In this lesson, we've pulled back the curtain on how grammY handles updates. You've learned that middleware is not just a simple linear chain, but a powerful, layered execution model that gives you fine-grained control over the entire update-processing lifecycle.
Key Takeaways:
- Middleware in grammY are functions with the signature
(ctx, next)that process updates in a chain. - The order of middleware registration is critical as it defines the execution order.
- The
next()function passes control to the subsequent middleware in the chain. Not calling it terminates the processing for that branch. - The
await next()pattern enables the "Russian Doll" model, allowing code to run both before and after the downstream handlers, perfect for tasks like logging, performance measurement, and error handling. - Forgetting to
await next()can lead to unpredictable behavior and crashes, so using a linter to catch floating promises is highly recommended.
Now that you have a solid theoretical understanding of the middleware model, you're ready to apply it. In our next lesson, you'll write your first custom middleware: a function for logging all incoming updates, putting these powerful concepts into practice.
Can't find a good explanation? Sign up and we'll make it for you
Sign up