A template for building Telegram bots in Python using aiogram 3.x, aiogram-dialog, and a modern technology stack.
| Component | Technology |
|---|---|
| Framework | aiogram 3.x |
| Dialogs | aiogram-dialog |
| DI Container | dishka |
| Database | PostgreSQL + SQLAlchemy 2.x (async) |
| ORM | SQLAlchemy 2.x |
| FSM Storage | Redis |
| Task Scheduler | TaskIQ + PostgreSQL + Redis |
| Configuration | Dynaconf |
| Linter/Formatter | Ruff |
| Type Checker | Ty |
git clone <repo-url>
cd telegram-bot-template
# Create .env file based on the example below
touch .env# Bot
BOT_TOKEN=your_telegram_bot_token
BOT_ADMINS=123456789 987654321 # Admin IDs separated by space
# Postgres
POSTGRES_DB=bot_db
POSTGRES_USER=bot_user
POSTGRES_PASSWORD=secure_password_min_7_chars
POSTGRES_HOST=postgres_db
# Redis
REDIS_HOST=redis
REDIS_PASSWORD=redis_password_min_7_chars
# Environment
ENV_FOR_DYNACONF=production # or defaultdocker compose up -d# Install dependencies
pip install -e .
# Run
python src/main.pytelegram-bot-template/
├── src/
│ ├── main.py # Application entry point
│ ├── bot/
│ │ ├── dialogs/ # aiogram-dialog dialogs
│ │ │ ├── flows/ # Dialog flows
│ │ │ │ └── start/ # Start dialog
│ │ │ │ ├── dialog.py # Window definitions
│ │ │ │ ├── handlers.py # Command handlers
│ │ │ │ └── states.py # Dialog states
│ │ │ └── common/ # Common components
│ │ ├── middlewares/ # Middleware (ACL, logging)
│ │ └── commands.py # Menu commands setup
│ ├── core/
│ │ ├── main_config.py # Pydantic configuration
│ │ ├── logger.py # Logging setup
│ │ └── enum.py # Enums (roles)
│ ├── db/
│ │ ├── models/ # SQLAlchemy models
│ │ └── repository/ # Repositories (CRUD)
│ ├── infrastructure/
│ │ └── di/ # dishka DI providers
│ │ └── providers/ # Bot, DB, Redis, Repositories
│ ├── services/ # Business logic
│ └── tasks/ # TaskIQ background tasks
│ ├── broker.py # Redis broker setup
│ ├── scheduler.py # Scheduler
│ └── tasks.py # Task definitions
├── settings.toml # Dynaconf configuration
├── docker-compose.yml # Infrastructure
└── Dockerfile # Image build
The project uses dishka for dependency injection:
ConfigProvider— configurationPostgresProvider— DB sessions (Scope.REQUEST)RedisProvider— Redis connectionRepositoryProvider— repositoriesBotProvider— bot instance
Example usage in a handler:
from dishka.integrations.aiogram import inject
from aiogram_dialog import DialogManager
@inject
async def my_handler(
message: Message,
dialog_manager: DialogManager,
user_repo: FromDishka[UserRepository],
) -> None:
user = await user_repo.get_by_id(message.from_user.id)Dialogs are located in src/bot/dialogs/flows/:
from aiogram_dialog import Dialog, Window
from aiogram_dialog.widgets.kbd import Start
from aiogram_dialog.widgets.text import Const
my_dialog = Dialog(
Window(
Const("Hello!"),
Start(Const("Next"), id="next", state=NextSG.start),
state=MySG.start,
),
)ACLMiddleware handles access control:
- Super admins (from
BOT_ADMINS) — full access - Regular users — checked against DB (
is_active,role) - Inactive users — access denied
Tasks are defined in src/tasks/tasks.py:
@broker.task
@inject(patch_module=True)
async def send_notification(
bot: FromDishka[Bot],
user_id: int,
) -> None:
await bot.send_message(user_id, "Notification!")Run worker:
python -m taskiq worker tasks.broker:broker --fs-discoverRun scheduler:
python -m taskiq scheduler tasks.scheduler:scheduler --fs-discover| Role | Description |
|---|---|
super_admin |
Full access, set via BOT_ADMINS |
admin |
Extended rights, set in DB |
The project uses Alembic for database migrations.
The migrate.sh script automates migration creation with testing:
# Create and test migration
./migrate.sh "add_users_table"What the script does:
- Starts PostgreSQL container
- Generates migration file (
alembic revision --autogenerate) - Test drive: upgrade → downgrade → upgrade
- Checks model/database consistency
- Stops the container
# Locally
cd src
alembic revision --autogenerate -m "description"
alembic upgrade head
# In Docker (runs automatically on startup)
docker compose run --rm migrations# Check
ruff check src
# Auto-fix
ruff check --fix src
# Format
ruff format src# Run Ty type checker
python -m ty src- Python 3.14+
- PostgreSQL 18+
- Redis 8+
MIT License