Construire un Bot Telegram avec grammY et TypeScript : du zéro au déploiement

Les bots Telegram sont partout. Du service client à l'automatisation des tâches, l'API Bot de Telegram alimente des millions de bots. Dans ce tutoriel, vous allez construire un bot Telegram prêt pour la production avec grammY — le framework moderne et typé pour construire des bots Telegram en TypeScript.
Ce que vous allez apprendre
À la fin de ce tutoriel, vous serez capable de :
- Créer un bot Telegram et obtenir un jeton API auprès de BotFather
- Configurer un projet TypeScript avec grammY
- Gérer les commandes, les messages et les requêtes callback
- Construire des claviers interactifs et des menus
- Utiliser les middlewares et les composers pour organiser votre code
- Implémenter la gestion d'état avec les sessions
- Ajouter des flux de conversation avec
@grammyjs/conversations - Déployer votre bot en mode long polling et webhooks
Prérequis
Avant de commencer, assurez-vous d'avoir :
- Node.js 20+ installé (
node --version) - Des connaissances en TypeScript (types, async/await, modules)
- Un compte Telegram (pour créer et tester le bot)
- Une compréhension basique de HTTP et des APIs
- Un éditeur de code (VS Code recommandé)
Étape 1 : Créer votre bot avec BotFather
Chaque bot Telegram commence avec BotFather — le bot officiel de Telegram pour gérer les bots.
- Ouvrez Telegram et recherchez
@BotFather - Envoyez
/newbot - Choisissez un nom d'affichage (ex : "Mon Bot grammY")
- Choisissez un nom d'utilisateur se terminant par
bot(ex :my_grammy_demo_bot) - Copiez le jeton API reçu — il ressemble à :
123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11
Gardez votre jeton secret ! Toute personne possédant le jeton de votre bot peut le contrôler. Ne le commitez jamais dans le contrôle de version.
Étape 2 : Configuration du projet
Créez un nouveau projet et installez les dépendances :
mkdir telegram-grammy-bot && cd telegram-grammy-bot
npm init -y
npm install grammy dotenv
npm install -D typescript @types/node tsx
npx tsc --initMettez à jour tsconfig.json pour TypeScript moderne :
{
"compilerOptions": {
"target": "ES2022",
"module": "ES2022",
"moduleResolution": "bundler",
"strict": true,
"esModuleInterop": true,
"outDir": "./dist",
"rootDir": "./src",
"skipLibCheck": true
},
"include": ["src"]
}Ajoutez "type": "module" à votre package.json et mettez à jour les scripts :
{
"type": "module",
"scripts": {
"dev": "tsx watch src/bot.ts",
"build": "tsc",
"start": "node dist/bot.js"
}
}Créez un fichier .env pour le jeton du bot :
BOT_TOKEN=123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11Étape 3 : Votre premier bot
Créez src/bot.ts :
import { Bot } from "grammy";
import "dotenv/config";
const token = process.env.BOT_TOKEN;
if (!token) throw new Error("BOT_TOKEN is not set");
const bot = new Bot(token);
// Gérer la commande /start
bot.command("start", (ctx) => {
ctx.reply(
`Bonjour ${ctx.from?.first_name} ! 👋\nJe suis un bot construit avec grammY et TypeScript.`
);
});
// Gérer la commande /help
bot.command("help", (ctx) => {
ctx.reply(
"Commandes disponibles :\n" +
"/start - Démarrer le bot\n" +
"/help - Afficher ce message d'aide\n" +
"/about - En savoir plus sur ce bot"
);
});
// Renvoyer tout message texte
bot.on("message:text", (ctx) => {
ctx.reply(`Vous avez dit : ${ctx.message.text}`);
});
// Démarrer le bot
bot.start();
console.log("Le bot est en cours d'exécution...");Lancez-le :
npm run devOuvrez Telegram, trouvez votre bot par son nom d'utilisateur et envoyez /start. Vous devriez voir un message de bienvenue.
Étape 4 : Claviers interactifs et requêtes callback
Les claviers interactifs ajoutent des boutons interactifs à vos messages. Créez src/keyboards.ts :
import { InlineKeyboard } from "grammy";
export const mainMenu = new InlineKeyboard()
.text("📊 Statut", "status")
.text("⚙️ Paramètres", "settings")
.row()
.text("ℹ️ À propos", "about")
.text("📞 Contact", "contact");
export const settingsMenu = new InlineKeyboard()
.text("🌍 Langue", "lang")
.text("🔔 Notifications", "notif")
.row()
.text("« Retour au menu", "back_to_menu");Maintenant, gérez les requêtes callback dans src/bot.ts :
import { Bot } from "grammy";
import { mainMenu, settingsMenu } from "./keyboards.js";
import "dotenv/config";
const token = process.env.BOT_TOKEN;
if (!token) throw new Error("BOT_TOKEN is not set");
const bot = new Bot(token);
bot.command("start", (ctx) => {
ctx.reply("Bienvenue ! Choisissez une option :", { reply_markup: mainMenu });
});
// Gérer les clics de boutons
bot.callbackQuery("status", (ctx) => {
ctx.answerCallbackQuery();
ctx.editMessageText("✅ Le bot fonctionne parfaitement !\n\nDisponibilité : 99,9 %", {
reply_markup: new InlineKeyboard().text("« Retour", "back_to_menu"),
});
});
bot.callbackQuery("settings", (ctx) => {
ctx.answerCallbackQuery();
ctx.editMessageText("⚙️ Paramètres :", { reply_markup: settingsMenu });
});
bot.callbackQuery("about", async (ctx) => {
await ctx.answerCallbackQuery();
await ctx.editMessageText(
"Ce bot est construit avec grammY + TypeScript.\nVersion : 1.0.0",
{ reply_markup: new InlineKeyboard().text("« Retour", "back_to_menu") }
);
});
bot.callbackQuery("back_to_menu", (ctx) => {
ctx.answerCallbackQuery();
ctx.editMessageText("Choisissez une option :", { reply_markup: mainMenu });
});
bot.start();Appelez toujours ctx.answerCallbackQuery() lors du traitement des requêtes callback. Cela supprime l'indicateur de chargement sur le bouton de l'utilisateur. Telegram affichera une erreur de délai d'attente si vous ne répondez pas dans les 30 secondes.
Étape 5 : Middlewares et Composers
À mesure que votre bot grandit, vous devez organiser les handlers. Le pattern Composer de grammY vous permet de modulariser votre code.
Créez src/handlers/admin.ts :
import { Composer } from "grammy";
import type { MyContext } from "../types.js";
const admin = new Composer<MyContext>();
const ADMIN_IDS = [123456789]; // Remplacez par votre ID utilisateur Telegram
admin.command("broadcast", async (ctx) => {
if (!ADMIN_IDS.includes(ctx.from?.id ?? 0)) {
return ctx.reply("⛔ Vous n'êtes pas autorisé à utiliser cette commande.");
}
const message = ctx.match;
if (!message) {
return ctx.reply("Utilisation : /broadcast <message>");
}
await ctx.reply(`📢 Diffusion : ${message}`);
});
admin.command("stats", async (ctx) => {
if (!ADMIN_IDS.includes(ctx.from?.id ?? 0)) {
return ctx.reply("⛔ Non autorisé.");
}
await ctx.reply("📊 Statistiques du bot :\n- Utilisateurs : 42\n- Messages aujourd'hui : 156");
});
export default admin;Créez src/types.ts pour les types partagés :
import type { Context, SessionFlavor } from "grammy";
export interface SessionData {
language: string;
notificationsEnabled: boolean;
messageCount: number;
}
export type MyContext = Context & SessionFlavor<SessionData>;Enregistrez le composer dans votre fichier bot principal :
import admin from "./handlers/admin.js";
// ... configuration du bot ...
bot.use(admin);Étape 6 : Gestion des sessions
Les sessions vous permettent de stocker des données par utilisateur à travers les messages. Installez le plugin de sessions :
npm install @grammyjs/storage-fileMettez à jour src/bot.ts pour utiliser les sessions :
import { Bot, session } from "grammy";
import type { MyContext, SessionData } from "./types.js";
import "dotenv/config";
const token = process.env.BOT_TOKEN;
if (!token) throw new Error("BOT_TOKEN is not set");
const bot = new Bot<MyContext>(token);
function defaultSession(): SessionData {
return {
language: "fr",
notificationsEnabled: true,
messageCount: 0,
};
}
bot.use(
session({
initial: defaultSession,
})
);
// Middleware pour compter les messages
bot.use(async (ctx, next) => {
if (ctx.message) {
ctx.session.messageCount++;
}
await next();
});
bot.command("mystats", (ctx) => {
const { language, notificationsEnabled, messageCount } = ctx.session;
ctx.reply(
`📊 Vos statistiques :\n` +
`- Langue : ${language}\n` +
`- Notifications : ${notificationsEnabled ? "Activées" : "Désactivées"}\n` +
`- Messages envoyés : ${messageCount}`
);
});
bot.command("setlang", (ctx) => {
const lang = ctx.match;
if (!lang || !["en", "ar", "fr"].includes(lang)) {
return ctx.reply("Utilisation : /setlang <en|ar|fr>");
}
ctx.session.language = lang;
ctx.reply(`✅ Langue définie sur : ${lang}`);
});
bot.start();Étape 7 : Flux de conversation
Pour les interactions multi-étapes (formulaires, assistants), utilisez le plugin conversations :
npm install @grammyjs/conversationsCréez src/conversations/feedback.ts :
import type { MyContext } from "../types.js";
import type { Conversation } from "@grammyjs/conversations";
type FeedbackConversation = Conversation<MyContext>;
export async function feedbackFlow(
conversation: FeedbackConversation,
ctx: MyContext
) {
await ctx.reply("📝 J'aimerais avoir votre avis !\n\nQuel est votre nom ?");
const nameCtx = await conversation.waitFor("message:text");
const name = nameCtx.message.text;
await ctx.reply(`Merci ${name} ! Comment évaluez-vous notre service ? (1-5)`);
let rating: number;
do {
const ratingCtx = await conversation.waitFor("message:text");
rating = parseInt(ratingCtx.message.text);
if (isNaN(rating) || rating < 1 || rating > 5) {
await ctx.reply("Veuillez entrer un nombre entre 1 et 5.");
}
} while (isNaN(rating) || rating < 1 || rating > 5);
await ctx.reply("Des commentaires supplémentaires ? (Envoyez /skip pour passer)");
const commentCtx = await conversation.waitFor("message:text");
const comment =
commentCtx.message.text === "/skip" ? "Aucun commentaire" : commentCtx.message.text;
await ctx.reply(
`✅ Retour reçu !\n\n` +
`Nom : ${name}\n` +
`Note : ${"⭐".repeat(rating)}\n` +
`Commentaire : ${comment}\n\n` +
`Merci pour votre retour !`
);
}Enregistrez-le dans votre bot :
import { conversations, createConversation } from "@grammyjs/conversations";
import { feedbackFlow } from "./conversations/feedback.js";
// Ajouter après le middleware de session
bot.use(conversations());
bot.use(createConversation(feedbackFlow));
bot.command("feedback", async (ctx) => {
await ctx.conversation.enter("feedbackFlow");
});Étape 8 : Gestion des erreurs
Les bots en production nécessitent une gestion robuste des erreurs :
import { BotError, GrammyError, HttpError } from "grammy";
bot.catch((err: BotError) => {
const ctx = err.ctx;
console.error(`Erreur lors du traitement de la mise à jour ${ctx.update.update_id} :`);
const e = err.error;
if (e instanceof GrammyError) {
console.error("Erreur dans la requête :", e.description);
if (e.description.includes("message is not modified")) {
// L'utilisateur a cliqué deux fois sur le même bouton — sûr à ignorer
return;
}
} else if (e instanceof HttpError) {
console.error("Impossible de contacter Telegram :", e);
} else {
console.error("Erreur inconnue :", e);
}
});Étape 9 : Déploiement avec Webhooks
En production, les webhooks sont plus efficaces que le long polling. Voici comment configurer un webhook avec grammY et Hono :
npm install hono @hono/node-serverCréez src/webhook.ts :
import { Bot, webhookCallback } from "grammy";
import { Hono } from "hono";
import { serve } from "@hono/node-server";
import type { MyContext } from "./types.js";
import "dotenv/config";
const token = process.env.BOT_TOKEN;
if (!token) throw new Error("BOT_TOKEN is not set");
const bot = new Bot<MyContext>(token);
// ... enregistrez tous vos handlers, middlewares, sessions ...
bot.command("start", (ctx) => ctx.reply("Bonjour depuis le mode webhook !"));
const app = new Hono();
app.get("/", (c) => c.text("Le bot fonctionne"));
app.post(`/webhook/${token}`, webhookCallback(bot, "hono"));
const PORT = parseInt(process.env.PORT || "3000");
serve({ fetch: app.fetch, port: PORT }, () => {
console.log(`Serveur webhook en cours d'exécution sur le port ${PORT}`);
});
// Définir l'URL du webhook (exécuter une seule fois)
// bot.api.setWebhook(`https://your-domain.com/webhook/${token}`);Configurez le webhook en appelant l'API Telegram :
curl -X POST "https://api.telegram.org/bot<YOUR_TOKEN>/setWebhook" \
-H "Content-Type: application/json" \
-d '{"url": "https://your-domain.com/webhook/<YOUR_TOKEN>"}'Étape 10 : Déployer sur un VPS
Créez un Dockerfile pour le déploiement :
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev
COPY --from=builder /app/dist ./dist
COPY .env .env
EXPOSE 3000
CMD ["node", "dist/webhook.js"]Construisez et lancez :
docker build -t grammy-bot .
docker run -d --name grammy-bot -p 3000:3000 grammy-botUtilisez un reverse proxy comme Caddy ou nginx pour gérer HTTPS :
# Caddyfile
bot.yourdomain.com {
reverse_proxy localhost:3000
}
Structure du projet
Voici la structure finale du projet :
telegram-grammy-bot/
├── src/
│ ├── bot.ts # Point d'entrée du bot (long polling)
│ ├── webhook.ts # Point d'entrée webhook
│ ├── types.ts # Types partagés
│ ├── keyboards.ts # Définitions des claviers
│ ├── handlers/
│ │ └── admin.ts # Composer des commandes admin
│ └── conversations/
│ └── feedback.ts # Flux de conversation feedback
├── .env
├── package.json
├── tsconfig.json
└── Dockerfile
Dépannage
Le bot ne répond pas ?
- Vérifiez que votre jeton est correct et non expiré
- Assurez-vous qu'aucune autre instance du bot ne tourne (une seule peut utiliser le long polling)
- Vérifiez que l'utilisateur n'a pas bloqué le bot
Le webhook ne fonctionne pas ?
- Assurez-vous que votre serveur possède un certificat SSL valide (HTTPS requis)
- Vérifiez l'URL du webhook via
https://api.telegram.org/bot<TOKEN>/getWebhookInfo - Confirmez que votre serveur est accessible publiquement
Données de session perdues ?
- Les sessions en mémoire sont effacées au redémarrage. Utilisez
@grammyjs/storage-fileou un adaptateur de base de données pour la persistance - Assurez-vous que le middleware de session est enregistré avant vos handlers
Prochaines étapes
- Ajoutez une intégration de base de données avec Prisma ou Drizzle pour le stockage persistant
- Implémentez l'internationalisation avec
@grammyjs/i18n - Ajoutez la limitation de débit avec
@grammyjs/ratelimiter - Construisez une application web avec la fonctionnalité Mini Apps de Telegram
- Configurez les paiements via le système de paiement intégré de Telegram
- Ajoutez la gestion de fichiers pour les images, documents et messages vocaux
Conclusion
Vous avez construit un bot Telegram entièrement fonctionnel avec grammY et TypeScript, couvrant :
- La création du bot et la gestion du jeton
- La gestion des commandes et des messages
- Les claviers interactifs
- Le code modulaire avec les composers
- La gestion d'état avec les sessions
- Les flux de conversation multi-étapes
- La gestion des erreurs et le déploiement en production
L'approche TypeScript-first de grammY vous offre une excellente autocomplétion, une sécurité de type et une API propre. Combiné à son écosystème de plugins, c'est le framework idéal pour construire des bots Telegram en 2026.
Le code source complet de ce tutoriel est disponible pour référence. Commencez à construire votre propre bot et explorez la riche API Bot de Telegram pour créer des automatisations puissantes et des expériences interactives.
Discutez de votre projet avec nous
Nous sommes ici pour vous aider avec vos besoins en développement Web. Planifiez un appel pour discuter de votre projet et comment nous pouvons vous aider.
Trouvons les meilleures solutions pour vos besoins.
Articles connexes

Construire votre premier serveur MCP avec TypeScript : Outils, Ressources et Prompts
Apprenez a construire un serveur MCP pret pour la production en partant de zero avec TypeScript. Ce tutoriel pratique couvre les outils, les ressources, les prompts, le transport stdio, et la connexion a Claude Desktop et Cursor.

Créer et déployer une API serverless avec Cloudflare Workers, Hono et D1
Apprenez à construire une API REST prête pour la production avec Cloudflare Workers, le framework Hono et la base de données D1 — de la configuration initiale au déploiement mondial.

Créer des APIs Type-Safe de bout en bout avec tRPC et Next.js App Router
Apprenez à créer des APIs entièrement type-safe avec tRPC et Next.js 15 App Router. Ce tutoriel pratique couvre la configuration du routeur, les procédures, le middleware, l'intégration de React Query et les appels côté serveur — le tout sans écrire un seul schéma d'API.