An open API service indexing awesome lists of open source software.

https://github.com/byteholic/teleplate

Telegram Bot Template with Grammy & Hono
https://github.com/byteholic/teleplate

bot-template grammy grammyjs hono honojs telegram telegram-bot-template

Last synced: 6 days ago
JSON representation

Telegram Bot Template with Grammy & Hono

Awesome Lists containing this project

README

          

# 🤖 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](https://github.com/ByteHolic/TelePlate/tree/grammy-version) - 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](https://github.com/ByteHolic/TelePlate/generate).

### 2. Environment Variables Setup

Create an environment variables file:

```bash
cp .env.example .env
```

Edit `.env` and set the required variables:

```env
API_ID=12345678
API_HASH=your_api_hash
BOT_TOKEN=your_bot_token
NODE_ENV=development
LOG_LEVEL=debug
SESSION_NAME=bot_session
DATABASE_URL=./bot-data/bot.db
```

### 3. Database Setup

Initialize the database:

```bash
bun run db:push
```

### 4. Launching the Bot

**Development Mode:**

```bash
# Install dependencies
bun install

# Start bot with hot reload
bun run dev
```

**Production Mode:**

```bash
# 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

```typescript
// src/modules/hello/hello.service.ts
import { Injectable, Inject } from '@core/decorators';
import { LOGGER } from '@common/logger/constants';
import type { ILogger } from '@common/logger/logger.interface';

@Injectable()
export class HelloService {
constructor(@Inject(LOGGER) private readonly logger: ILogger) {}

getGreeting(name: string): string {
this.logger.log(`Generating greeting for ${name}`);
return `Hello, ${name}! 👋`;
}
}
```

```typescript
// src/modules/hello/hello.updates.ts
import { Inject } from '@core/decorators';
import { OnCommand, OnText } from '@core/decorators';
import { TELEGRAM_CLIENT } from '@core/module-loader';
import { HelloService } from './hello.service';
import type { TelegramClient, Message } from '@mtcute/bun';

export class HelloUpdates {
constructor(
@Inject(HelloService) private readonly helloService: HelloService,
@Inject(TELEGRAM_CLIENT) private readonly client: TelegramClient
) {}

@OnCommand('hello')
async handleHello(msg: Message) {
const greeting = this.helloService.getGreeting(msg.sender.firstName);
await this.client.sendText(msg.chat.id, greeting);
}

@OnText(/hi|hey/i)
async handleGreeting(msg: Message) {
await this.client.sendText(msg.chat.id, '👋 Hi there!');
}
}
```

```typescript
// src/modules/hello/hello.module.ts
import { 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]
})
export class HelloModule {}
```

Register in `bot.module.ts`:

```typescript
import { HelloModule } from '@modules/hello/hello.module';

@Module({
imports: [
ConfigModule,
LoggerModule,
DatabaseModule,
HelloModule, // Add here
]
})
export class BotModule {}
```

***

## 🎨 Available Decorators

### Message Handlers

```typescript
@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

```typescript
@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

```typescript
@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

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

***

## 🗃️ Database Operations

### Define Schema

```typescript
// src/database/schema/posts.schema.ts
import { sqliteTable, integer, text } from 'drizzle-orm/sqlite-core';

export const posts = 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(),
});

export type Post = typeof posts.$inferSelect;
export type NewPost = typeof posts.$inferInsert;
```

### Use in Service

```typescript
import { eq } from 'drizzle-orm';

@Injectable()
export class PostService {
constructor(@Inject(DbService) private readonly db: DbService) {}

async createPost(data: NewPost) {
return await this.db.db.insert(posts).values(data).returning();
}

async getPost(id: number) {
return await this.db.db.query.posts.findFirst({
where: eq(posts.id, id),
});
}
}
```

***

## 🔧 Configuration

All configuration is done via environment variables validated with Zod:

```typescript
// src/common/config/env.schema.ts
export const envSchema = z.object({
API_ID: z.coerce.number().positive(),
API_HASH: z.string().min(1),
BOT_TOKEN: z.string().min(1),
// Add your custom variables
MY_VAR: z.string().default('default_value'),
});
```

Access in services:

```typescript
constructor(@Inject(ENV_SERVICE) private readonly env: EnvService) {
const apiId = this.env.apiId;
const custom = this.env.get('MY_VAR');
}
```

***

## 🚢 Deployment

### Using Docker

```dockerfile
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:

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

### Using PM2

```bash
# 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

- [mtcute Documentation](https://mtcute.dev) - Telegram client library
- [Drizzle ORM](https://orm.drizzle.team) - TypeScript ORM
- [Bun](https://bun.sh) - JavaScript runtime
- [TelePlate](https://github.com/ByteHolic/TelePlate) - grammY-based alternative

***

## 🤝 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](LICENSE) file for details.

***

## 🙏 Acknowledgments

- [mtcute](https://github.com/mtcute/mtcute) - Telegram client library
- [NestJS](https://nestjs.com) - Architecture inspiration
- [Drizzle ORM](https://orm.drizzle.team) - Database toolkit
- [Bun](https://bun.sh) - Fast all-in-one runtime

***

## 📞 Support

If you like this project, please consider giving it a ⭐️ on [GitHub](https://github.com/ByteHolic/TelePlate)!

**Made with ❤️ by [ByteHolic](https://github.com/ByteHolic)**