<div><img src="https://top-fwz1.mail.ru/counter?id=3548135;js=na" style="position:absolute;left:-9999px;" alt="Top.Mail.Ru" /></div>
Публичное облако на базе VMware с управлением через vCloud Director
Вход / Регистрация

Telegram Web App: полная инструкция по созданию мини-приложение в Телеграм

Вадим Андоськин
Вадим Андоськин
Технический писатель
11 июня 2025 г.
106
35 минут чтения
Средний рейтинг статьи: 5

Telegram Web App (или Telegram Mini Apps) — это веб-приложения, которые функционируют внутри мессенджера Telegram, используя стандартные веб-технологии, такие как HTML, CSS и JavaScript. Они представлены в виде веб-страниц, которые интегрированы с Telegram. Это позволяет пользователям взаимодействовать с ботами, синхронизировать данные и получать удобный доступ к различным сервисам.

Мини-приложения в Telegram делают взаимодействие с цифровыми сервисами проще и добавляют новые возможности мессенджеру. Они могут использоваться для решения различных задач: от покупок и бронирований до игр и розыгрышей. Например, Telegram Web Apps позволяют запускать онлайн-магазины, игровые платформы или сервисы аналитики прямо в чатах.

Идея Telegram Mini Apps зародилась, когда Павел Дуров представил концепцию веб-приложений, работающих внутри Telegram. Изначально называвшиеся WebApps, они стали частью стратегии по превращению Telegram в универсальную платформу. Это позволило разработчикам использовать знакомые веб-технологии для создания сервисов без необходимости разрабатывать отдельные мобильные или десктопные приложения.

cloud

Возможности Telegram Web App

Telegram Web Apps предоставляют множество преимуществ, и для тех, кто использует продукт, и для тех, кто его создает.

Для пользователей

  • Легкость доступа: Запуск Web Apps осуществляется прямо из Telegram, без установки дополнительных программ.
  • Интерактивность: От простых форм до сложных игр — Web Apps обеспечивают разнообразные функции.
  • Удобство: Интерфейс интегрирован в Telegram, что делает его интуитивно понятным.
  • Синхронизация данных: Данные и настройки автоматически синхронизируются между устройствами.

Для разработчиков

  • Кроссплатформенность: Web Apps работают на любых устройствах и операционных системах, поддерживающих Telegram.
  • Быстрые обновления: Любые изменения, которые происходят на сервере, сразу же отображаются в приложении.
  • Простота разработки: Применение типовых веб-решений позволяет оптимизировать временные и материальные затраты.
  • Легкое распространение: Нет необходимости публиковать приложение в магазинах, что ускоряет доступ пользователей.

Архитектура Telegram Web App

Чтобы понять, как создать Telegram Web App в Телеграм, важно разобраться в их архитектуре.

Компоненты архитектуры

  1. Клиентская часть

    • HTML, CSS, JavaScript: Базовые технологии для разработки интерфейса и пользовательского взаимодействия. Web Apps выглядят как обычные веб-страницы и могут включать функциональность, поддерживаемую этими технологиями.

    • WebView: Web Apps загружаются и отображаются в WebView внутри Telegram. Это позволяет им работать на любых устройствах и операционных системах, поддерживающих Telegram.

  1. Серверная часть

    • Серверные технологии: Web Apps могут взаимодействовать с собственными серверными API, написанными на любом языке программирования (например, Node.js, Python, Ruby). Серверная часть отвечает за обработку данных, взаимодействие с базами данных и другими внешними сервисами.

    • Telegram Bot API: Для интеграции с Telegram Web App могут использовать Telegram Bot API. Это позволяет им взаимодействовать с пользователями через ботов, отправлять и получать сообщения, а также выполнять другие действия.

  1. Взаимодействие с Telegram

    • SDK: Специальные SDK, предоставляемые Telegram или сообществом, включающие в себя методы и инструменты для упрощения разработки и интеграции Web App с Telegram.

  1. Обновления и деплой

    • Обновления на сервере: Для обновления Web App необходимо лишь отправить изменения на сервер. Пользователи сразу же получат доступ к обновленной версии при следующем запуске приложения.

    • Контроль версий: Разработчики могут применять системы контроля версий для управления изменениями в коде и обеспечения стабильности приложения.

