npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

turbogram-v1

v1.0.3

Published

A blazing-fast, lightweight Telegram Bot SDK for Node.js with Lightspeed Engine™

Readme

🚀 turbogram

The fastest Telegram Bot SDK for Node.js · Zero dependencies · Built for performance

npm version license node downloads


turbogram-v1 is a blazing-fast, production-ready Telegram Bot framework powered by Lightspeed Engine™. It delivers maximum throughput with minimal overhead—no bloat, no unnecessary dependencies, just raw Node.js performance.


✨ Highlights

| Feature | Description | |---------|-------------| | ⚡ Lightspeed Engine™ | Microtask scheduling, update deduplication & memory pooling | | 📡 Broadcast System | Send messages to millions with concurrency control | | 🎭 Scene Manager | Complex multi-step bot flows | | 💬 Conversation Manager | Natural dialog flows with validation | | 💾 Session Manager | User state persistence (Memory/File) | | 🔄 Polling + Webhook | Flexible update ingestion | | 🎹 Keyboard Builder | Fluent API for inline & reply keyboards | | 🛡️ Telegram Verification | Built-in Web App & login validation | | 📝 Rich Formatting | Markdown, MarkdownV2 & HTML | | ⚙️ Middleware Engine | Composable, Express-style middleware | | 🎯 Zero Dependencies | 100% native Node.js APIs | | 📦 Ultra Lightweight | Minimal footprint, maximum speed |


📦 Installation

npm install turbogram-v1

🚀 Quick Start

Polling Mode

import { Bot } from 'turbogram-v1';

const bot = new Bot({ token: 'YOUR_BOT_TOKEN' });

bot.command('/start', async (ctx) => {
  await ctx.reply('Welcome!');
});

bot.startPolling();

Webhook Mode

const bot = new Bot({ token: 'YOUR_BOT_TOKEN' });

bot.startWebhook({ port: 3000, url: 'https://domain.com/webhook' });

⚠️ Important: Correct Usage Guide

✅ DO: Always use async/await

// ✅ CORRECT - Using async/await
bot.command('/start', async (ctx) => {
  await ctx.reply('Hello World!');
});

bot.on('callback_query', async (ctx) => {
  // Always answer callback first
  await ctx.answerCallback();
  
  // Then edit or reply
  await ctx.edit('Updated text');
});

❌ DON'T: Forget async/await

// ❌ WRONG - Missing async/await
bot.command('/start', (ctx) => {
  ctx.reply('Hello World!'); // Promise not awaited!
});

bot.on('callback_query', (ctx) => {
  ctx.edit('Updated text'); // Will fail silently!
});

📚 Complete API Reference

Bot Methods

| Method | Description | Example | |--------|-------------|---------| | bot.command(command, handler) | Register command handler | bot.command('/start', async (ctx) => {}) | | bot.hears(pattern, handler) | Listen for text patterns | bot.hears(/hello/i, async (ctx) => {}) | | bot.on(event, handler) | Listen for events | bot.on('message', async (ctx) => {}) | | bot.use(middleware) | Add middleware | bot.use(async (ctx, next) => {}) | | bot.catch(handler) | Global error handler | bot.catch((error, ctx) => {}) | | bot.startPolling() | Start long polling | await bot.startPolling() | | bot.stopPolling() | Stop polling | bot.stopPolling() | | bot.startWebhook(options) | Start webhook server | await bot.startWebhook({ port: 3000 }) | | bot.broadcast(params) | Send mass messages | await bot.broadcast({ chatIds, text }) | | bot.broadcastPhoto(params) | Broadcast photos | await bot.broadcastPhoto({ chatIds, photo }) | | bot.verifyTelegramUser(data) | Verify Telegram user | bot.verifyTelegramUser(initData) |

Context Methods (ctx)

