How to Create a Telegram Mini App with React
A Telegram Mini App is a web application that opens inside the Telegram messenger. These apps are built with standard web technologies like HTML, CSS, and JavaScript, and they look like regular websites. In this article, we'll create a Mini App using React and deploy it with Hostman App Platform.
Preparation
You'll need Node.js installed to develop a Mini App. You can download it from the official website. We recommend keeping the default installation path to avoid potential issues.
Development
We will create a memory card game as an example. The goal is to match pairs of colors and earn points, with an option to share your score in Telegram chats.
Project Setup
Open the terminal: Start the Windows console or another terminal.
Choose a directory: Navigate to the folder where you want to create the project. For example, to create it on your desktop, enter:
cd Desktop
Create a project: Use the following command to create a new React project:
npx create-react-app memory-game
Here, memory-game is the name of the project folder. You can choose any other name.
Open the project in an editor: I recommend using Visual Studio Code (VS Code), but you can use any other code editor.
Writing the Code
You should execute all commands for installing libraries and running the project inside the project directory. If you encounter issues, make sure you're in the correct directory.
The structure of the created folder looks like this. The necessary files for development will be created in the src folder.
Verify that the React app runs locally by entering:
npm start
This will start the project, and a new browser tab will open.
Install the Telegram SDK:
npm install @telegram-apps/sdk
Initialize the Telegram SDK: To emulate the Telegram Mini App environment during development, you need to handle the fact that the code is not running in a Mini App environment. Open the index.js file and add the following code:
import React from 'react';
import { createRoot } from 'react-dom/client';
import App from './App';
import { initMiniApp, mockTelegramEnv, parseInitData } from '@telegram-apps/sdk';
const initializeTelegramSDK = async () => {
try {
// Attempt to initialize the real Telegram environment
console.log("Initializing Telegram environment");
const [miniApp] = initMiniApp();
await miniApp.ready();
} catch (error) {
// In case of an error, initialize a mock environment
console.error('Error initializing Telegram:', error);
const initDataRaw = new URLSearchParams([
['user', JSON.stringify({
id: 99281932,
first_name: 'Andrew',
last_name: 'Rogue',
username: 'rogue',
language_code: 'en',
is_premium: true,
allows_write_to_pm: true,
})],
['hash', '89d6079ad6762351f38c6dbbc41bb53048019256a9443988af7a48bcad16ba31'],
['auth_date', '1716922846'],
['start_param', 'debug'],
['chat_type', 'sender'],
['chat_instance', '8428209589180549439'],
]).toString();
mockTelegramEnv({
themeParams: {
accentTextColor: '#6ab2f2',
bgColor: '#17212b',
buttonColor: '#5288c1',
buttonTextColor: '#ffffff',
destructiveTextColor: '#ec3942',
headerBgColor: '#fcb69f',
hintColor: '#708499',
linkColor: '#6ab3f3',
secondaryBgColor: '#232e3c',
sectionBgColor: '#17212b',
sectionHeaderTextColor: '#6ab3f3',
subtitleTextColor: '#708499',
textColor: '#f5f5f5',
},
initData: parseInitData(initDataRaw),
initDataRaw,
version: '7.2',
platform: 'tdesktop',
});
console.log('Mock Telegram environment initialized');
}
};
// Initialize SDK
initializeTelegramSDK();
const container = document.getElementById('root');
const root = createRoot(container);
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
Writing the Game Logic
Next, we write the core game logic for our memory card game in App.js. The game will have a limited number of turns (15), and it will track the score, display win/lose screens, and save the score in local storage.
import React, { useReducer, useEffect } from 'react';
import './App.css';
const generateDeck = () => {
const colors = ['#FF6347', '#4682B4', '#32CD32', '#FFD700', '#FF69B4', '#8A2BE2'];
const deck = [];
// Add two cards for each color
for (let color of colors) {
deck.push({ color, matched: false });
deck.push({ color, matched: false });
}
// Shuffle the deck
return deck.sort(() => Math.random() - 0.5);
};
const initialState = {
deck: generateDeck(),
flipped: [],
matched: [],
turns: 0,
score: 0,
pendingReset: false,
gameOver: false,
};
const gameReducer = (state, action) => {
switch (action.type) {
case 'FLIP_CARD':
// Flip the card
if (state.flipped.length < 2 && !state.flipped.includes(action.index) && !state.matched.includes(state.deck[action.index].color)) {
return { ...state, flipped: [...state.flipped, action.index] };
}
return state;
case 'CHECK_MATCH':
// Check if flipped cards match
const [first, second] = state.flipped;
if (state.deck[first].color === state.deck[second].color) {
const newMatched = [...state.matched, state.deck[first].color];
const isGameOver = newMatched.length === state.deck.length / 2;
return {
...state,
matched: newMatched,
score: isGameOver ? state.score + 1 : state.score,
flipped: [],
pendingReset: false,
gameOver: isGameOver,
};
} else {
return { ...state, pendingReset: true };
}
case 'RESET_FLIPPED':
// Reset flipped cards
return { ...state, flipped: [], pendingReset: false };
case 'INCREMENT_TURN':
// Increment the turn counter
return { ...state, turns: state.turns + 1 };
case 'RESET_GAME':
// Reset the game state
return {
...initialState,
deck: generateDeck(),
};
default:
return state;
}
};
const App = () => {
const [state, dispatch] = useReducer(gameReducer, initialState);
// Check for a match when two cards are flipped
useEffect(() => {
if (state.flipped.length === 2) {
dispatch({ type: 'CHECK_MATCH' });
dispatch({ type: 'INCREMENT_TURN' });
}
}, [state.flipped]);
// Timer to reset flipped cards
useEffect(() => {
if (state.pendingReset) {
const timer = setTimeout(() => {
dispatch({ type: 'RESET_FLIPPED' });
}, 1000);
return () => clearTimeout(timer);
}
}, [state.pendingReset]);
// Handle card click event
const handleCardClick = (index) => {
if (!state.gameOver && state.flipped.length < 2 && !state.flipped.includes(index)) {
dispatch({ type: 'FLIP_CARD', index });
}
};
const handlePlayAgain = () => {
dispatch({ type: 'RESET_GAME' });
};
return (
<div className="App">
<h1>Memory Game</h1>
<div className="info">
<p>Score: {state.score}</p>
<p>Attempts: {state.turns}/15</p>
</div>
<div className="deck">
{state.deck.map((card, index) => (
<div
key={index}
className={`card ${state.flipped.includes(index) || state.matched.includes(card.color) ? 'flipped show' : ''}`}
style={{ '--card-color': card.color }}
onClick={() => handleCardClick(index)}
/>
))}
</div>
{state.gameOver && (
<>
<div className="overlay" />
<div className="game-over">
<h2>You won!</h2>
<button onClick={handlePlayAgain}>Play Again</button>
</div>
</>
)}
{!state.gameOver && state.turns >= 15 && (
<>
<div className="overlay" />
<div className="game-over">
<h2>Game Over!</h2>
<button onClick={handlePlayAgain}>Play Again</button>
</div>
</>
)}
</div>
);
};
export default App;
Adding Styles
You must also write CSS styles to make the game look visually appealing. When writing CSS, it's important to understand that elements should preferably be placed in the center or anchored to the edges of the screen. This ensures that when they shift, the app's elements also shift accordingly. After all, a Mini App is essentially a website optimized for phone screens.
In App.css, write the following styles:
body {
font-family: 'Arial', sans-serif;
background-color: #f0f0f0;
display: flex;
justify-content: center;
align-items: center;
background: linear-gradient(to right, #ffecd2, #fcb69f);
}
/* Additional styling here */
When creating a Telegram Mini App, keep in mind that you need to optimize the application for a specific screen size. To control this size, you can use the DevTools in your browser. To open DevTools, press the F12 key. After that, click on the icon that shows a laptop and a phone.
Now the app will appear as a website opened on a phone. You need to adjust the screen dimensions slightly. Enter 350 for the width (first field) and 670 for the height (second field). This size will be the approximate resolution of the Mini App.
Code Improvement
At the moment, the documentation for @telegram-apps/sdk does not include expand. This feature ensures that when you open the Telegram Mini App on a phone, it opens in full screen rather than only taking up half of it.
To add expand, open the index.html file located in the public folder, and add the following code after the opening <head> tag:
<script src="https://telegram.org/js/telegram-web-app.js"></script>
<script>
Telegram.WebApp.expand();
</script>
Here, the connection to the library for creating a Mini App is established directly through a script, and the expand feature from this library is used.
Let’s add a bit of color to the header in the Telegram Mini App. To do this, add the following code after initializing the Mini App in index.js:
miniApp.setHeaderColor('#fcb69f');
Now header looks like this:
Let's also add the ability to share your score in other Telegram chats using the MainButton provided by the SDK. We'll modify index.js.
The import now looks as follows:
import { initMiniApp, initMainButton, mockTelegramEnv, parseInitData, initUtils } from '@telegram-apps/sdk';
And after setting the header color, you need to add the following code to get the score from the game and insert it into a message. This message can then be forwarded to other chats in Telegram.
// Initialization of the main button
const [mainButton] = initMainButton();
mainButton.setParams({
backgroundColor: '#aa1388',
text: 'Share Score',
isVisible: true,
isEnabled: true,
});
mainButton.show();
const utils = initUtils();
// Setting up the click handler for the main button
mainButton.on('click', () => {
try {
// Getting the current score from localStorage
const score = localStorage.getItem('memory-game-score') || 0;
utils.shareURL(`Check it out! I have ${score} points in the game!`);
console.log('Chat selection window opened for sending the message.');
} catch (error) {
console.error('Error opening the chat selection window:', error);
}
});
Deployment
Next, we need to make our Telegram application available to other users. For this, we will upload the code to GitHub and then deploy the application to a server.
Uploading Code to GitHub
Once the Mini App is ready, you need to upload the files from the folder to GitHub. Create a new private repository on GitHub, then return to your project. Using the command line (make sure you are in the project folder), upload all files to your repository.
Enter these commands sequentially:
Initialize a new Git repository in the current directory:
git init
Add all changes (new, modified, and deleted files) in the current directory to the next commit. Enter the command with the dot at the end:
git add .
Create a commit with the message "first commit," recording all changes added with the git add command:
git commit -m "first commit"
Rename the current branch to main and set it as the default branch. The -M flag means "rename":
git branch -M main
Add a remote repository named origin and link it to the specified URL. Provide the link to your newly created repository:
git remote add origin https://github.com/my-account/memory-game
Push changes from the local main branch to the remote main branch on the origin repository. The -u flag sets up a tracking relationship between the local and remote branches so that you can use git push without additional parameters in the future:
git push -u origin main
Deploying the Mini App
After uploading your files to GitHub, you can upload the application to the server. I will be deploying the React Mini App using Hostman's App platform. If you are not yet a Hostman client, register first.
Go to the App platform section and start creating an app. Choose the Frontend type and the React framework.
You will need to link your GitHub profile to upload projects. Next, enter the repository name you specified during creation. In the Region section, choose the one closest to you, where the ping is the lowest.
In the configuration, select the request limit you need. Do not change anything in the application settings.
In the application information section, specify its name and any comments, and select the project to which your application will be linked.
Next, click the Deploy button, which will start the automatic deployment of your React application to the server. After some time, the application will start, and if the deployment is successful, the deployment logs will display "Deployment successfully completed."
When the application starts, you will also get a free domain linked to the application. You can find it in the Settings section of your application.
To visit the domain and check if everything is working, simply copy it and paste it into your browser's address bar.
Preparing a Telegram Bot for Mini App
You need to create and configure a Telegram bot to ensure that the Mini App opens within Telegram. Follow these steps:
Step 1. Creating the Bot
Go to the official BotFather bot and start a chat with it.
Enter the command /newbot. The bot will ask you to provide a name for the bot. After entering the name, the bot will request a username. This username must end with the word bot. For example, if your bot's name is Tetris, the username can be:
TetrisBot
Tetric_bot
Tetrisbot
Tetris_Bot
If successful, the bot will send you a message with your new bot's details and the token. Never share the bot token with people you do not trust.
Step 2. Configuring the Bot:
To configure the bot, enter the command /mybots. The bot will send a list of your created bots. Select the desired bot using the inline buttons. You will see a menu like this:
Clicking on "Edit Bot" allows you to add an avatar, a description in the "About" section (which appears when entering the bot), a photo for the description, and to change the bot’s name. You can skip these actions if the application is still in development.
Adding a Button to Open Telegram Mini App:
Add a button to open the Telegram Mini App. Go to "Bot Settings", then select "Menu Button" and within that, "Configure menu button".
When you click these buttons, the bot will ask you to send a link to your Mini App. Go back to Hostman and copy the URL where your application is hosted.
After sending the URL, the bot will ask you to enter a name for the button.
Step 3. Testing the Bot
Now, you can go to the bot and press the "Start" button to send the "Start" command. The bot won’t respond as there is no code written for responses yet, but what you need is the button in the lower left corner. When clicked, it will open the Telegram Mini App.
Step 4. Adding Screenshots
You can add screenshots of your Telegram application to your bot's description. Currently, this feature is available only on mobile devices and only after some time has passed since creating the bot (and only if your Mini App is actively used).
Analytics and Monetization
You’ve created a Telegram Mini App and want to showcase it to others and launch it globally. But what if you want to collect statistics from your Mini App?
In addition to analytics, if your Telegram Mini App is a game, you might want to add monetization so that people watch ads and get something in return, while you earn revenue.
Analytics
Recently, a service specializing in collecting analytics for Telegram Mini Apps has been introduced. It’s called Telemetree. With it, you can gather various information:
Number of Telegram Mini App users:
Total number
New users
Returning users
User device
Event count: How many times and which buttons were clicked
Highest activity
Day of the week when the Telegram Mini App is used most frequently
Most active time
Session duration
This service is currently being developed so we may expect new tracking options for different events in the future.
To start collecting data, you need to register on the project’s website. After registration, you will receive an Application ID and Project ID. These keys should not be shared with anyone.
Now, you need to install their library using the command:
npm install @tonsolutions/telemetree-react --save
After installation, open the index.js file and add the import:
import { TwaAnalyticsProvider } from '@tonsolutions/telemetree-react';
Then update the render method to start collecting analytics after rendering your application. Replace projectId and apiKey with the values provided during registration, and enter your product name in appName:
root.render(
<React.StrictMode>
<TwaAnalyticsProvider
projectId='YOUR_PROJECT_ID'
apiKey='YOUR_API_KEY'
appName='YOUR_APPLICATION_NAME'
>
<App />
</TwaAnalyticsProvider>
</React.StrictMode>
);
After completing these steps:
Upload the changes to the server using Git.
When the data updates and a new build appears in Hostman, launch your Telegram Mini App and click on a few buttons to start collecting analytics.
Return to the website, refresh the page, and then click the "Proceed" button.
Monetization
To add monetization to your Telegram Mini App, you need to use Adsgram. It provides rewards for watching ads in Ton.
Go to the Adsgram website and create an app owner profile by clicking the "For app owner" button.
After registration, click "Create…" in the upper left corner and select "platform". In the window that opens, enter the application name, the Telegram Mini App link, and the website where the application is hosted.
To get the link to the Telegram Mini App, go back to BotFather and enter the command /newapp. A list of your bots will appear. Select the bot linked to the Telegram Mini App link. The bot will ask you to enter the application name, a brief description, and to send a photo. You can also send a GIF for the application, but you can skip this step by entering the command /empty. The bot will then ask for the website link where your application is hosted. Return to Hostman and copy the link again. Finally, the bot will ask you to send a short name for the Telegram Mini App.
Go back to the Adsgram website and enter this direct link to the Mini App in the "Telegram Direct Link" field.
After creating the platform, click "Add ad block" in the upper right corner to create an ad block. Enter a name for the block. For "Minimum cost per mine," it is advisable not to change the value from "Do not fill in" to "Fill in." You can also choose the type of ad: it will be either a video or a promotional post.
Once you’ve created the ad block, open the index.js file and add the following script in the head tag:
<script src="https://sad.adsgram.ai/js/sad.min.js"></script>
Create a new file named useAdsgram.js and place the following code in it:
import { useCallback, useEffect, useRef } from 'react';
export function useAdsgram({ blockId, onReward, onError }) {
const AdControllerRef = useRef(undefined);
useEffect(() => {
AdControllerRef.current = window.Adsgram?.init({ blockId });
}, [blockId]);
return useCallback(async () => {
if (AdControllerRef.current) {
AdControllerRef.current
.show()
.then(() => {
onReward();
})
.catch((result) => {
onError?.(result);
});
} else {
onError?.({
error: true,
done: false,
state: 'load',
description: 'Adsgram script not loaded',
});
}
}, [onError, onReward]);
}
Then open app.js, where the game logic is located. Add a new import and modify the game logic to include two buttons on the game-over menu: one to restart the game and another to earn additional turns by watching an ad. You can now start a new game or watch an ad to receive 5 extra turns.
Add the new import for the newly created hook:
import { useAdsgram } from './useAdsgram'; // Import the created hook
Then, at the end of gameReducer, add:
case 'ADD_EXTRA_TURNS':
return {
...state,
turns: state.turns - 5,
};
In the game components, in const App, add:
const showAd = useAdsgram({
blockId: 'YOUR_BLOCK_ID',
onReward: () => dispatch({ type: 'ADD_EXTRA_TURNS' }),
onError: (error) => console.error('Error showing ad:', error),
});
Replace blockId with your block ID, which can be obtained by clicking "Show code" in the ad block menu.
Add a new button to the game-over menu:
<div className="game-over"> <button onClick={showAd}>+5 Turns</button></div>
Now, after losing the game, in addition to the "Play Again" button, you will also have a "+5 Turns" button, which will show an ad and grant the user 5 extra turns.
Moderation
According to the new Adsgram platform rules, to show ads in your Telegram Mini App, you need to pass moderation by contacting support via Telegram.
To pass moderation, you must:
Attach a screenshot from BotFather showing the Direct Link and Web App URL.
Provide a link to your platform in the format https://partner.adsgram.ai/platforms/xxx/.
Platforms that will not pass moderation include:
Games where every click leads to an ad.
Games where ads are shown immediately after a loss.
Cheap game clones created to increase ad views.
Services for inflating statistics and ads.
Services where you need to watch ads to perform certain actions.
Conclusion
Creating a Telegram Mini App using React allows developers to build interactive and user-friendly applications integrated into the Telegram ecosystem. This guide presented a step-by-step process for creating a simple memory card game, including setting up the environment, writing the core code, styling the app, and integrating analytics and monetization.
By applying this knowledge, you can create engaging and useful Mini Apps and effectively utilize Telegram's resources for distribution and monetization. The analytics and ad integration options discussed will aid in further developing and improving your Telegram applications.
24 September 2024 · 19 min to read