Как работают Web Apps

  1. Инициализация: Пользователь нажимает кнопку в Telegram, чтобы запустить Web App.
  2. Загрузка: Telegram загружает приложение через WebView с указанного URL.
  3. Взаимодействие: Пользователь работает с интерфейсом, который может включать формы, игры или другие элементы.
  4. Синхронизация: Данные сохраняются и синхронизируются между устройствами.
  5. Завершение: Пользователь закрывает Web App и возвращается к Telegram.

Image5

Изображение: press-release.ru

Примеры использования Telegram Web Apps

Web App в Телеграм находят применение в самых разных областях. Вот несколько примеров:

  • Онлайн-магазины: Бот @DurgerKingBot позволяет добавлять товары в корзину и оплачивать их прямо в Telegram. Это пример, созданный разработчиками Telegram для демонстрации возможностей Web Apps.

  • Розыгрыши: Сервисы вроде @BlessMeBot или @GiveShareBot используют Web Apps для отображения условий розыгрышей в каналах и чатах.

  • Игры: Платформа @Gamee предлагает коллекцию HTML5-игр, где пользователи могут соревноваться с друзьями.

Для вдохновения можно изучить примеры Telegram Web Apps, доступные в Telegram-каналах или в документации.

Зачем создавать свои Web Apps

Создание Mini Apps открывает множество возможностей:

  • Современность: Telegram Web Apps — это новая технология, привлекающая внимание пользователей.

  • Узнаваемость бренда: Уникальные Web Apps помогают продвигать бренд и повышать его популярность.

  • Монетизация: Встроенные покупки, реклама через AdsGram или подписки позволяют зарабатывать. Подробнее о том, как монетизировать свое Telegram-приложение, мы рассказывали, например, в этой статье.

Как создать Web Apps в Telegram: пошаговое руководство

Теперь перейдем к практической части и разработаем Telegram Web App на примере игры-кликера с лидербордом, применяя React для фронтенда и Nest.js для бэкенда.

Шаг 1: Подготовка окружения

Прежде чем начать разработку, необходимо проверить, что на вашем компьютере уже есть Node.js. Если нет, то его необходимо скачать с официального сайта.

Также должен быть глобально установлен Nest.js. Чтобы установить Nest.js, нужно запустить командную строку Windows или другую консоль и ввести команду: 

npm i -g @nestjs/cli

Теперь можно приступать к разработке проекта.

Windows

  1. Создайте папку на рабочем столе

  2. Запустите командную строку Windows или другой терминал.

  3. Перейдите в созданную вами папку. Например, если вы создали папку на рабочем столе, то введите команду:

cd Desktop
  1. Затем введите еще раз команду cd, но уже с названием созданной вами папки:

cd mini-app-clicker
  1. Чтобы создать Frontend-часть, выполните команду:

npm create vite@latest frontend

frontend в данной команде обозначает название папки. Вы можете дать любое название своей папке, которая будет использоваться для Frontend-части.

  1. После выполнения команды вам будет предложено выбрать фреймворк. С помощью стрелок выберите React.

  2. Затем нужно выбрать вариант «JavaScript + SWC».

  1. В этой же консоли введите команду для создания Backend-части:

nest new backend

backend в данной команде обозначает название папки. Вы можете дать любое название своей папке, которая будет использоваться для Backend-части.

  1. В процессе создания вам будет предложено выбрать менеджер пакетов. С помощью стрелок выберите npm.

  1. Откройте главную папку вашего проекта (в моем случае это mini-app-clicker) в любом удобном для вас редакторе кода. Я рекомендую использовать Visual Studio Code (VS Code).