| Method | Description | Returns | |--------|-------------|---------| | ctx.reply(text, options?) | Send/reply text message | Promise<Message> | | ctx.replyHTML(text, options?) | Send HTML formatted message | Promise<Message> | | ctx.replyMarkdown(text, options?) | Send Markdown formatted | Promise<Message> | | ctx.replyMarkdownV2(text, options?) | Send MarkdownV2 (auto-escaped) | Promise<Message> | | ctx.replyPhoto(photo, options?) | Send photo | Promise<Message> | | ctx.replyVideo(video, options?) | Send video | Promise<Message> | | ctx.replyDocument(doc, options?) | Send document | Promise<Message> | | ctx.replyAudio(audio, options?) | Send audio | Promise<Message> | | ctx.replyVoice(voice, options?) | Send voice | Promise<Message> | | ctx.replyAnimation(anim, options?) | Send animation | Promise<Message> | | ctx.replySticker(sticker, options?) | Send sticker | Promise<Message> | | ctx.replyLocation(lat, lon, options?) | Send location | Promise<Message> | | ctx.replyVenue(lat, lon, title, address, options?) | Send venue | Promise<Message> | | ctx.replyContact(phone, firstName, options?) | Send contact | Promise<Message> | | ctx.replyPoll(question, options, options?) | Send poll | Promise<Message> | | ctx.replyDice(emoji?, options?) | Send dice | Promise<Message> | | ctx.edit(text, options?) | Edit current message | Promise<Message> | | ctx.editMessageText(text, options?) | Edit message text (safe) | Promise<Message> | | ctx.delete() | Delete current message | Promise<void> | | ctx.answerCallback(text?, options?) | Answer callback query | Promise<void> | | ctx.forwardMessage(chatId, options?) | Forward message | Promise<Message> | | ctx.copyMessage(chatId, options?) | Copy message | Promise<Message> | | ctx.pinMessage(options?) | Pin message | Promise<void> | | ctx.unpinMessage(options?) | Unpin message | Promise<void> | | ctx.enter(sceneName) | Enter a scene | Promise<void> | | ctx.leave() | Leave current scene | Promise<void> | | ctx.reenter() | Re-enter current scene | Promise<void> |

Context Properties

| Property | Type | Description | |----------|------|-------------| | ctx.update | Update | Raw Telegram update object | | ctx.message | Message | Current message object | | ctx.from | User | User who sent the message | | ctx.chat | Chat | Current chat information | | ctx.callbackQuery | CallbackQuery | Callback query data | | ctx.session | Object | User session data (requires SessionManager) | | ctx.scene | SceneManager | Scene manager instance | | ctx.bot | Bot | Bot instance | | ctx.api | TelegramAPI | Direct API access |

Events

| Event | Description | |-------|-------------| | message | New message received | | text | Text message received | | photo | Photo received | | video | Video received | | audio | Audio received | | document | Document received | | animation | Animation received | | voice | Voice message received | | sticker | Sticker received | | contact | Contact shared | | location | Location shared | | venue | Venue shared | | dice | Dice/sticker sent | | poll | Poll received | | poll_answer | Poll answer received | | callback_query | Inline button clicked | | inline_query | Inline query received | | chosen_inline_result | Inline result selected | | edited_message | Message edited | | channel_post | Channel post received | | edited_channel_post | Channel post edited | | chat_member | Chat member updated | | my_chat_member | Bot's chat member updated | | chat_join_request | Chat join request |

Keyboard Builders

| Method | Description | Example | |--------|-------------|---------| | InlineKeyboard() | Create inline keyboard | InlineKeyboard().callback('Click', 'data') | | ReplyKeyboard() | Create reply keyboard | ReplyKeyboard().text('Button') | | RemoveKeyboard() | Remove keyboard | RemoveKeyboard() | | ForceReplyMarkup() | Force reply | ForceReplyMarkup() |

Keyboard Builder Methods

| Method | Description | Keyboard Type | |--------|-------------|---------------| | .text(text) | Text button | Reply | | .callback(text, data) | Callback button | Inline | | .url(text, url) | URL button | Inline | | .contactRequest(text) | Contact request | Reply | | .locationRequest(text) | Location request | Reply | | .pollRequest(text, type?) | Poll request | Reply | | .webApp(text, url) | WebApp button | Both | | .login(text, url, options?) | Login button | Inline | | .pay(text) | Pay button | Inline | | .switchInline(text, query) | Switch to inline | Inline | | .switchInlineCurrent(text, query) | Switch inline current | Inline | | .resize(enable) | Resize keyboard | Reply | | .oneTime(enable) | One time keyboard | Reply | | .persistent(enable) | Persistent keyboard | Reply | | .selective(enable) | Selective keyboard | Reply | | .placeholder(text) | Input placeholder | Reply | | .row() | New row | Both | | .build() | Build keyboard object | Both |

Reply Markup Options

