By default, Telegraf does not know if two request from the same user were related. That's where session comes in. Similar to sessions in webservers, session is a store used to associate some data with a chat or user.
Telegraf sessions are also important for Scenes and Wizards, which internally use session to store the current state of the user.
To use session in Telegraf, import the builtin session middleware, like so:
import { Telegraf, session } from "telegraf";
const bot = new Telegraf(token);
bot.use(session());
// session is ready to use!
This attaches ctx.session to the context object. To let Typescript know about this, we want to augment the Context type:
import { Telegraf, session, type Context } from "telegraf";
import type { Update } from "telegraf/types";
interface MyContext <U extends Update = Update> extends Context<U> {
session: {
count: number
},
};
// Telegraf constructor accepts a custom Context type
const bot = new Telegraf<MyContext>(token);
// we can also set a default value for session:
bot.use(session({ defaultSession: () => ({ count: 0 }) }));
bot.use(ctx => {
// ctx.session is available
});
The default session
middleware in Telegraf is backed by an in-memory store. Sometimes this is sufficient, but if your bot restarts you will lose all session data. This can be bad, because your users may need to start over again. To avoid this, we must back our session with a real database. This can be done by installing @telegraf/session
and a database of choice (see options). We'll use Redis in this example, but go over to @telegraf/session
docs for other options (SQLite, MongoDB, MySQL, Postgres, etc).
import { Redis } from "@telegraf/session/redis";
const store = Redis({
// this assumes you're using a locally installed Redis daemon running at 6379
url: "redis://127.0.0.1:6379",
});
// pass the store to session
bot.use(session({ store }));
Seasoned Telegraf users may remember #1372 and that session was deprecated for a while due to the potential for race conditions. This was fixed in Telegraf 4.12.0 (#1713). However, we must review the safeties that new session provides, and how not to step out of it.
Things to remember:
-
Always
await
or return any async API calls (ornext()
) in your middlewares.bot.on("message", async ctx => { // ❌ BAD! You did not await ctx.reply() ctx.reply("Hello there!"); // ✅ Good, you awaited your requests. await ctx.reply("Hello there!"); // Also applies to any other async calls you may make: const res = await fetch(API_URL); // Returning calls is also okay, because the promise will be returned return ctx.sendAnimation(GOOD_MORNING_GIF); });
-
If you write to
ctx.session
at any point, normally Telegraf will try to persist it after all middlewares are done. However, if a parallel update has made changes to session, those changes will also be written.bot.use(async (ctx, next) => { ctx.session.count++; // increment once return next(); // pass control to next middleware }); bot.on("message", async (ctx, next) => { ctx.session.count++; // increment again }); // all middleware have run. ctx.session will be written back to store
-
Session will write to store even if a middleware errors. If you need to roll data back on an error, you must handle the error and fix your session data.
bot.use(async (ctx, next) => { ctx.session.count++; // increment count await functionThatThrows(); return next(); }); // incremented count will still be written to store
-
If you use webhook mode, you must disable
webhookReply
.const bot = new Telegraf(token, { telegram: { webhookReply: false } })
-
You must not introduce race-conditions of your own. Be wary of writing stale data back to session. Example:
bot.on(message("text"), async ctx => { // reading value from session const count = ctx.session.count; const response = await fetch(API_URL, { body: count }); // ⚠️ WARNING! You wrote stale value to session. // Another request may have updated session while you awaited fetch, be careful of that ctx.session.count = count + 1; // ✅ Good, you wrote immediately after reading ctx.session.count = ctx.session.count + 1; });