Linux

  1. Создайте папку на рабочем столе. Это можно сделать с помощью следующей команды в терминале:

mkdir ~/Desktop/mini-app-clicker
  1. Перейдите в созданную вами папку. Допустим, вы создали папку на рабочем столе, тогда выполните следующее команду:

cd ~/Desktop/mini-app-clicker
  1. Чтобы создать Frontend-часть, выполните следующую команду:

npm create vite@latest frontend

frontend в данном случае обозначает название папки. Свою папку для Frontend-части вы можете назвать как угодно.

  1. После выполнения команды вам будет предложено выбрать фреймворк. С помощью стрелок выберите React. Затем выберите вариант JavaScript + SWC.

  1. В этой же консоли введите команду для создания Backend-части:

nest new backend

backend в данном случае обозначает название папки. Свою папку для Backend-части вы можете назвать как угодно.

  1. При создании у вас попросят выбрать менеджер пакетов. С помощью стрелок выберите npm.

  2. Откройте главную папку вашего проекта (в данном случае mini-app-clicker) в любом удобном для вас редакторе кода. Я рекомендую использовать Visual Studio Code (VS Code).

В редакторе кода главная папка будет иметь следующий вид:

Image30

При разработке мы будем неоднократно использовать консоль для той или иной цели, поэтому всегда удостоверяйтесь, что вы находитесь в нужной директории проекта перед вводом той или иной команды.

Шаг 2: Создание Frontend-части

В первую очередь создадим Frontend-часть нашего Mini App. В ней расположим кнопку, на которую мы будем нажимать, прибавление очков, отображение First Name пользователя, а также добавим вкладку с лидербордом.

Перед созданием Frontend-части необходимо выполнить установить зависимости, которые необходимы для запуска нашего визуала. 

Откройте консоль и введите команду:

cd frontend

Укажите в ней свое название папки для Frontend’а.

Затем введите:

npm install

После выполнения этой команды появится новая папка под названием node_modules, в которой  будут все нужные файлы, чтобы запустить проект.

Image4

Попробуйте запустить пустой проект с помощью команды:

npm run dev

В случае успеха вам предложат открыть проект по ссылке http://localhost:5173/

Вы увидите стандартную страницу только что созданного проекта:

Image25

Откроем редактор кода повторно. Все необходимые для проекта файлы мы будем создавать в папке 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.

Image10

Изменим 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;
}

После всех этих изменений у нас будет готова логика кликера, а также заготовка на будущий лидерборд:

Image26

Чтобы в браузере ваша страница выглядела в виде телефона, необходимо нажать на F12, а затем нажать на кнопку переключения девайса.

Image11

Затем нужно настроить параметры экрана так, чтобы они соответствовали параметрам Mini App. В поле ширина (первая строка) введите значение 360, а в поле высота (вторая строка) — 800. Эти значения будут примерным разрешением для Mini App.

Image14

Шаг 3: Создание Backend-части

Теперь приступим к написанию 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

Image6

Все необходимые материалы для разработки проекта будем создавать и изменять в папке 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

В первый раз запуск может занять много времени, но если всё пройдет хорошо, то в консоли вы увидите примерно следующее:

Image3

Шаг 4: Тестирование запросов

Теперь необходимо протестировать все запросы, чтобы мы точно знали, что наш Backend работает правильно. Лучше всего для тестирования запросов подойдет приложение Postman.

  1. Откройте Postman на своем ПК.

  2. Создайте новый запрос для создания пользователя, нажав на «+» в верхнем меню.

  3. Рядом с полем для ввода URL нажмите на метод запроса и в открывшемся списке выберите POST-запрос, после чего в поле для ввода URL введите:

http://localhost:3000/users/create
  1. Перейдите во вкладку Body, которая находится под полем для ввода URL. В ней выберите raw, а справа нажмите на Text и выберите метод JSON. В текстовое поле введите:

