The Telegram Bot Framework for Node.js and Deno with middleware-based architecture and full TypeScript support
Specialized error classes for API errors, network failures, and middleware execution errors.
Wraps errors that occur during middleware execution, providing context about where the error happened.
/**
* Error wrapper for middleware errors, includes the context where error occurred
*/
class BotError<C extends Context> extends Error {
/**
* Create BotError
* @param error - The original error
* @param ctx - Context where error occurred
*/
constructor(error: unknown, ctx: C);
/**
* The wrapped error (original error that was thrown)
*/
error: unknown;
/**
* Context object where error occurred
*/
ctx: C;
}Usage Examples:
import { Bot, BotError } from "grammy";
const bot = new Bot("TOKEN");
// Set error handler
bot.catch((err: BotError) => {
console.error("Error in middleware:", err.error);
console.error("Update:", err.ctx.update);
console.error("Chat:", err.ctx.chat?.id);
// Access original error
if (err.error instanceof Error) {
console.error("Stack trace:", err.error.stack);
}
// Notify user
if (err.ctx.chat) {
err.ctx.reply("Sorry, an error occurred!").catch(console.error);
}
});
// Middleware that might throw
bot.on("message", (ctx) => {
// This error will be wrapped in BotError
throw new Error("Something went wrong!");
});Represents errors returned by the Telegram Bot API, including error codes and descriptions from Telegram.
/**
* Error class for Telegram Bot API errors
*/
class GrammyError extends Error {
/**
* Create GrammyError
* @param message - Error message
* @param err - API error object from Telegram
* @param method - API method that failed
* @param payload - Request payload that caused error
*/
constructor(
message: string,
err: ApiError,
method: string,
payload: Record<string, unknown>
);
/**
* Always false (indicates API error)
*/
ok: false;
/**
* Telegram error code
*/
error_code: number;
/**
* Error description from Telegram
*/
description: string;
/**
* Additional error parameters (e.g., retry_after for rate limits)
*/
parameters: ResponseParameters;
/**
* API method that failed
*/
method: string;
/**
* Request payload that caused the error
*/
payload: Record<string, unknown>;
}Common Error Codes:
400 - Bad Request (invalid parameters)401 - Unauthorized (invalid bot token)403 - Forbidden (bot blocked by user, no permissions)404 - Not Found (chat/message not found)409 - Conflict (webhook and polling active simultaneously)429 - Too Many Requests (rate limit exceeded)500 - Internal Server Error (Telegram server issue)Usage Examples:
import { GrammyError } from "grammy";
bot.catch((err: BotError) => {
const error = err.error;
if (error instanceof GrammyError) {
console.error("API Error:", error.description);
console.error("Error code:", error.error_code);
console.error("Method:", error.method);
console.error("Payload:", error.payload);
// Handle specific error codes
switch (error.error_code) {
case 403:
console.log("Bot was blocked by user or lacks permissions");
break;
case 429:
console.log("Rate limited. Retry after:", error.parameters.retry_after);
break;
case 400:
if (error.description.includes("message is not modified")) {
console.log("Tried to edit message with same content");
}
break;
}
}
});
// Catching specific API errors
try {
await bot.api.sendMessage(invalidChatId, "Hello!");
} catch (error) {
if (error instanceof GrammyError) {
if (error.error_code === 400) {
console.log("Invalid chat ID");
}
}
}Represents network-level errors such as connection failures, timeouts, or DNS resolution errors.
/**
* Error class for network/HTTP errors
*/
class HttpError extends Error {
/**
* Create HttpError
* @param message - Error message
* @param error - Underlying error object
*/
constructor(message: string, error: unknown);
/**
* The underlying error (e.g., fetch error, network error)
*/
error: unknown;
}Usage Examples:
import { HttpError } from "grammy";
bot.catch((err: BotError) => {
const error = err.error;
if (error instanceof HttpError) {
console.error("Network error:", error.message);
console.error("Underlying error:", error.error);
// Could be timeout, DNS failure, connection refused, etc.
// Might want to retry or alert monitoring system
}
});
// Handling network errors in direct API calls
try {
await bot.api.sendMessage(chatId, "Hello!");
} catch (error) {
if (error instanceof HttpError) {
console.log("Network problem, will retry later");
// Implement retry logic
}
}Global Error Handler:
bot.catch((err) => {
const error = err.error;
if (error instanceof GrammyError) {
console.error("API Error:", error.description);
} else if (error instanceof HttpError) {
console.error("Network Error:", error.message);
} else {
console.error("Unknown Error:", error);
}
});Error Boundaries:
// Isolate risky middleware
bot.errorBoundary(
(err, next) => {
console.error("Error in risky operation:", err.error);
// Continue to next middleware
return next();
},
riskyMiddleware
);Try-Catch in Middleware:
bot.on("message", async (ctx) => {
try {
await doSomethingRisky(ctx);
} catch (error) {
console.error("Caught error:", error);
return ctx.reply("Sorry, something went wrong");
}
});Retry Logic:
async function sendWithRetry(
chatId: number,
text: string,
maxRetries = 3
) {
for (let i = 0; i < maxRetries; i++) {
try {
return await bot.api.sendMessage(chatId, text);
} catch (error) {
if (error instanceof GrammyError && error.error_code === 429) {
// Rate limited - wait and retry
const retryAfter = error.parameters.retry_after || 1;
await new Promise(resolve => setTimeout(resolve, retryAfter * 1000));
continue;
}
if (error instanceof HttpError && i < maxRetries - 1) {
// Network error - retry
await new Promise(resolve => setTimeout(resolve, 1000 * (i + 1)));
continue;
}
throw error;
}
}
}Graceful Degradation:
bot.on("message:photo", async (ctx) => {
try {
// Try to process photo
await processPhoto(ctx.message.photo);
await ctx.reply("Photo processed!");
} catch (error) {
// Fallback gracefully
console.error("Photo processing failed:", error);
await ctx.reply("Received your photo, but couldn't process it");
}
});Error Logging and Monitoring:
import { BotError, GrammyError, HttpError } from "grammy";
bot.catch((err) => {
// Log to monitoring service
logger.error("Bot error", {
update_id: err.ctx.update.update_id,
chat_id: err.ctx.chat?.id,
user_id: err.ctx.from?.id,
error_type: err.error?.constructor?.name,
error_message: err.error instanceof Error ? err.error.message : String(err.error)
});
// Send to error tracking service (e.g., Sentry)
if (err.error instanceof Error) {
Sentry.captureException(err.error, {
contexts: {
telegram: {
update: err.ctx.update,
chat: err.ctx.chat,
user: err.ctx.from
}
}
});
}
// Alert for critical errors
if (err.error instanceof GrammyError && err.error.error_code === 401) {
alertAdmins("Bot token is invalid!");
}
});/**
* API error response from Telegram
*/
interface ApiError {
ok: false;
error_code: number;
description: string;
parameters?: ResponseParameters;
}
/**
* Additional error parameters
*/
interface ResponseParameters {
/**
* Optional. The group has been migrated to a supergroup with the specified identifier.
*/
migrate_to_chat_id?: number;
/**
* Optional. In case of exceeding flood control, the number of seconds left
* to wait before the request can be repeated
*/
retry_after?: number;
}Install with Tessl CLI
npx tessl i tessl/npm-grammy