Telegram Web App (или Telegram Mini Apps) — это веб-приложения, которые функционируют внутри мессенджера Telegram, используя стандартные веб-технологии, такие как HTML, CSS и JavaScript. Они представлены в виде веб-страниц, которые интегрированы с Telegram. Это позволяет пользователям взаимодействовать с ботами, синхронизировать данные и получать удобный доступ к различным сервисам.
Мини-приложения в Telegram делают взаимодействие с цифровыми сервисами проще и добавляют новые возможности мессенджеру. Они могут использоваться для решения различных задач: от покупок и бронирований до игр и розыгрышей. Например, Telegram Web Apps позволяют запускать онлайн-магазины, игровые платформы или сервисы аналитики прямо в чатах.
Идея Telegram Mini Apps зародилась, когда Павел Дуров представил концепцию веб-приложений, работающих внутри Telegram. Изначально называвшиеся WebApps, они стали частью стратегии по превращению Telegram в универсальную платформу. Это позволило разработчикам использовать знакомые веб-технологии для создания сервисов без необходимости разрабатывать отдельные мобильные или десктопные приложения.
cloud
Telegram Web Apps предоставляют множество преимуществ, и для тех, кто использует продукт, и для тех, кто его создает.
Чтобы понять, как создать Telegram Web App в Телеграм, важно разобраться в их архитектуре.
Клиентская часть
HTML, CSS, JavaScript: Базовые технологии для разработки интерфейса и пользовательского взаимодействия. Web Apps выглядят как обычные веб-страницы и могут включать функциональность, поддерживаемую этими технологиями.
WebView: Web Apps загружаются и отображаются в WebView внутри Telegram. Это позволяет им работать на любых устройствах и операционных системах, поддерживающих Telegram.
Серверная часть
Серверные технологии: Web Apps могут взаимодействовать с собственными серверными API, написанными на любом языке программирования (например, Node.js, Python, Ruby). Серверная часть отвечает за обработку данных, взаимодействие с базами данных и другими внешними сервисами.
Telegram Bot API: Для интеграции с Telegram Web App могут использовать Telegram Bot API. Это позволяет им взаимодействовать с пользователями через ботов, отправлять и получать сообщения, а также выполнять другие действия.
Взаимодействие с Telegram
SDK: Специальные SDK, предоставляемые Telegram или сообществом, включающие в себя методы и инструменты для упрощения разработки и интеграции Web App с Telegram.
Обновления и деплой
Обновления на сервере: Для обновления Web App необходимо лишь отправить изменения на сервер. Пользователи сразу же получат доступ к обновленной версии при следующем запуске приложения.
Контроль версий: Разработчики могут применять системы контроля версий для управления изменениями в коде и обеспечения стабильности приложения.
Изображение: press-release.ru
Web App в Телеграм находят применение в самых разных областях. Вот несколько примеров:
Онлайн-магазины: Бот @DurgerKingBot позволяет добавлять товары в корзину и оплачивать их прямо в Telegram. Это пример, созданный разработчиками Telegram для демонстрации возможностей Web Apps.
Розыгрыши: Сервисы вроде @BlessMeBot или @GiveShareBot используют Web Apps для отображения условий розыгрышей в каналах и чатах.
Игры: Платформа @Gamee предлагает коллекцию HTML5-игр, где пользователи могут соревноваться с друзьями.
Для вдохновения можно изучить примеры Telegram Web Apps, доступные в Telegram-каналах или в документации.
Создание Mini Apps открывает множество возможностей:
Современность: Telegram Web Apps — это новая технология, привлекающая внимание пользователей.
Узнаваемость бренда: Уникальные Web Apps помогают продвигать бренд и повышать его популярность.
Монетизация: Встроенные покупки, реклама через AdsGram или подписки позволяют зарабатывать. Подробнее о том, как монетизировать свое Telegram-приложение, мы рассказывали, например, в этой статье.
Теперь перейдем к практической части и разработаем Telegram Web App на примере игры-кликера с лидербордом, применяя React для фронтенда и Nest.js для бэкенда.
Прежде чем начать разработку, необходимо проверить, что на вашем компьютере уже есть Node.js. Если нет, то его необходимо скачать с официального сайта.
Также должен быть глобально установлен Nest.js. Чтобы установить Nest.js, нужно запустить командную строку Windows или другую консоль и ввести команду:
npm i -g @nestjs/cli
Теперь можно приступать к разработке проекта.
Windows
Создайте папку на рабочем столе
Запустите командную строку Windows или другой терминал.
Перейдите в созданную вами папку. Например, если вы создали папку на рабочем столе, то введите команду:
cd Desktop
Затем введите еще раз команду cd
, но уже с названием созданной вами папки:
cd mini-app-clicker
Чтобы создать Frontend-часть, выполните команду:
npm create vite@latest frontend
frontend
в данной команде обозначает название папки. Вы можете дать любое название своей папке, которая будет использоваться для Frontend-части.
После выполнения команды вам будет предложено выбрать фреймворк. С помощью стрелок выберите React.
Затем нужно выбрать вариант «JavaScript + SWC».
В этой же консоли введите команду для создания Backend-части:
nest new backend
backend
в данной команде обозначает название папки. Вы можете дать любое название своей папке, которая будет использоваться для Backend-части.
В процессе создания вам будет предложено выбрать менеджер пакетов. С помощью стрелок выберите npm
.
Откройте главную папку вашего проекта (в моем случае это mini-app-clicker
) в любом удобном для вас редакторе кода. Я рекомендую использовать Visual Studio Code (VS Code).
Linux
Создайте папку на рабочем столе. Это можно сделать с помощью следующей команды в терминале:
mkdir ~/Desktop/mini-app-clicker
Перейдите в созданную вами папку. Допустим, вы создали папку на рабочем столе, тогда выполните следующее команду:
cd ~/Desktop/mini-app-clicker
Чтобы создать Frontend-часть, выполните следующую команду:
npm create vite@latest frontend
frontend
в данном случае обозначает название папки. Свою папку для Frontend-части вы можете назвать как угодно.
После выполнения команды вам будет предложено выбрать фреймворк. С помощью стрелок выберите React. Затем выберите вариант JavaScript + SWC.
В этой же консоли введите команду для создания Backend-части:
nest new backend
backend
в данном случае обозначает название папки. Свою папку для Backend-части вы можете назвать как угодно.
При создании у вас попросят выбрать менеджер пакетов. С помощью стрелок выберите npm
.
Откройте главную папку вашего проекта (в данном случае mini-app-clicker
) в любом удобном для вас редакторе кода. Я рекомендую использовать Visual Studio Code (VS Code).
В редакторе кода главная папка будет иметь следующий вид:
При разработке мы будем неоднократно использовать консоль для той или иной цели, поэтому всегда удостоверяйтесь, что вы находитесь в нужной директории проекта перед вводом той или иной команды.
В первую очередь создадим Frontend-часть нашего Mini App. В ней расположим кнопку, на которую мы будем нажимать, прибавление очков, отображение First Name пользователя, а также добавим вкладку с лидербордом.
Перед созданием Frontend-части необходимо выполнить установить зависимости, которые необходимы для запуска нашего визуала.
Откройте консоль и введите команду:
cd frontend
Укажите в ней свое название папки для Frontend’а.
Затем введите:
npm install
После выполнения этой команды появится новая папка под названием node_modules
, в которой будут все нужные файлы, чтобы запустить проект.
Попробуйте запустить пустой проект с помощью команды:
npm run dev
В случае успеха вам предложат открыть проект по ссылке http://localhost:5173/
.
Вы увидите стандартную страницу только что созданного проекта:
Откроем редактор кода повторно. Все необходимые для проекта файлы мы будем создавать в папке src
.
С помощью консоли установим библиотеку для работы с Telegram Web App:
npm i @telegram-apps/sdk
Теперь откройте файл main.jsx
, чтобы внести в него некоторые изменения. В дальнейшем мы еще раз обратимся к этому файлу.
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import './index.css'
import App from './App.jsx'
import { init } from '@telegram-apps/sdk';
const initializeTelegramSDK = async () => {
try {
await init();
} catch (error) {
console.error('Ошибка инициализации:', error);
}
};
initializeTelegramSDK();
createRoot(document.getElementById('root')).render(
<StrictMode>
<App />
</StrictMode>,
)
В окне браузера вы увидите сообщение об ошибке, но на нее можно не обращать внимания — мы сами вызываем ее, чтобы в будущем знать, инициализировалась ли наша библиотека в среде Telegram.
Изменим index.css
, удалив лишние стили, которые нам не нужны. Файл будет выглядеть следующим образом:
:root {
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
line-height: 1.5;
font-weight: 400;
color: #ffffff;
background-color: #181818;
font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
Установим fontawesome-иконки, которые будут использоваться в нашем проекте. Введите команду:
npm i --save @fortawesome/react-fontawesome @fortawesome/fontawesome-svg-core @fortawesome/free-solid-svg-icons @fortawesome/free-regular-svg-icons @fortawesome/free-brands-svg-icons
Теперь откройте файл App.jsx
и введите там следующий код:
import { useState } from 'react';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faTrophy } from '@fortawesome/free-solid-svg-icons';
import './App.css';
import Leaderboard from './Components/Leaderboard';
function App() {
const [score, setScore] = useState(0);
const [showLeaderboard, setShowLeaderboard] = useState(false);
const handleClick = () => {
setScore(score + 1);
};
return (
<div className='App'>
{showLeaderboard ? (
<Leaderboard setShowLeaderboard={setShowLeaderboard} />
) : (
<>
<div className='header'>
<h1 className='firstname'>
<span id='firstname'>Имя</span>
</h1>
</div>
<div className='content'>
<div className='score-container'>
<h2 className='score'>
<span id='score'>{score}</span>
</h2>
</div>
<div className='button-container'>
<button className='button-click' id='button-click' onClick={handleClick}>Нажми</button>
</div>
</div>
<div className='footer'>
<button className='btn-leaderboard' id='btn-leaderboard' onClick={() => setShowLeaderboard(true)}>
<FontAwesomeIcon icon={faTrophy} />
</button>
</div>
</>
)}
</div>
);
}
export default App;
Добавим стили. Откройте файл App.css
и измените его следующим образом:
.App {
display: flex;
flex-direction: column;
align-items: center;
}
.header {
margin-top: 20px;
margin-bottom: 20px;
}
.score-container {
margin-bottom: 20px;
}
.score {
display: flex;
justify-content: center;
font-size: 48px;
}
.button-container {
display: flex;
justify-content: center;
}
.button-click {
width: 200px;
height: 200px;
background-color: #d3862e;
color: white;
border-radius: 50%;
border: none;
cursor: pointer;
display: flex;
justify-content: center;
align-items: center;
font-size: 30px;
font-weight: bold;
margin: 8px 0;
}
.button-click:hover {
opacity: 0.8;
}
.footer {
background-color: #181818;
position: fixed;
bottom: 0;
width: 100%;
color: white;
text-align: center;
padding: 10px 0;
}
.btn-leaderboard {
background-color: transparent;
color: white;
border: none;
padding: 10px 20px;
cursor: pointer;
border-radius: 10px;
font-size: 40px;
}
После этого создадим новую папку Components
внутри src
. В ней создадим два новых файла, Leaderboard.jsx
и Leaderboard.css
.
Откройте файл Leaderboard.jsx
и напишите там следующий код:
import React from 'react';
import PropTypes from 'prop-types';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faHouse } from '@fortawesome/free-solid-svg-icons';
import './Leaderboard.css';
function Leaderboard({ setShowLeaderboard }) {
return (
<div className='Leaderboard'>
<div className='header-leaderboard'>
<h1 className='leaderboard-title'>
Топ игроков
</h1>
</div>
<div className='content-leaderboard'>
<ul className='leaderboard-list'>
<h2>Лучшие игроки:</h2>
<li>
<div className='leaderboard-item'>
<div className='leaderboard-rank'>
<h3 className='leaderboard-place'>
<span id='leaderboard-place'>1</span>.
</h3>
<h3 className='leaderboard-name'>
<span id='leaderboard-name'>Имя</span>
</h3>
</div>
<h3 className='leaderboard-score'>
<span id='leaderboard-score'>10</span>
</h3>
</div>
</li>
<li>
<div className='leaderboard-item'>
<div className='leaderboard-rank'>
<h3 className='leaderboard-place'>
<span id='leaderboard-place'>1</span>.
</h3>
<h3 className='leaderboard-name'>
<span id='leaderboard-name'>Имя</span>
</h3>
</div>
<h3 className='leaderboard-score'>
<span id='leaderboard-score'>10</span>
</h3>
</div>
</li>
<li>
<div className='leaderboard-item'>
<div className='leaderboard-rank'>
<h3 className='leaderboard-place'>
<span id='leaderboard-place'>1</span>.
</h3>
<h3 className='leaderboard-name'>
<span id='leaderboard-name'>Имя</span>
</h3>
</div>
<h3 className='leaderboard-score'>
<span id='leaderboard-score'>10</span>
</h3>
</div>
</li>
</ul>
</div>
<div className='footer-leaderboard'>
<button className='btn-home' onClick={() => setShowLeaderboard(false)}>
<FontAwesomeIcon icon={faHouse} />
</button>
</div>
</div>
);
}
Leaderboard.propTypes = {
setShowLeaderboard: PropTypes.func.isRequired,
}
;
export default Leaderboard;
Добавим стили для страницы с лидербордом. Откройте файл Leaderboard.css
и измените его следующим образом:
.Leaderboard {
display: flex;
flex-direction: column;
align-items: center;
}
.content-leaderboard {
width: 100%;
max-width: 400px;
margin: 20px auto;
}
.leaderboard-list {
list-style: none;
padding: 0;
margin: 0;
width: 300px;
height: 100px;
}
.leaderboard-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px;
margin-bottom: 10px;
background-color: #333;
border-radius: 10px;
height: 50px;
}
.leaderboard-rank {
display: flex;
align-items: center;
gap: 10px;
}
.leaderboard-place,
.leaderboard-name,
.leaderboard-score {
font-size: 24px;
}
.footer-leaderboard {
position: fixed;
bottom: 0;
width: 100%;
color: white;
text-align: center;
padding: 10px 0;
background-color: #181818;
}
.btn-home {
background-color: transparent;
color: white;
border: none;
padding: 10px 20px;
cursor: pointer;
border-radius: 10px;
font-size: 40px;
}
После всех этих изменений у нас будет готова логика кликера, а также заготовка на будущий лидерборд:
Чтобы в браузере ваша страница выглядела в виде телефона, необходимо нажать на F12, а затем нажать на кнопку переключения девайса.
Затем нужно настроить параметры экрана так, чтобы они соответствовали параметрам Mini App. В поле ширина (первая строка) введите значение 360
, а в поле высота (вторая строка) — 800
. Эти значения будут примерным разрешением для Mini App.
Теперь приступим к написанию Backend-части.
Так как мы создаем простой тестовый проект, чтобы познакомиться с разработкой Mini App поближе и сделать это быстро, мы будем использовать SQLite3.
Однако, если вы собираетесь делать большой проект, рассчитанный на тысячи пользователей и большую нагрузку, я рекомендую использовать PostgreSQL или другую подобную базу данных.
Откройте консоль и введите команду cd Backend
, чтобы переключится на папку с Backend, и затем установите все необходимые зависимости, выполнив следующую команду:
npm install @nestjs/common @nestjs/core @nestjs/platform-express @nestjs/config @nestjs/typeorm sqlite3 typeorm class-validator class-transformer
Все необходимые материалы для разработки проекта будем создавать и изменять в папке src
.
Создай файл ormconfig.ts
и напишите в нем настройки для TypeORM (SQLite3):
import { TypeOrmModuleOptions } from '@nestjs/typeorm';
export const typeOrmConfig: TypeOrmModuleOptions = {
type: 'sqlite',
database: 'database.sqlite',
entities: [__dirname + '/**/*.entity{.ts,.js}'],
synchronize: true,
};
Теперь создайте папку с названием users, а в ней файл user.entity.ts
. Изменим файл следующим образом:
import { Entity, Column, PrimaryColumn } from 'typeorm';
@Entity()
export class User {
@PrimaryColumn()
id: string;
@Column()
firstname: string;
@Column({ default: 0 })
points: number;
}
В этой же папке создайте файл user.service.ts
:
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { User } from './user.entity';
@Injectable()
export class UserService {
constructor(
@InjectRepository(User)
private userRepository: Repository<User>,
) {}
async createUser(id: string, firstname: string): Promise<User> {
let user = await this.userRepository.findOne({ where: { id } });
if (!user) {
user = this.userRepository.create({ id, firstname });
await this.userRepository.save(user);
}
return user;
}
async getUser(id: string): Promise<User | null> {
return this.userRepository.findOne({ where: { id } });
}
async updatePoints(id: string, points: number): Promise<User | null> {
const user = await this.getUser(id);
if (user) {
user.points += points;
await this.userRepository.save(user);
return user;
}
return null;
}
async getLeaderboard(): Promise<User[]> {
return this.userRepository.find({ order: { points: 'DESC' }, take: 10 });
}
async getUserById(id: string): Promise<User | null> {
return this.userRepository.findOne({ where: { id } });
}
async getUserRank(
id: string,
): Promise<{ firstname: string; points: number; rank: number } | null> {
const leaderboard = await this.getLeaderboard();
const index = leaderboard.findIndex((user) => user.id === id);
if (index === -1) {
return null;
}
const user = leaderboard[index];
return {
firstname: user.firstname,
points: user.points,
rank: index + 1,
};
}
}
Рядом с этими файлами необходимо создать новый файл user.controller.ts
:
import {
Controller,
Post,
Get,
Body,
Param,
NotFoundException,
} from '@nestjs/common';
import { UserService } from './user.service';
@Controller('users')
export class UserController {
constructor(private readonly userService: UserService) {}
@Post('create')
async createUser(@Body() body: { id: string; firstname: string }) {
return this.userService.createUser(body.id, body.firstname);
}
@Post('update')
async updatePoints(@Body() body: { id: string; points: number }) {
return this.userService.updatePoints(body.id, body.points);
}
@Get('leaderboard')
async getLeaderboard() {
return this.userService.getLeaderboard();
}
@Get('leaderboard/:id')
async getUserRank(@Param('id') id: string) {
return this.userService.getUserRank(id);
}
@Get(':id')
async getUser(@Param('id') id: string) {
const user = await this.userService.getUserById(id);
if (!user) {
throw new NotFoundException('User not found');
}
return { firstname: user.firstname, points: user.points };
}
}
Теперь подключим всё это в один файл. Создайте user.module.ts
и напишите в нем следующее:
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { User } from './user.entity';
import { UserService } from './user.service';
import { UserController } from './user.controller';
@Module({
imports: [TypeOrmModule.forFeature([User])],
controllers: [UserController],
providers: [UserService],
})
export class UserModule {}
Откройте файл app.module.ts
, который находится в папке src
, и измените его:
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { UserModule } from './users/user.module';
import { typeOrmConfig } from './ormconfig';
@Module({
imports: [TypeOrmModule.forRoot(typeOrmConfig), UserModule],
})
export class AppModule {}
Если после внесения всех этих изменений файлы подсвечиваются у вас красным цветом, эта проблема касается проверки форматирования, которую проводит Prettier. Для того чтобы устранить эти проблемы, необходимо выполнить в консоли следующую команду:
npx prettier --write .
На конце команды обязательно должна быть точка.
Запустите Backend с помощью команды:
npm run start
В первый раз запуск может занять много времени, но если всё пройдет хорошо, то в консоли вы увидите примерно следующее:
Теперь необходимо протестировать все запросы, чтобы мы точно знали, что наш Backend работает правильно. Лучше всего для тестирования запросов подойдет приложение Postman.
Откройте Postman на своем ПК.
Создайте новый запрос для создания пользователя, нажав на «+» в верхнем меню.
Рядом с полем для ввода URL нажмите на метод запроса и в открывшемся списке выберите POST-запрос, после чего в поле для ввода URL введите:
http://localhost:3000/users/create
Перейдите во вкладку Body, которая находится под полем для ввода URL. В ней выберите raw, а справа нажмите на Text и выберите метод JSON. В текстовое поле введите:
{
"id": "123456",
"firstname": "Test"
}
Тут мы указываем, какие параметры мы передаем в теле запроса, чтобы создать нового пользователя.
Перейдите во вкладку Headers. В поле Key необходимо ввести Content-Type
, а в Value — application/json
.
Отправляем наш запрос, нажав на кнопку Send. Если всё успешно, то внизу появится ответ от нашего запроса с ID, firstname и points.
А также справа будет указан код ответа 201.
Создайте новый запрос для обновления очков пользователя, нажав на «+» верхнем меню.
Выберите метод POST-запрос, после чего в поле для ввода URL введите:
http://localhost:3000/users/update
Перейдите во вкладку Body. В ней выберите raw и метод JSON. В текстовое поле введите:
{
"id": "123456",
"points": 10
}
Перейдите во вкладку Headers. В поле Key необходимо ввести Content-Type
, а в Value — application/json
.
Отправляем наш запрос, нажав на кнопку Send. Если всё успешно, то вы получите ответ с обновленными очками:
Создайте новый запрос для получения данных пользователя, нажав на «+» верхнем меню.
Выберите метод GET, после чего в поле для ввода URL введите:
http://localhost:3000/users/123456
Будьте внимательны при отправке этого запроса, ведь после users/
необходимо указать ID человека, который уже создан, иначе вам вернет ошибку 404.
Отправляем наш запрос, нажав на кнопку Send. Если всё успешно, то вы получите firstname и points человека:
Также справа будет указан код ответа 200:
Создайте новый запрос для получения лидерборда, нажав на «+» верхнем меню.
Выберите метод GET, после чего в поле для ввода URL введите:
http://localhost:3000/users/leaderboard
Отправляем наш запрос, нажав на кнопку Send. Если всё успешно, то вы получите список людей.
Попробуйте создать еще несколько людей и выдать им очки. После чего снова отправьте запрос на лидерборд и вы увидите, как меняется список от больших очков к меньшим.
Благодаря данным из Postman в процессе тестирования Backend’а мы можем отследить время выполнения запросов и их вес.
Response Time отображает, сколько времени занимает тот или иной процесс. Мы можем проанализировать эти данные и при необходимости оптимизировать запросы, сделав их быстрее.
Response Size и Request Size позволяют можно узнать вес запросов, что также позволит оптимизировать их при необходимости.
Теперь нужно объединить написанные нами Frontend- и Backend-части.
Откройте файл vite.config.js
из Frontend-части и измените его следующим образом, чтобы в будущем при сборке на сервере не возникло ошибок:
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react-swc'
export default defineConfig({
plugins: [react()],
build: {
outDir: 'build',
}
})
Откройте файл main.jsx
и измените его следующим образом:
import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import './index.css';
import App from './App.jsx';
import { init, initData, miniApp } from '@telegram-apps/sdk';
const initializeTelegramSDK = async () => {
try {
await init();
if (miniApp.ready.isAvailable()) {
await miniApp.ready();
window.dispatchEvent(new Event('miniAppReady'));
}
initData.restore();
initData.state();
const user = initData.user();
console.log('Данные пользователя:', user);
if (user) {
window.userId = user.id;
window.firstName = user.first_name;
console.log('ID пользователя:', window.userId);
console.log('Имя пользователя:', window.firstName);
// Ожидаем полной готовности всех данных
window.dispatchEvent(new Event('userIdReady'));
} else {
console.error('Ошибка: Пользовательские данные не загружены!');
}
} catch (error) {
console.error('Ошибка инициализации Telegram Mini App:', error);
}
};
// Дожидаемся обоих событий, затем рендерим приложение
const waitForUserData = async () => {
await new Promise((resolve) => {
const checkReady = () => {
if (window.userId && window.firstName && miniApp.ready.isAvailable()) {
resolve();
}
};
window.addEventListener('userIdReady', checkReady);
window.addEventListener('miniAppReady', checkReady);
checkReady();
});
createRoot(document.getElementById('root')).render(
<StrictMode>
<App />
</StrictMode>,
);
};
initializeTelegramSDK().then(waitForUserData);
Затем откройте и измените App.jsx
:
import { useState, useEffect } from 'react';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faTrophy } from '@fortawesome/free-solid-svg-icons';
import './App.css';
import Leaderboard from './Components/Leaderboard';
import Enquiry from './API/Enquiry';
function App() {
const [score, setScore] = useState(0);
const [firstname, setFirstname] = useState('Загрузка...');
const [showLeaderboard, setShowLeaderboard] = useState(false);
const [leaderboard, setLeaderboard] = useState([]);
useEffect(() => {
const handleScoreUpdate = (event) => {
if (event.detail.points !== undefined) {
setScore(event.detail.points);
}
};
window.addEventListener('updateScore', handleScoreUpdate);
return () => {
window.removeEventListener('updateScore', handleScoreUpdate);
};
}, []);
const handleClick = () => {
setScore((prev) => prev + 1);
window.dispatchEvent(new CustomEvent('updateScoreFromApp', { detail: { points: score + 1 } }));
};
return (
<div className='App'>
<Enquiry
score={score}
setScore={setScore}
setFirstname={setFirstname}
setLeaderboard={setLeaderboard}
/>
{showLeaderboard ? (
<Leaderboard setShowLeaderboard={setShowLeaderboard} leaderboard={leaderboard} />
) : (
<>
<div className='header'>
<h1 className='firstname'><span id='firstname'>{firstname}</span></h1>
</div>
<div className='content'>
<div className='score-container'>
<h2 className='score'><span id='score'>{score}</span></h2>
</div>
<div className='button-container'>
<button className='button-click' onClick={handleClick}>Нажми</button>
</div>
</div>
<div className='footer'>
<button className='btn-leaderboard' onClick={() => setShowLeaderboard(true)}>
<FontAwesomeIcon icon={faTrophy} />
</button>
</div>
</>
)}
</div>
);
}
export default App;
Внесите изменения в Leaderboard.jsx
:
import PropTypes from 'prop-types';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faHouse } from '@fortawesome/free-solid-svg-icons';
import './Leaderboard.css';
function Leaderboard({ setShowLeaderboard, leaderboard }) {
return (
<div className='Leaderboard'>
<div className='header-leaderboard'>
<h1 className='leaderboard-title'>Топ игроков</h1>
</div>
<div className='content-leaderboard'>
<ul className='leaderboard-list'>
{leaderboard.length > 0 ? (
leaderboard.map((player, index) => (
<li key={player.id}>
<div className='leaderboard-item'>
<div className='leaderboard-rank'>
<h3 className='leaderboard-place'>{index + 1}.</h3>
<h3 className='leaderboard-name'>{player.firstname}</h3>
</div>
<h3 className='leaderboard-score'>{player.points}</h3>
</div>
</li>
))
) : (
<p>Лидерборд загружается...</p>
)}
</ul>
</div>
<div className='footer-leaderboard'>
<button className='btn-home' onClick={() => setShowLeaderboard(false)}>
<FontAwesomeIcon icon={faHouse} />
</button>
</div>
</div>
);
}
Leaderboard.propTypes = {
setShowLeaderboard: PropTypes.func.isRequired,
leaderboard: PropTypes.array.isRequired,
};
export default Leaderboard;
Создайте новую папку API
внутри папки src
Frontend’а. Внутри API
создайте файл Enquiry.jsx
. Через него мы будем отправлять запросы на наш Backend.
import { useEffect, useState } from 'react';
import PropTypes from 'prop-types';
const Enquiry = ({ score, setScore, setFirstname, setLeaderboard }) => {
const [isLoaded, setIsLoaded] = useState(false);
useEffect(() => {
const fetchUserData = async () => {
if (!window.userId) return;
try {
let response = await fetch(`/users/${window.userId}`);
if (response.status === 404) {
await fetch('/users/create', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ id: window.userId, firstname: window.firstName }),
});
response = await fetch(`/users/${window.userId}`);
}
const userData = await response.json();
setFirstname(userData.firstname || 'Неизвестный');
setScore(userData.points || 0);
setIsLoaded(true);
} catch (error) {
console.error('Ошибка при получении данных пользователя:', error);
}
};
if (window.userId) {
fetchUserData();
} else {
window.addEventListener('userIdReady', fetchUserData);
}
return () => {
window.removeEventListener('userIdReady', fetchUserData);
};
}, [setScore, setFirstname]);
useEffect(() => {
if (!isLoaded) return;
const updateUserPoints = async () => {
if (!window.userId) return;
try {
await fetch('/users/update', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ id: window.userId, points: score }),
});
} catch (error) {
console.error('Ошибка при обновлении очков:', error);
}
};
updateUserPoints();
}, [score, isLoaded]);
useEffect(() => {
const fetchLeaderboard = async () => {
try {
const response = await fetch('/users/leaderboard');
if (!response.ok) throw new Error('Ошибка загрузки');
const data = await response.json();
setLeaderboard(data);
} catch (error) {
console.error('Ошибка при получении лидерборда:', error);
}
};
fetchLeaderboard();
}, [setLeaderboard]);
return null;
};
Enquiry.propTypes = {
score: PropTypes.number.isRequired,
setScore: PropTypes.func.isRequired,
setFirstname: PropTypes.func.isRequired,
setLeaderboard: PropTypes.func.isRequired,
setUserRank: PropTypes.func.isRequired,
};
export default Enquiry;
Места, где должны находится ссылки на запросы мы пока что оставим пустыми, ведь сперва нужно запустить наш Backend на сервере, чтобы знать, куда отправлять запросы.
Создайте два закрытых репозитория на GitHub для Frontend’а и Backend’а, а затем вернитесь к вашему проекту. Далее нужно проделать следующие действия для обеих частей Mini App:
Перейдите в нужную директорию
Создайте новый репозиторий Git:
git init
Добавьте все изменения к коммиту:
git add .
Создайте коммит:
git commit -m "first commit"
Измените название текущей ветви и установите ее как основную:
git branch -M main
Добавьте удаленный репозиторий и свяжите его с указанным URL. Вместо ссылки на мой репозиторий, предоставьте ссылку на ваш новый репозиторий.
git remote add origin https://github.com/an-vadim-an/mini-app-clicker-backend
Отправьте изменения на репозитории:
git push -u origin main
Когда файлы будут загружены на GitHub, можно приступить к запуску Mini App на сервере. Я буду использовать сервис Apps от Timeweb Cloud. Если вы еще не зарегистрированы на Timeweb Cloud, то для начала необходимо пройти регистрацию.
Прежде всего, необходимо создать новый проект. Дайте ему наименование, а при необходимости — добавьте описание и изображение.
После того как проект был создан, необходимо создать Apps. Прежде всего мы запустим наш Backend. В процессе создания нужно выбрать тип Backend и платформу Nest.js.
Для загрузки проектов необходимо привязать учетную запись на GitHub. После завершения процесса привязки введите имя репозитория, которое вы использовали при создании. В разделе «Регион» выберите тот, который находится ближе к вам и имеет наименьший пинг.
Выберите минимальную конфигурацию — ее достаточно для нашей задачи. В настройках приложения не нужно вносить изменения.
В описании приложения необходимо указать его наименование, при необходимости — комментарий, а также выберите проект, с которым будет связано ваше приложение.
Затем можно активировать процесс развертывания Nest.js на сервере, нажав на кнопку «Запустить деплой».
Через некоторое время приложение будет запущено, и в случае успешного развертывания проекта в журнале деплоя появится сообщение «Deployment successfully completed». Еще до полного запуска нам будет доступен домен, который уже можно скопировать. Расположен он будет на Дашборде, в окошке справа:
В моем случае я буду отправлять запросы на адрес:
https://an-vadim-an-mini-app-clicker-backend-aeae.twc1.net/
Этот адрес необходимо вставить во все запросы, которые находятся в файле Enquiry.jsx
.
После того как вы вставили этот адрес во все запросы, необходимо отправить наше изменения на GitHub, выполнив поочередно команды:
git add .
git commit -m "requests"
git push
Теперь можно запускать наш Frontend. При создании Apps нужно выбрать Frontend и React.
А далее как с Backend. Выбираете нужный репозиторий, регион, конфигурацию. В настройках приложения ничего не меняете и вносите нужную информацию о приложении, не забыв выбрать проект.
Когда Frontend начнет запускаться, вы получите бесплатный домен. Найти его можно в разделе «Настройки» вашего приложения. Его необходимо скопировать.
Теперь необходимо открыть файл main.ts
из Backend’a и изменить его таким образом, чтобы он принимал запросы только от нашей Frontend-части:
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.enableCors({
origin: 'https://an-vadim-an-mini-app-clicker-frontend-ab25.twc1.net/',
methods: 'GET,POST,PUT,DELETE',
allowedHeaders: 'Content-Type,Authorization',
});
await app.listen(process.env.PORT ?? 3000);
}
bootstrap();
Отправьте изменения Backend’a на GitHub.
После того как все необходимые шаги были выполнены, можно приступать к завершающему шагу, после которого можно будет начать тестировать Mini App в Telegram.
Пока Frontend и Backend запускаются, необходимо создать и настроить нашего бота, чтобы Mini App открывался внутри Telegram.
Перейдите в BotFather и запустите его. Выполните команду /newbot
. Бот предложит вам ввести имя для нового бота, а затем его username. Если вы попытаетесь использовать уже занятый username, бот сообщит о невозможности его использования и предложит вам выбрать другой.
При успешном исходе событий бот ответит следующим образом:
Теперь вы можете настроить созданного бота. Для этого используйте команду /mybots
. Бот отобразит список всех созданных ботов. Выберите нужный с помощью инлайн-кнопок. Откроется следующее меню:
Перейдите в «Bot Settings» → «Menu Button» и нажмите на «Configure menu button». BotFather попросит прислать URL, который будет открываться при нажатии на специальную кнопку. Отправьте ему вашу ссылку на Frontend. После отправки он попросит прислать название для этой кнопки. Если вы сделали всё верно, то бот ответит успехом.
Далее нажмите на «Back to Bot» и повторно зайдите в настройки. Теперь необходимо нажать на «Configure Mini App» и в открывшемся меню нажать на инлайн-кнопку «Enable Mini App». Бот снова попросит ссылку.
Снова нажмите на «Back to Bot», зайдите в настройки и опять же кликните по «Configure Mini App». В открывшемся меню нажмите на «Change Mod». Внутри выберите Fullscreen. Благодаря этому приложение будет открываться на весь экран и не будет иметь рамок.
Активировируйте бота, нажав на кнопку «Запустить», чтобы отправилась команда «Start». Бот не будет нам отвечать, но зато слева внизу будет кнопка, активировав которую, мы сможем открыть наш Telegram Mini App.
Технология |
Плюсы |
Минусы |
React |
Большое сообщество, мощные библиотеки (Redux, React Router), быстрая разработка, поддержка компонентного подхода |
Требует настройки для оптимизации (например, Webpack или Vite), может быть избыточной для простых приложений |
Vue.js |
Легкость освоения, компактный код, высокая производительность, простая интеграция |
Меньше готовых решений и библиотек, специфичных для Telegram Mini Apps |
Angular |
Мощный фреймворк с поддержкой TypeScript, встроенные инструменты для масштабируемых приложений, строгая структура |
Более высокая кривая обучения, избыточная сложность для небольших проектов, больший размер бандла |
Bootstrap |
Быстрое создание адаптивных интерфейсов, множество готовых компонентов, кросс-браузерная совместимость |
Ограниченная кастомизация, может увеличивать размер приложения из-за избыточных стилей |
Telegram Web App — это инновационная технология, позволяющая встраивать в чаты мессенджера мини-приложения, упрощающая доступ к сервисам, таким как онлайн-магазины, игры и аналитика, не покидая приложение, а также обеспечивающая бесшовную синхронизацию данных между устройствами.
Разработка возможна с применением веб-технологий (HTML, CSS, JavaScript) и популярных фреймворков, например, React или Angular, с поддержкой API Telegram для взаимодействия с ботами, что позволяет легко интегрировать сложные функции.
Для улучшения опыта аудитории необходимо персонализировать контент, анализировать поведение с помощью современных инструментов аналитики и добавлять интерактивные элементы, например, мини-игры или опросы, для повышения вовлеченности.
Регулярное обновление, тестирование на реальных устройствах и мониторинг производительности гарантируют стабильность, актуальность и высокий уровень удобства использования, а также помогают быстро адаптироваться к новым требованиям Telegram.
Создание Web Apps в Telegram открывает возможности для монетизации, продвижения бренда и привлечения пользователей. Наш пример игры-кликера демонстрирует, как сделать Web App в Telegram с использованием React и Nest.js, от настройки окружения до запуска в Telegram. Следуя этому руководству, вы сможете создать свой Telegram Mini App и интегрировать его в мессенджер для решения самых разных задач.