{
  "id": "123456",
  "firstname": "Test"
}

Тут мы указываем, какие параметры мы передаем в теле запроса, чтобы создать нового пользователя.

  1. Перейдите во вкладку Headers. В поле Key необходимо ввести Content-Type, а в Valueapplication/json.

  2. Отправляем наш запрос, нажав на кнопку Send. Если всё успешно, то внизу появится ответ от нашего запроса с ID, firstname и points.

Image19

А также справа будет указан код ответа 201.

Image16

  1. Создайте новый запрос для обновления очков пользователя, нажав на «+» верхнем меню.

  2. Выберите метод POST-запрос, после чего в поле для ввода URL введите:

http://localhost:3000/users/update
  1. Перейдите во вкладку Body. В ней выберите raw и метод JSON. В текстовое поле введите:

{
  "id": "123456",
  "points": 10
}
  1. Перейдите во вкладку Headers. В поле Key необходимо ввести Content-Type, а в Value application/json.

  2. Отправляем наш запрос, нажав на кнопку Send. Если всё успешно, то вы получите ответ с обновленными очками:

Image13

  1. Создайте новый запрос для получения данных пользователя, нажав на «+» верхнем меню.

  2. Выберите метод GET, после чего в поле для ввода URL введите:

http://localhost:3000/users/123456

Будьте внимательны при отправке этого запроса, ведь после users/ необходимо указать ID человека, который уже создан, иначе вам вернет ошибку 404.

  1. Отправляем наш запрос, нажав на кнопку Send. Если всё успешно, то вы получите firstname и points человека:

Image29

Также справа будет указан код ответа 200:

Image28

  1. Создайте новый запрос для получения лидерборда, нажав на «+» верхнем меню.

  2. Выберите метод GET, после чего в поле для ввода URL введите:

http://localhost:3000/users/leaderboard
  1. Отправляем наш запрос, нажав на кнопку Send. Если всё успешно, то вы получите список людей.

Image7

Попробуйте создать еще несколько людей и выдать им очки. После чего снова отправьте запрос на лидерборд и вы увидите, как меняется список от больших очков к меньшим.

Благодаря данным из Postman в процессе тестирования Backend’а мы можем отследить время выполнения запросов и их вес.

  • Response Time отображает, сколько времени занимает тот или иной процесс. Мы можем проанализировать эти данные и при необходимости оптимизировать запросы, сделав их быстрее.

Image12

  • Response Size и Request Size позволяют можно узнать вес запросов, что также позволит оптимизировать их при необходимости.

Image9

Шаг 5: Объединение Frontend и Backend

Теперь нужно объединить написанные нами 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 на сервере, чтобы знать, куда отправлять запросы.

Шаг 6: Загрузка на GitHub

Создайте два закрытых репозитория на GitHub для Frontend’а и Backend’а, а затем вернитесь к вашему проекту. Далее нужно проделать следующие действия для обеих частей Mini App:

  1. Перейдите в нужную директорию

  2. Создайте новый репозиторий Git:

git init
  1. Добавьте все изменения к коммиту:

git add . 
  1. Создайте коммит:

git commit -m "first commit"
  1. Измените название текущей ветви и установите ее как основную:

git branch -M main
  1. Добавьте удаленный репозиторий и свяжите его с указанным URL. Вместо ссылки на мой репозиторий, предоставьте ссылку на ваш новый репозиторий.

git remote add origin https://github.com/an-vadim-an/mini-app-clicker-backend
  1. Отправьте изменения на репозитории:

git push -u origin main

Шаг 7: Загрузка на сервер и завершение настройки Mini App

Когда файлы будут загружены на GitHub, можно приступить к запуску Mini App на сервере. Я буду использовать сервис Apps от Timeweb Cloud. Если вы еще не зарегистрированы на Timeweb Cloud, то для начала необходимо пройти регистрацию.

