Setup bot trong 2 phút
- Tìm
@BotFathertrên Telegram →/newbot→ đặt tên và username - Nhận token dạng
110201543:AAHdqTcvCH1vGWJxfSeofSAs0K5PALDsaw - Mở chat với bot, gửi
/start - Gọi
https://api.telegram.org/botTOKEN/getUpdatesđể lấychat_id
{ "result": [{ "message": { "chat": { "id": 123456789 } } }] }
Thêm hai biến vào .env:
TELEGRAM_BOT_TOKEN=110201543:AAHdqTcvCH1vGWJxfSeofSAs0K5PALDsaw
TELEGRAM_CHAT_ID=123456789
TelegramModule
Tạo module riêng để inject vào bất kỳ chỗ nào trong app:
// telegram/telegram.service.ts
@Injectable()
export class TelegramService {
private readonly token: string;
private readonly chatId: string;
private readonly cooldowns = new Map<string, number>();
constructor(private readonly config: ConfigService) {
this.token = this.config.getOrThrow("TELEGRAM_BOT_TOKEN");
this.chatId = this.config.getOrThrow("TELEGRAM_CHAT_ID");
}
async send(text: string): Promise<void> {
await fetch(
`https://api.telegram.org/bot${this.token}/sendMessage`,
{
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
chat_id: this.chatId,
text,
parse_mode: "HTML",
}),
}
).catch((err) => {
// Không để alert làm crash app
console.error("[Telegram] send failed:", err.message);
});
}
// Gửi tối đa 1 lần trong khoảng cooldownMs cho mỗi key
// Tránh bị spam alert khi lỗi xảy ra liên tục
async sendOnce(key: string, text: string, cooldownMs = 60_000): Promise<void> {
const now = Date.now();
if (now - (this.cooldowns.get(key) ?? 0) < cooldownMs) return;
this.cooldowns.set(key, now);
await this.send(text);
}
}
// telegram/telegram.module.ts
@Module({
imports: [ConfigModule],
providers: [TelegramService],
exports: [TelegramService],
})
export class TelegramModule {}
Import vào AppModule:
@Module({
imports: [
ConfigModule.forRoot({ isGlobal: true }),
TelegramModule,
// ...
],
})
export class AppModule {}
Exception filter — alert lỗi production
Bắt mọi unhandled exception và gửi Telegram chỉ với 5xx:
// common/filters/all-exceptions.filter.ts
@Catch()
export class AllExceptionsFilter implements ExceptionFilter {
constructor(private readonly telegram: TelegramService) {}
async catch(exception: unknown, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const req = ctx.getRequest<Request>();
const res = ctx.getResponse<Response>();
const status =
exception instanceof HttpException
? exception.getStatus()
: HttpStatus.INTERNAL_SERVER_ERROR;
const message =
exception instanceof Error ? exception.message : String(exception);
if (status >= 500) {
const alert =
`🔴 <b>Server Error ${status}</b>\n` +
`<code>${req.method} ${req.url}</code>\n` +
`<code>${message}</code>`;
// Cooldown 1 phút mỗi endpoint, tránh spam
await this.telegram.sendOnce(`${req.method}:${req.url}`, alert);
}
res.status(status).json({
statusCode: status,
message,
timestamp: new Date().toISOString(),
});
}
}
Đăng ký global trong main.ts:
async function bootstrap() {
const app = await NestFactory.create(AppModule);
const telegram = app.get(TelegramService);
app.useGlobalFilters(new AllExceptionsFilter(telegram));
await app.listen(3000);
}
Thông báo khi app start và shutdown
Implement OnApplicationBootstrap và OnApplicationShutdown trong AppModule:
// app.module.ts
@Module({ ... })
export class AppModule implements OnApplicationBootstrap, OnApplicationShutdown {
constructor(private readonly telegram: TelegramService) {}
async onApplicationBootstrap() {
await this.telegram.send(
`✅ <b>App started</b>\n` +
`Env: <code>${process.env.NODE_ENV}</code>\n` +
`Time: <code>${new Date().toLocaleString("vi-VN")}</code>`
);
}
async onApplicationShutdown(signal?: string) {
await this.telegram.send(
`⚠️ <b>App shutting down</b> (${signal ?? "unknown signal"})`
);
}
}
Để onApplicationShutdown chạy được, cần bật shutdown hooks:
// main.ts
const app = await NestFactory.create(AppModule);
app.enableShutdownHooks(); // bắt SIGTERM, SIGINT
await app.listen(3000);
Alert khi BullMQ job thất bại
// jobs/alert.processor.ts (hoặc bất kỳ processor nào)
@Processor("email")
export class EmailProcessor {
constructor(private readonly telegram: TelegramService) {}
@Process("send")
async handleSend(job: Job<{ to: string; subject: string }>) {
// ... xử lý job
}
@OnQueueFailed()
async onFailed(job: Job, error: Error) {
const alert =
`❌ <b>Job Failed</b>\n` +
`Queue: <code>${job.queue.name}</code>\n` +
`Job: <code>${job.name} #${job.id}</code>\n` +
`Attempts: ${job.attemptsMade}/${job.opts.attempts}\n` +
`Error: <code>${error.message}</code>`;
await this.telegram.sendOnce(
`job-fail:${job.queue.name}:${job.name}`,
alert,
5 * 60_000 // cooldown 5 phút mỗi loại job
);
}
}
Thông báo deploy từ script
Khi dùng PM2 + script deploy:
#!/bin/bash
# deploy.sh
notify() {
curl -s -X POST "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage" \
--data-urlencode "chat_id=${TELEGRAM_CHAT_ID}" \
--data-urlencode "text=$1" \
-d "parse_mode=HTML" > /dev/null
}
notify "🚀 Bắt đầu deploy <b>$(hostname)</b>..."
git pull origin main && \
npm ci --omit=dev && \
npm run build && \
pm2 restart ecosystem.config.js --update-env
if [ $? -eq 0 ]; then
notify "✅ Deploy thành công lúc $(date '+%H:%M:%S')"
else
notify "❌ Deploy thất bại trên <b>$(hostname)</b>!"
exit 1
fi
Format tin nhắn
parse_mode: "HTML" dễ kiểm soát hơn Markdown, không cần escape ký tự đặc biệt trong code:
// Hàm helper dùng chung
function formatAlert(opts: {
emoji: string;
title: string;
fields: Record<string, string>;
code?: string;
}): string {
const lines = [
`${opts.emoji} <b>${opts.title}</b>`,
"",
...Object.entries(opts.fields).map(
([k, v]) => `<b>${k}:</b> <code>${v}</code>`
),
];
if (opts.code) {
lines.push("", `<pre>${opts.code.slice(0, 800)}</pre>`);
}
return lines.join("\n");
}
// Dùng trong exception filter
const alert = formatAlert({
emoji: "🔴",
title: "Server Error 500",
fields: {
Method: req.method,
URL: req.url,
Error: error.message,
},
code: error.stack,
});
Lưu ý production
- Không
awaittrong critical path: gửi Telegram có thể mất 200–500ms, nên fire-and-forget ở filter hoặc lifecycle hook - Rate limit Telegram: 30 msg/giây — đủ cho alerting, dùng
sendOnceđể tránh spam - Token phải ở env: không commit vào git, không log ra console
- Test bằng curl trước: đảm bảo token và chat_id đúng trước khi tích hợp vào code