Skip to content

Repository files navigation

🤖 MTCute Modular Bot Template

Telegram bot template built with mtcute, Bun, TypeScript, and Drizzle ORM. Features a NestJS-inspired modular architecture with dependency injection, decorators, and type safety.

💡 Also check out TelePlate - Our grammY-based bot template!


✨ Features

  • 🏗️ Modular Architecture - NestJS-style modules, services, and decorators
  • 💉 Dependency Injection - Custom DI container with automatic resolution
  • 🎨 Decorator-Based Handlers - Clean and intuitive update handling
  • 🗃️ Drizzle ORM - Type-safe database operations with SQLite
  • 📁 Path Aliases - Clean imports (@core, @common, @database, @modules)
  • Bun Runtime - Lightning-fast performance
  • 🔧 Full TypeScript - End-to-end type safety
  • 📊 Built-in Logger - Configurable logging system
  • ⚙️ Environment Validation - Zod-based configuration validation
  • 🎭 30+ Event Types - Complete mtcute API coverage
  • 🔄 Hot Reload - Auto-restart in development mode

📦 Usage

Follow these steps to set up and run your bot:

1. Create a New Repository

Start by creating a new repository using this template. Click here to create.

2. Environment Variables Setup

Create an environment variables file:

cp .env.example .env

Edit .env and set the required variables:

API_ID=12345678API_HASH=your_api_hashBOT_TOKEN=your_bot_tokenNODE_ENV=developmentLOG_LEVEL=debugSESSION_NAME=bot_sessionDATABASE_URL=./bot-data/bot.db

3. Database Setup

Initialize the database:

bun run db:push

4. Launching the Bot

Development Mode:

# Install dependencies
bun install
# Start bot with hot reload
bun run dev

Production Mode:

# Install production dependencies only
bun install --production
# Set NODE_ENV to production in .env# Then start the bot
bun run start

📝 List of Available Commands

  • bun run dev — Start in development mode with hot reload
  • bun run start — Start in production mode
  • bun run db:push — Push database schema
  • bun run db:studio — Open Drizzle Studio (database GUI)
  • bun run db:generate — Generate migrations

🗂️ Directory Structure

project-root/
├── src/
│ ├── core/ # Core framework
│ │ ├── di/ # Dependency injection
│ │ │ ├── container.ts # DI container
│ │ │ └── metadata.ts # Metadata keys
│ │ ├── decorators/ # All decorators
│ │ │ ├── injectable.decorator.ts
│ │ │ ├── inject.decorator.ts
│ │ │ ├── module.decorator.ts
│ │ │ ├── update.decorators.ts # 30+ event decorators
│ │ │ ├── message.decorators.ts # Message filters
│ │ │ ├── callback.decorators.ts # Callback queries
│ │ │ ├── inline.decorators.ts # Inline mode
│ │ │ └── chat.decorators.ts # Chat events
│ │ ├── interfaces/
│ │ │ └── module.interface.ts
│ │ └── module-loader.ts # Module loader
│ ├── common/ # Common utilities
│ │ ├── config/ # Configuration
│ │ │ ├── env.schema.ts # Zod validation
│ │ │ ├── env.validator.ts
│ │ │ ├── env.service.ts
│ │ │ └── config.module.ts
│ │ └── logger/ # Logging system
│ │ ├── logger.service.ts
│ │ ├── logger.interface.ts
│ │ └── logger.module.ts
│ ├── database/ # Database layer
│ │ ├── schema/ # Drizzle schemas
│ │ │ ├── users.schema.ts
│ │ │ └── chats.schema.ts
│ │ ├── db.service.ts
│ │ └── database.module.ts
│ ├── modules/ # Bot modules
│ │ ├── user/ # User module
│ │ │ ├── user.service.ts # Business logic
│ │ │ ├── user.updates.ts # Update handlers
│ │ │ └── user.module.ts # Module definition
│ │ └── chat/ # Chat module
│ │ ├── chat.service.ts
│ │ ├── chat.updates.ts
│ │ └── chat.module.ts
│ ├── bot.module.ts # Root module
│ └── index.ts # Entry point
├── drizzle/ # Database migrations
├── drizzle.config.ts # Drizzle configuration
├── tsconfig.json # TypeScript config with paths
├── package.json
└── .env # Environment variables

🚀 Quick Start Example

Create a New Module

// src/modules/hello/hello.service.tsimport{Injectable,Inject}from'@core/decorators';import{LOGGER}from'@common/logger/constants';importtype{ILogger}from'@common/logger/logger.interface';
@Injectable()exportclassHelloService{constructor(@Inject(LOGGER)privatereadonlylogger: ILogger){}getGreeting(name: string): string{this.logger.log(`Generating greeting for ${name}`);return`Hello, ${name}! 👋`;}}
// src/modules/hello/hello.updates.tsimport{Inject}from'@core/decorators';import{OnCommand,OnText}from'@core/decorators';import{TELEGRAM_CLIENT}from'@core/module-loader';import{HelloService}from'./hello.service';importtype{TelegramClient,Message}from'@mtcute/bun';exportclassHelloUpdates{constructor(
@Inject(HelloService)privatereadonlyhelloService: HelloService,
@Inject(TELEGRAM_CLIENT)privatereadonlyclient: TelegramClient){}
@OnCommand('hello')asynchandleHello(msg: Message){constgreeting=this.helloService.getGreeting(msg.sender.firstName);awaitthis.client.sendText(msg.chat.id,greeting);}
@OnText(/hi|hey/i)asynchandleGreeting(msg: Message){awaitthis.client.sendText(msg.chat.id,'👋 Hi there!');}}
// src/modules/hello/hello.module.tsimport{Module}from'@core/decorators';import{HelloService}from'./hello.service';import{HelloUpdates}from'./hello.updates';import{LoggerModule}from'@common/logger/logger.module';
@Module({imports: [LoggerModule],providers: [HelloService],updates: [HelloUpdates],exports: [HelloService]})exportclassHelloModule{}