Прежде всего, необходимо создать новый проект. Дайте ему наименование, а при необходимости — добавьте описание и изображение.

Image27

После того как проект был создан, необходимо создать Apps. Прежде всего мы запустим наш Backend. В процессе создания нужно выбрать тип Backend и платформу Nest.js.

Image15

Для загрузки проектов необходимо привязать учетную запись на GitHub. После завершения процесса привязки введите имя репозитория, которое вы использовали при создании. В разделе «Регион» выберите тот, который находится ближе к вам и имеет наименьший пинг.

Image20

Выберите минимальную конфигурацию — ее достаточно для нашей задачи. В настройках приложения не нужно вносить изменения.   

Image24

В описании приложения необходимо указать его наименование, при необходимости — комментарий, а также выберите проект, с которым будет связано ваше приложение.

Image23

Затем можно активировать процесс развертывания Nest.js на сервере, нажав на кнопку «Запустить деплой». 

Через некоторое время приложение будет запущено, и в случае успешного развертывания проекта в журнале деплоя появится сообщение «Deployment successfully completed». Еще до полного запуска нам будет доступен домен, который уже можно скопировать. Расположен он будет на Дашборде, в окошке справа:

Image2

В моем случае я буду отправлять запросы на адрес:

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.

Image22

А далее как с Backend. Выбираете нужный репозиторий, регион, конфигурацию. В настройках приложения ничего не меняете и вносите нужную информацию о приложении, не забыв выбрать проект.

Когда Frontend начнет запускаться, вы получите бесплатный домен. Найти его можно в разделе «Настройки» вашего приложения. Его необходимо скопировать.

Image21

Теперь необходимо открыть файл 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.

Шаг 8: Настройка Telegram-бота для Web App

Пока Frontend и Backend запускаются, необходимо создать и настроить нашего бота, чтобы Mini App открывался внутри Telegram. 

Перейдите в BotFather и запустите его. Выполните команду /newbot. Бот предложит вам ввести имя для нового бота, а затем его username. Если вы попытаетесь использовать уже занятый username, бот сообщит о невозможности его использования и предложит вам выбрать другой.

При успешном исходе событий бот ответит следующим образом:

Image8

Теперь вы можете настроить созданного бота. Для этого используйте команду /mybots. Бот отобразит список всех созданных ботов. Выберите нужный с помощью инлайн-кнопок. Откроется следующее меню:

Image1

Перейдите в «Bot Settings» → «Menu Button» и нажмите на «Configure menu button». BotFather попросит прислать URL, который будет открываться при нажатии на специальную кнопку. Отправьте ему вашу ссылку на Frontend. После отправки он попросит прислать название для этой кнопки. Если вы сделали всё верно, то бот ответит успехом.

Image17

Далее нажмите на «Back to Bot» и повторно зайдите в настройки. Теперь необходимо нажать на «Configure Mini App» и в открывшемся меню нажать на инлайн-кнопку «Enable Mini App». Бот снова попросит ссылку.

Image18

Снова нажмите на «Back to Bot», зайдите в настройки и опять же кликните по «Configure Mini App». В открывшемся меню нажмите на «Change Mod». Внутри выберите Fullscreen. Благодаря этому приложение будет открываться на весь экран и не будет иметь рамок.

Активировируйте бота, нажав на кнопку «Запустить», чтобы отправилась команда «Start». Бот не будет нам отвечать, но зато слева внизу будет кнопка, активировав которую, мы сможем открыть наш Telegram Mini App.

Сравнение технологий для разработки 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 и интегрировать его в мессенджер для решения самых разных задач.

Хотите внести свой вклад?
Участвуйте в нашей контент-программе за
вознаграждение или запросите нужную вам инструкцию
img-server
11 июня 2025 г.
106
35 минут чтения
Средний рейтинг статьи: 5
Пока нет комментариев