// Inline Keyboard Markup
{
  inline_keyboard: [
    [
      { text: 'Button 1', callback_data: 'data1' },
      { text: 'Button 2', url: 'https://example.com' }
    ],
    [
      { text: 'Button 3', callback_data: 'data3' }
    ]
  ]
}

// Reply Keyboard Markup
{
  keyboard: [
    [{ text: 'Button 1' }, { text: 'Button 2' }],
    [{ text: 'Button 3' }]
  ],
  resize_keyboard: true,
  one_time_keyboard: false,
  persistent: true
}

// Remove Keyboard
{
  remove_keyboard: true,
  selective: false
}

// Force Reply
{
  force_reply: true,
  selective: false,
  input_field_placeholder: 'Type here...'
}

Managers

| Class | Purpose | Methods | |-------|---------|---------| | SessionManager | User state persistence | get(), set(), delete(), save(), clear() | | SceneManager | Multi-step bot flows | scene(), enter(), leave(), reenter(), middleware() | | ConversationManager | Natural dialog flows | startConversation(), handleResponse(), middleware() |

Storage Options

| Class | Description | Use Case | |-------|-------------|----------| | MemoryStorage | In-memory storage | Development, small bots | | FileStorage | File-based storage | Production, data persistence |

Utilities

import { 
  // Timing
  sleep,           // sleep(1000) - Wait for ms
  debounce,        // debounce(fn, 300) - Debounce function
  throttle,        // throttle(fn, 100) - Throttle function
  
  // Formatting
  escapeHTML,      // escapeHTML('<b>') -> '&lt;b&gt;'
  escapeMarkdown,  // escapeMarkdown('*text*') -> '\*text\*'
  escapeMarkdownV2,// escapeMarkdownV2('!text') -> '\!text'
  
  // Data
  randomId,        // randomId(10) - Random ID
  chunk,           // chunk(array, 3) - Split array
  deepMerge,       // deepMerge(obj1, obj2) - Deep merge objects
  
  // Files
  downloadFile,    // downloadFile(url, path) - Download file
  getFileLink,     // getFileLink(token, filePath) - Get file URL
  
  // Commands
  parseCommand,    // parseCommand('/cmd arg1 arg2') - Parse command
  
  // Verification
  verifyHash,      // verifyHash(data, token) - Verify hash
  verifySignature, // verifySignature(initData, token) - Verify signature
} from 'turbogram-v1';

🎹 Complete Keyboard Examples

Inline Keyboard with Callbacks

import { InlineKeyboard } from 'turbogram-v1';

bot.command('/menu', async (ctx) => {
  const keyboard = InlineKeyboard()
    .callback('✅ Approve', 'approve')
    .callback('❌ Reject', 'reject')
    .row()
    .url('🌐 Website', 'https://example.com')
    .switchInline('📤 Share', 'check this')
    .row()
    .callback('🔙 Back', 'back')
    .build();

  await ctx.reply('Choose an action:', { reply_markup: keyboard });
});

// Handle callbacks correctly
bot.on('callback_query', async (ctx) => {
  const data = ctx.callbackQuery?.data;
  
  // IMPORTANT: Always answer callback first!
  await ctx.answerCallback();
  
  // Then handle the action
  switch (data) {
    case 'approve':
      await ctx.edit('✅ Approved!');
      break;
    case 'reject':
      await ctx.edit('❌ Rejected!');
      break;
    case 'back':
      await ctx.edit('🔙 Back to main menu');
      break;
  }
});

Reply Keyboard with Options

import { ReplyKeyboard, RemoveKeyboard } from 'turbogram-v1';

bot.command('/contact', async (ctx) => {
  const keyboard = ReplyKeyboard()
    .resize(true)
    .oneTime(true)
    .placeholder('Share your info...')
    .contactRequest('📱 Share Contact')
    .row()
    .locationRequest('📍 Share Location')
    .row()
    .text('❌ Cancel')
    .build();

  await ctx.reply('Please share your information:', {
    reply_markup: keyboard
  });
});

bot.hears('❌ Cancel', async (ctx) => {
  await ctx.reply('Keyboard removed. Use /start to return.', {
    reply_markup: RemoveKeyboard()
  });
});

🔄 Callback Query Best Practices

✅ Correct Pattern

