Scaling Up I: Large Codebase
As soon as your bot grows in complexity, you are going to face the challenge of how to structure your application code base. Naturally, you can split it across files.
Possible Solution
grammY is still pretty young and does not provide any official integrations with DI containers yet. Subscribe to @grammyjs
_news to be notified as soon as we support this.
You are free to structure your code however you like, and there is no one-size-fits-all solution. That being said, a straightforward and proven strategy to structure your code is the following.
- Group things that semantically belong together in the same file (or, depending on the code size, directory). Every single one of these parts exposes middleware that will handle the designated messages.
- Create a bot instance centrally that merges all middleware by installing it onto the bot.
- (Optional.) Pre-filter the updates centrally, and send down updates the right way only. You may also want to check out
bot
(API Reference) or alternatively the router plugin for that..route
A runnable example that implements the above strategy can be found in the Example Bot repository.
Example Structure
For a very simple bot that manages a TODO list, you could imagine this structure.
src/
├── bot.ts
└── todo/
├── item.ts
└── list.ts
item
just defines some stuff about TODO items, and these code parts are used in list
.
In list
, you would then do something like this:
export const lists = new Composer();
// Register some handlers here that handle your middleware the usual way.
lists.on("message", async (ctx) => {/* ... */});
2
3
4
Note that if you use TypeScript, you need to pass your custom context type when creating the composer. For example, you’ll need to use
new Composer<My
.Context>()
Optionally, you can use an error boundary to handle all errors that happen inside your module.
Now, in bot
, you can install this module like so:
import { lists } from "./todo/list";
const bot = new Bot("");
bot.use(lists);
// ... maybe more modules like `todo` here
bot.start();
2
3
4
5
6
7
8
Optionally, you can use the router plugin or bot
to bundle up the different modules, if you’re able to determine which middleware is responsible upfront.
However, remember that the exact way of how to structure your bot is very hard to say generically. As always in software, do it in a way that makes the most sense 😉
Type Definitions for Extracted Middleware
The above structure using composers works well. However, sometimes you may find yourself in the situation that you want to extract a handler into a function, rather than creating a new composer and adding the logic to it. This requires you to add the correct middleware type definitions to your handlers because they can no longer be inferred through the composer.
grammY exports type definitions for all narrowed types of middleware, such as the middleware that you can pass to command handlers. In addition, it exports the type definitions for the narrowed context objects that are being used in that middleware. Both types are parameterized with your custom context object. Hence, a command handler would have the type Command
and its context object Command
. They can be used as follows.
import {
type CallbackQueryMiddleware,
type CommandContext,
type NextFunction,
} from "grammy";
function commandMiddleware(ctx: CommandContext<MyContext>, next: NextFunction) {
// command handling
}
const callbackQueryMiddleware: CallbackQueryMiddleware<MyContext> = (ctx) => {
// callback query handling
};
bot.command(["start", "help"], commandMiddleware);
bot.callbackQuery("query-data", callbackQueryMiddleware);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import {
type CallbackQueryMiddleware,
type CommandContext,
type NextFunction,
} from "https://deno.land/x/grammy@v1.31.1/mod.ts";
function commandMiddleware(ctx: CommandContext<MyContext>, next: NextFunction) {
// command handling
}
const callbackQueryMiddleware: CallbackQueryMiddleware<MyContext> = (ctx) => {
// callback query handling
};
bot.command(["start", "help"], commandMiddleware);
bot.callbackQuery("query-data", callbackQueryMiddleware);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Check out the type aliases API reference to see an overview over all type aliases that grammY exports.