Register in bot.module.ts:

import{HelloModule}from'@modules/hello/hello.module';
@Module({imports: [ConfigModule,LoggerModule,DatabaseModule,HelloModule,// Add here]})exportclassBotModule{}

🎨 Available Decorators

Message Handlers

@OnCommand('start')// Single command
@OnCommand(['help','about'])// Multiple commands
@OnText()// Any text message
@OnText('hello')// Text contains "hello"
@OnText(/pattern/i)// Regex pattern
@OnPhoto()// Photo messages
@OnVideo()// Video messages
@OnAudio()// Audio messages
@OnVoice()// Voice messages
@OnDocument()// Document messages
@OnSticker()// Stickers
@OnAnimation()// GIFs
@OnContact()// Contacts
@OnLocation()// Location
@OnPoll()// Polls
@OnDice()// Dice

Update Handlers

@OnNewMessage()// New message
@OnEditMessage()// Message edited
@OnDeleteMessage()// Message deleted
@OnMessageGroup()// Album/media group
@OnChatMemberUpdate()// Member status changed
@OnUserStatusUpdate()// User online/offline
@OnUserTyping()// User typing
@OnHistoryRead()// Messages read
@OnBotStopped()// Bot blocked by user
@OnPollUpdate()// Poll updated
@OnPollVote()// Poll vote
@OnStoryUpdate()// Story posted
@OnBotReactionUpdate()// Reaction added

Callback & Inline

@OnCallback()// Any callback query
@OnCallback('button_id')// Specific callback data
@OnCallback(/^action_/)// Regex pattern
@OnInline()// Any inline query
@OnInline('search')// Contains text
@OnChosenInline()// Inline result chosen

Chat Events

@OnNewChatMembers()// New members
@OnLeftChatMember()// Member left
@OnPinnedMessage()// Message pinned
@OnNewChatTitle()// Title changed
@OnNewChatPhoto()// Photo changed

🗃️ Database Operations

Define Schema

// src/database/schema/posts.schema.tsimport{sqliteTable,integer,text}from'drizzle-orm/sqlite-core';exportconstposts=sqliteTable('posts',{id: integer('id').primaryKey({autoIncrement: true}),title: text('title').notNull(),content: text('content').notNull(),userId: integer('user_id').notNull(),createdAt: integer('created_at',{mode: 'timestamp'}).notNull(),});exporttypePost=typeofposts.$inferSelect;exporttypeNewPost=typeofposts.$inferInsert;

Use in Service

import{eq}from'drizzle-orm';
@Injectable()exportclassPostService{constructor(@Inject(DbService)privatereadonlydb: DbService){}asynccreatePost(data: NewPost){returnawaitthis.db.db.insert(posts).values(data).returning();}asyncgetPost(id: number){returnawaitthis.db.db.query.posts.findFirst({where: eq(posts.id,id),});}}

🔧 Configuration

All configuration is done via environment variables validated with Zod:

// src/common/config/env.schema.tsexportconstenvSchema=z.object({API_ID: z.coerce.number().positive(),API_HASH: z.string().min(1),BOT_TOKEN: z.string().min(1),// Add your custom variablesMY_VAR: z.string().default('default_value'),});

Access in services:

constructor(@Inject(ENV_SERVICE)privatereadonlyenv: EnvService){constapiId=this.env.apiId;constcustom=this.env.get('MY_VAR');}

🚢 Deployment

Using Docker

FROM oven/bun:latest
WORKDIR /app
COPY package.json bun.lockb ./
RUN bun install --frozen-lockfile --production
COPY . .
RUN bun run db:push
CMD ["bun", "run", "start"]

Build and run:

docker build -t telegram-bot .
docker run -d --env-file .env telegram-bot

Using PM2

# Install PM2
bun add -g pm2
# Start
pm2 start bun --name "telegram-bot" -- run start
# Monitor
pm2 logs telegram-bot
pm2 monit

💡 Best Practices

  1. Keep modules focused - One responsibility per module
  2. Use dependency injection - Better testability and maintainability
  3. Leverage path aliases - Keep imports clean (@core, @common, etc.)
  4. Log important events - Use the logger service
  5. Validate all inputs - Use Zod schemas
  6. Handle errors gracefully - Wrap handlers in try-catch
  7. Type everything - Take advantage of TypeScript

📚 Learn More


🤝 Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/amazing)
  3. Commit your changes (git commit -m 'Add amazing feature')
  4. Push to the branch (git push origin feature/amazing)
  5. Open a Pull Request

📄 License

This project is licensed under the MIT License - see the LICENSE file for details.


🙏 Acknowledgments


📞 Support

If you like this project, please consider giving it a ⭐️ on GitHub!

Made with ❤️ by ByteHolic

About

Telegram Bot Template Based On MTCute

Topics

Resources

Stars

18 stars

Watchers

1 watching

Forks

Used by

Contributors

Languages