// Always follow this pattern for callbacks
bot.on('callback_query', async (ctx) => {
  const data = ctx.callbackQuery?.data;
  const userId = ctx.from?.id;
  
  // Step 1: Answer callback immediately
  await ctx.answerCallback();
  
  // Step 2: Try to edit the message
  try {
    await ctx.edit(`You selected: ${data}`, {
      reply_markup: newKeyboard
    });
  } catch (error) {
    // Step 3: If edit fails, send new message
    console.log('Edit failed, sending new message');
    await ctx.reply(`You selected: ${data}`, {
      reply_markup: newKeyboard
    });
  }
});

❌ Wrong Pattern (Will Cause Errors)

// DON'T DO THIS!
bot.on('callback_query', (ctx) => {
  const data = ctx.callbackQuery?.data;
  
  // Missing async/await
  // Missing answerCallback first
  // Missing error handling
  ctx.edit(`You selected: ${data}`); // ❌ Will fail!
});

📝 Message Editing Guide

edit() vs editMessageText()

// ctx.edit() - Automatically uses current message ID
await ctx.edit('Updated text', {
  parse_mode: 'HTML',
  reply_markup: keyboard
});

// ctx.editMessageText() - Same but more explicit
await ctx.editMessageText('Updated text', {
  chat_id: chatId,
  message_id: messageId,
  parse_mode: 'HTML',
  reply_markup: keyboard
});

When Edit Fails

// Safe edit function
async function safeEdit(ctx, text, options = {}) {
  try {
    // Check if text is not empty
    if (!text || text.trim() === '') {
      throw new Error('Text cannot be empty');
    }
    
    // Try to edit
    await ctx.edit(text, options);
    return true;
  } catch (error) {
    // If edit fails, send new message
    console.log('Edit failed:', error.message);
    await ctx.reply(text, options);
    return false;
  }
}

// Usage
bot.on('callback_query', async (ctx) => {
  await ctx.answerCallback();
  
  await safeEdit(ctx, '✅ Action completed!', {
    reply_markup: getMainMenuKeyboard()
  });
});

💾 Session Manager

Store user data across messages:

import { SessionManager, sessionMiddleware, MemoryStorage } from 'turbogram-v1';

const sessions = new SessionManager({
  ttl: 3600, // 1 hour
  storage: new MemoryStorage(),
});

bot.use(sessionMiddleware(sessions));

// Now ctx.session is available
bot.on('text', async (ctx) => {
  ctx.session.messageCount = (ctx.session.messageCount || 0) + 1;
  ctx.session.lastMessage = ctx.message?.text;
  
  await ctx.reply(`Messages sent: ${ctx.session.messageCount}`);
});

🎭 Scene Manager

Build complex multi-step flows:

import { SceneManager } from 'turbogram-v1';

const scenes = new SceneManager();

scenes.scene('order')
  .enter('order', async (ctx) => {
    await ctx.reply('What would you like to order?');
  })
  .hear('order', /pizza|burger/i, async (ctx) => {
    ctx.session.order = ctx.message?.text;
    await ctx.reply('What size? (Small/Medium/Large)');
  })
  .hear('order', /small|medium|large/i, async (ctx) => {
    ctx.session.size = ctx.message?.text;
    await ctx.reply(`✅ Ordered: ${ctx.session.size} ${ctx.session.order}!`);
    await ctx.leave();
  });

scenes.default('main');
bot.use(scenes.middleware());

bot.command('/order', async (ctx) => {
  await ctx.enter('order');
});

💬 Conversation Manager

Natural dialog flows with validation:

import { ConversationManager } from 'turbogram-v1';

const conversations = new ConversationManager();
bot.use(conversations.middleware());

bot.command('/register', async (ctx) => {
  await conversations.startConversation(ctx, {
    id: 'registration',
    steps: [
      {
        question: 'What is your name?',
        validate: (answer) => answer.length >= 2,
        errorMessage: 'Name must be at least 2 characters.',
      },
      {
        question: 'How old are you?',
        validate: (answer) => {
          const age = parseInt(answer);
          return age >= 13 && age <= 120;
        },
        errorMessage: 'Please enter a valid age (13-120).',
        transform: (answer) => parseInt(answer),
      },
    ],
    onComplete: async (answers, ctx) => {
      await ctx.replyHTML(`
<b>✅ Registration Complete!</b>
<b>Name:</b> ${answers.step_0}
<b>Age:</b> ${answers.step_1}
      `);
    },
    onCancel: async (ctx) => {
      await ctx.reply('❌ Registration cancelled.');
    },
    timeout: 300000,
  });
});

