Last active
October 10, 2025 09:28
-
-
Save waptik/2038ad8f167b7af6d25d34ff9b070a2f to your computer and use it in GitHub Desktop.
This is a minimal codebase of getting grammY to work with nextjs pages directory. Please use this version of `next-connect`: `"next-connect": "^0.13.0",`
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import { Bot, Context } from "grammy"; | |
| export const bot = new Bot<Context>(process.env.TELEGRAM_BOT_TOKEN ?? ""); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import { bot } from "path/to/bot"; | |
| const WEBAPP_URL = "" // URL to your production main site(eg. https://my-secrete-webapp.tld) | |
| const handleGracefulShutdown = async () => { | |
| await bot.stop(); | |
| process.exit(); | |
| }; | |
| if (process.env.NODE_ENV==="development") { | |
| // Graceful shutdown handlers | |
| process.once("SIGTERM", handleGracefulShutdown); | |
| process.once("SIGINT", handleGracefulShutdown); | |
| } | |
| export const startTelegramBotInDev = async () => { | |
| if (!bot.isInited()) { | |
| await bot.start(); | |
| } | |
| }; | |
| export const startTelegramBotInProduction = async () => { | |
| const webhookUrl = `${WEBAPP_URL}/api/telegram-webhook?token=${env.TELEGRAM_BOT_WEBHOOK_TOKEN}`; | |
| const webhookInfo = await bot.api.getWebhookInfo(); | |
| if (webhookInfo.url !== webhookUrl) { | |
| await bot.api.deleteWebhook(); | |
| await bot.api.setWebhook(webhookUrl); | |
| } | |
| } catch (_) { } | |
| }; | |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import { NextApiRequest, NextApiResponse } from "next"; | |
| import nc from "next-connect"; | |
| import { BotError } from "grammy"; | |
| import { startTelegramBotInDev } from "path/to/start"; | |
| // this is to test the bot locally by visiting http://localhost:3000/api/telegram-dev?action=start | |
| const handler = nc<NextApiRequest, NextApiResponse>({ | |
| attachParams: true, | |
| onError: (err, _req, res, next) => { | |
| if (err instanceof BotError) { | |
| res.status(200).send({}); | |
| } else { | |
| console.error(err); | |
| res.status(500).end("Something broke!"); | |
| } | |
| next(); | |
| }, | |
| }) | |
| .get((req, _res, next) => { | |
| if (process.env.NODE_ENV==="development") { | |
| next(); | |
| } | |
| }) | |
| .get(async (req: NextApiRequest, res: NextApiResponse) => { | |
| try { | |
| if (req.query && req.query.action !== "start") { | |
| res.status(500).send({ error: { message: "Wrong gateway." } }); | |
| return; | |
| } | |
| await startTelegramBotInDev(); | |
| res.status(200).send("ok"); | |
| } catch (error) { | |
| res.status(500).json({ error }); | |
| } | |
| }); | |
| export default handler; | |
| // path: /pages/api/telegram-dev.ts |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import { NextApiRequest, NextApiResponse } from "next"; | |
| import nc from "next-connect"; | |
| import { BotError, webhookCallback } from "grammy"; | |
| import { bot } from "path/to/bot"; | |
| import { startTelegramBotInProduction } from "path/to/start"; | |
| const isProd = process.env.NODE_ENV === "production" | |
| const handler = nc<NextApiRequest, NextApiResponse>({ | |
| attachParams: true, | |
| onError: (err, _req, res, next) => { | |
| if (err instanceof BotError) { | |
| res.status(200).send({}); | |
| } else { | |
| res.status(500).end("Something broke!"); | |
| } | |
| next(); | |
| }, | |
| }) | |
| .post((req, _res, next) => { | |
| if (req.query && req.query.token === process.env.TELEGRAM_BOT_WEBHOOK_TOKEN) { | |
| next(); | |
| } | |
| }) | |
| .post(webhookCallback(bot, "next-js")) | |
| .get(async (req, res) => { | |
| // this is used to automatically setup your webhook by visiting https://my-secrete-webapp.tld/api/telegram-webhook?token=[YOUR-BOT-TOKEN] | |
| // replace [YOUR-BOT-TOKEN] with your telegram bot token | |
| // only do so after you have deployed your bot in production | |
| try { | |
| if (process.env.NODE_ENV !=="production" || (req.query && req.query.token !== process.env.TELEGRAM_BOT_WEBHOOK_TOKEN)) { | |
| return res.status(500).send({ error: { message: "Wrong gateway." } }); | |
| } | |
| await startTelegramBotInProduction(); | |
| } finally { | |
| return res.status(200).send("ok"); | |
| } | |
| }); | |
| export default handler; | |
| // path: /pages/api/telegram-webhook.ts |
typo in start.ts#L6: console.info
Thanks! I fixed it and removed non important code from the entire structure so as to minimize unexpected errors
did you try this with app directory?
Next.js 15 APP Route webhookCallback use std/htt, no next-js
// src/app/api/bot/telegram/webhook/route.ts
export const dynamic = 'force-dynamic'
export const fetchCache = 'force-no-store'
import { webhookCallback } from "grammy";
import { type NextRequest, NextResponse } from "next/server";
import { appConfig } from "@/lib/config";
import { telegramBot } from "@/services/telegram-bot";
export const ENDPOINT = "/api/bot/telegram/webhook";
const handleUpdate = webhookCallback(telegramBot, "std/http", {
secretToken: appConfig.bot.telegram.secretToken,
timeoutMilliseconds: 10000,
onTimeout: "return",
});
export async function POST(req: NextRequest) {
try {
if (!req.body) {
console.error("Webhook error: Empty request body");
return NextResponse.json({ error: "Empty request body" }, { status: 400 });
}
const contentType = req.headers.get("content-type");
if (!contentType?.includes("application/json")) {
console.error(`Webhook error: Invalid Content-Type: ${contentType}`);
return NextResponse.json({ error: "Invalid Content-Type" }, { status: 400 });
}
const response = await handleUpdate(req);
return response;
} catch (error) {
console.error("Webhook error:", error);
return NextResponse.json({ error: "server error" }, { status: 500 });
}
}set bot.api.setWebhook to src/instrumentation.ts
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
typo in start.ts#L6: console.info