📡 Broadcast System

await bot.broadcast({
  chatIds: [123, 456, 789],
  text: '📢 Important Announcement',
  options: {
    concurrency: 5,
    delay: 50,
    retry: 3,
    ignoreErrors: true,
    onProgress: (sent, total) => {
      console.log(`Progress: ${sent}/${total}`);
    },
    onComplete: (results) => {
      console.log(`✅ Sent ${results.length} messages`);
    },
    onError: (error, chatId) => {
      console.error(`Failed for ${chatId}:`, error.message);
    }
  }
});

⚡ Lightspeed Engine™ Architecture

| Component | Benefit | |-----------|---------| | Microtask Scheduler | Sub-millisecond update dispatch | | Update Deduplication | Prevents redundant processing | | Memory Pooling | Reduces GC pressure under load | | Keep-Alive Connections | HTTP agent reuse for faster requests | | JSON Parse Cache | Avoids re-parsing identical payloads | | Batch Processing | Processes 50 updates per microtask | | Lazy Context Creation | Minimal object allocation |


🎯 Complete Production Example

import { 
  Bot, SessionManager, SceneManager, ConversationManager,
  sessionMiddleware, InlineKeyboard, ReplyKeyboard,
  MemoryStorage
} from 'turbogram-v1';

const bot = new Bot({ token: process.env.BOT_TOKEN });

// Setup managers
const sessions = new SessionManager({ storage: new MemoryStorage() });
const scenes = new SceneManager();
const conversations = new ConversationManager();

// Setup middleware pipeline
bot.use(sessionMiddleware(sessions));
bot.use(scenes.middleware());
bot.use(conversations.middleware());

// Request logger middleware
bot.use(async (ctx, next) => {
  const start = Date.now();
  await next();
  console.log(`Request: ${Date.now() - start}ms`);
});

// Main scene
scenes.scene('main').default('main')
  .enter('main', async (ctx) => {
    const keyboard = ReplyKeyboard()
      .resize(true)
      .text('🛒 Shop')
      .text('👤 Profile')
      .build();
    await ctx.reply('Main Menu:', { reply_markup: keyboard });
  });

// Shop scene
scenes.scene('shop')
  .enter('shop', async (ctx) => {
    const keyboard = InlineKeyboard()
      .callback('📦 Product 1', 'buy_1')
      .callback('📦 Product 2', 'buy_2')
      .build();
    await ctx.reply('Products:', { reply_markup: keyboard });
  });

// Commands
bot.command('/start', async (ctx) => {
  ctx.session.visits = (ctx.session.visits || 0) + 1;
  await ctx.enter('main');
});

// Text handlers
bot.hears('🛒 Shop', async (ctx) => {
  await ctx.enter('shop');
});

// Callback handler (CORRECT PATTERN)
bot.on('callback_query', async (ctx) => {
  await ctx.answerCallback();
  
  try {
    await ctx.edit('✅ Added to cart!');
  } catch (error) {
    await ctx.reply('✅ Added to cart!');
  }
});

// Error handler
bot.catch(async (error, ctx) => {
  console.error('Error:', error.message);
  
  try {
    if (ctx?.callbackQuery) {
      await ctx.answerCallback('⚠️ Error occurred');
    } else if (ctx?.message) {
      await ctx.reply('⚠️ An error occurred. Please try again.');
    }
  } catch (e) {
    console.error('Failed to send error message:', e.message);
  }
});

// Start bot
bot.startPolling().then(() => {
  console.log('✅ Bot is running!');
}).catch(console.error);

// Graceful shutdown
process.once('SIGINT', () => {
  console.log('Shutting down...');
  bot.stopPolling();
  process.exit(0);
});

📊 Performance Benchmarks

| Metric | Value | |--------|-------| | Update Processing | < 1ms per update | | Memory Usage | < 10MB base | | Middleware Overhead | < 0.1ms | | Concurrent Users | 10,000+ | | Dependencies | 0 |


📄 License

MIT © TurboGram

Built for speed. Designed for scale.


🤝 Contributing

Contributions are welcome! Please:

  1. Fork the repository
  2. Create a feature branch
  3. Submit a Pull Request

For major changes, please open an issue first to discuss your ideas.


🌟 Star us on GitHub | 🐛 Report Issues | 💬 Join Community