DEV Community

Max Bantsevich for dev.family

Posted on

Telegram mini-app development and testing specifics: from initialisation to launch

We receive more and more requests for Telegram mini apps development. Why is this happening? Telegram continues to grow actively, and companies want to reach this audience by offering convenient solutions within the messenger, where their customers spend a significant part of their time.
The dev.family team has already talked about these and other benefits for business in one of our articles.

From a technical point of view, mini apps also have their advantages: they allow full-fledged applications to be launched directly in Telegram, without the need to develop separate native mobile versions. This significantly reduces development and support costs and speeds up the product's time to market.

In this article, I will share my own experience of developing Telegram mini apps and answer the following questions:

  • What are Telegram mini apps?
  • How are they different from other platforms?
  • What are the development prospects for Telegram mini apps?
  • How to properly test Telegram mini apps, taking into account the peculiarities of the platform?
  • How to set up the environment for a future application and initialise the Telegram apps SDK?
  • What are the nuances of Telegram mini apps development?

What are Telegram mini apps and why are they so popular

Today, Telegram mini apps allow us to open any website from within the messenger. The technology is implemented with the help of a special WebView browser shell – a system component that is responsible for integrating web pages into other applications and adapting to the device's operating system.

A Telegram mini app can be any application that can be written in JavaScript or any tool that can be compiled into it. For example, React, Next.js, Angular or even React Native for Web.

The product can either be a standalone solution or part of a business ecosystem. We have already told you in our Telegram mini app tutorial how to integrate a ready-made React Native application into Telegram using the react-native-web package.

Development prospects for Telegram mini apps
The number of Telegram users is gradually approaching one billion. Imagine giving them access to a cross-browser application that runs on any operating system, be it iOS, Android, Windows, macOS or Linux. And it doesn't require any mobile development. Now you can add the mini app to the desktop of your device and open it full screen as a standalone application. And it seems we are only at the beginning of its development path.

The functionality of Telegram mini apps and the possibility of integrating additional services and tools is quite wide: we can use accelerometer, gyroscope, biometrics, have access to geolocation, receive push notifications and much more.

Of course, many things can be done using the standard browser API. But Telegram simplifies user interaction, permissions, payments, data exchange and bot management by providing all the necessary tools. And thanks to Telegram's built-in security, mini apps can be more secure than traditional web apps.

I am sure that in the future Telegram will develop the mini apps format towards mobile development. There will be more and more opportunities to access the hardware features of devices, such as Bluetooth, NFC, Wi-Fi, flashlight, contacts, calendar and so on.

Working with Telegram mini apps is also very convenient for the user: if the messenger is already installed on the device with a connected account, there is no need to download anything additional. All the functionalities are already available in Telegram.

Businesses, in turn, get a convenient mobile app without having to resort to mobile development, which is usually more expensive than web development. Once we write an app competently, we can run it in the browser, mobile and desktop versions of Telegram, getting more opportunities and conversions for less cost.

How to properly test and debug the Telegram mini app

Let's go ahead and start with the peculiarities of testing Telegram mini apps, because we'll talk about them later in the development section. So it is important to set the context. The main difficulty is the inability to launch the project as easily as in a browser via localhost, and the lack of a developer console. However, these problems are easily solved.

As we have already seen, the Telegram mini app is a website that, when linked to, will run in the Telegram environment. So we need to pass a link that displays the development environment. This is done using port forwarding.

There are two ways of doing this.

In the first case, we use the third-party utility ngrok. But it only has a monthly limit on the number of connections, so this option is not very suitable for development.

In the second case we can use VSCode, where we need to open our project and run it as localhost. Next, open the terminal, go to the 'Ports' tab, type in the port number and you will get a link to the redirected port, which can now be used to access our localhost. We will use this link as the link to the mini app in the chatbot.
It looks like this: https://d0etr6gh-3000.euw.devtunnels.ms.

A redirected port can be created as follows:

  • Private – no one but you will be able to access it;
  • Public – the port will be available to everyone if we want to share the link to the application with others.

Image description

So we can launch our Telegram mini app and follow the development process in real time, because Hot Reload works just like in a browser.

But how do you debug an application without access to the developer console? To do this, let's learn about Eruda.

Eruda is a mobile development console that can be launched in any environment. All you need to do is click on the button that is embedded in your application after initialising this library.
This is what it looks like:

Image description

Image description

Image description

Image description

Below we'll go through how to create a mini app in Telegram from scratch and set up an initial template to initialise the telegram-apps/sdk-react package and the app itself. We will see how easy it is to integrate the Eruda package into our project.

You can also read the documentation and connect the package in a different way. Either way, it will only take a few lines of string.
In addition, I would like to mention one more thing that relates to testing.

If you try to open the app in a browser, you will see the following error:
“Unable to retrieve launch parameters from any known source. Perhaps, you have opened your app outside Telegram?”

Image description

The message says that the mini app cannot be launched outside the Telegram environment.

Let's move from theory to practice.

However, we can configure our application to open in a browser. We will not be able to get real data from Telegram, but we will add mock data that will mimic the environment we need.

Development of Telegram mini app from scratch

We have already described the process of creating a chatbot in Telegram using BotFather in this article.

So let's move on to the code itself.

The next step is to initialise the mini app project. In general, the Telegram developers have gone to the trouble of preparing a selection of ready-made template that you can use depending on the technology. For example, here are variants for Next.js and React.js.

✍ Does it make sense to use Next.js? Unfortunately, mini apps are not currently able to fully adapt to SSR, ISR and other rendering methods. Communication with Telegram happens through the use of a window object, which is impossible to get on the server side.

So if your application involves user identification or a chatbot, you will not be able to do this on the server and queries will have to be executed on the client. However, this does not change the fact that Next.js has additional benefits, features and optimisations over React and can still be used for mini app development.

Let's go back to the code. We took one of the ready-made templates and adapted it for our purposes, removing anything unnecessary and adding additional logic. To work with Telegram mini apps, we used the @telegram-apps/sdk-react package. Let's talk about it in a bit more detail.

We have a provider that is used in the root barkout to initialise this package and wrap the whole app.

It looks something like this:

<html lang={locale} className={`${GeologicaFont.variable}`}>
<body>
         <TelegramSDKInitProvider>
           <AppInitProvider>
               <div className={styles.layout}>{children}</div>
           </AppInitProvider>
         </TelegramSDKInitProvider>
     </body>
   </html>
Enter fullscreen mode Exit fullscreen mode

What happens here and why do we need two providers?

First, all the logic is executed inside TelegramSDKInitProvider: the package is initialised so that we can get the user data and use the Telegram API in our application. We check the environment and mock the data if we are in development mode. This is to imitate the Telegram environment to run the application in the browser.

If we are in a production environment, it makes more sense to show an error or inform the user that the app needs to run inside Telegram. And ideally, add a link and QR code to the interface to go directly to the messenger.

'use client'

import { type PropsWithChildren } from 'react'

import { useClientOnce } from '@/src/hooks/useClientOnce'
import { useTelegramMock } from '@/src/hooks/useTelegramMock'

import { telegramSDKInit } from './init'

export function TelegramSDKInitProvider({ children }: PropsWithChildren) {
 if (process.env.NODE_ENV === 'dev') {
   useTelegramMock()
 }

 useClientOnce(() => {
   telegramSDKInit(false)
 })

 return children
}
Enter fullscreen mode Exit fullscreen mode

Inside the useTelegramMock hook we use a check provided by the package itself – isTMA. This allows us to determine if we are in the Telegram environment. If we are not, we mock the data, if we are, we exit the function.

import { isTMA, mockTelegramEnv, parseInitData } from '@telegram-apps/sdk-react'

import { useClientOnce } from './useClientOnce'

export function useTelegramMock(): void {
 useClientOnce(() => {
   let shouldMock: boolean
   const MOCK_KEY = '____mocked'

   if (isTMA('simple')) {
     shouldMock = !!sessionStorage.getItem(MOCK_KEY)
   } else {
     shouldMock = true
   }

   if (!shouldMock) {
     return
   }

   const initDataRaw = new URLSearchParams([
     [
       'user',
       JSON.stringify({
         id: 99281932,
         first_name: 'Arthur',
         last_name: 'Mocked',
         username: 'arthurmocked',
         language_code: 'en',
         is_premium: true,
         allows_write_to_pm: true,
         photoUrl:
           'https://t.me/i/userpic/320/haNXSpRmeucJyo-oXFbNrifxZ-Au0PjvhpJ2l6h4ozcTbTT8yvNKIZABSCLpjtIp.svg',
       }),
     ],
     ['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: '#17212b',
       hintColor: '#708499',
       linkColor: '#6ab3f3',
       secondaryBgColor: '#232e3c',
       sectionBgColor: '#17212b',
       sectionHeaderTextColor: '#6ab3f3',
       subtitleTextColor: '#708499',
       textColor: '#f5f5f5',
     },
     initData: parseInitData(initDataRaw),
     initDataRaw,
     version: '8',
     platform: 'tdesktop',
   })
   sessionStorage.setItem(MOCK_KEY, '1')

   console.info(
     '⚠️ As long as the current environment was not considered as the Telegram-based one, it was mocked. Take a note, that you should not do it in production and current behavior is only specific to the development process. Environment mocking is also applied only in development mode. So, after building the application, you will not see this behavior and related warning, leading to crashing the application outside Telegram.',
   )
 })
}
Enter fullscreen mode Exit fullscreen mode

We can use our own data. For example, from your Telegram account.
Now we can run our app both in Telegram and in any browser.

Next, we run telegramSDKInit, which initialises the SDK package and performs the initial configuration of the application. For example, configuring the full screen mode or the behaviour of closing the app with vertical swipes.

This is also where we initialise the Eruda mobile console that I mentioned in the testing section. The initialisation happens only once – when the app is launched, which is controlled by the useClientOnce hook.

import {
 $debug,
 backButton,
 expandViewport,
 init as initSDK,
 initData,
 miniApp,
 swipeBehavior,
 themeParams,
 viewport,
} from '@telegram-apps/sdk-react'

export function telegramSDKInit(debug: boolean): void {
 $debug.set(debug)

 initSDK()
 expandViewport()

 if (backButton.isSupported()) {
   backButton.mount()
 }

 miniApp.mount()
 themeParams.mount()
 swipeBehavior.mount()
 initData.restore()

 void viewport
   .mount()
   .then(() => {
     viewport.bindCssVars()
     miniApp.bindCssVars()
     themeParams.bindCssVars()
     viewport.requestFullscreen()
     swipeBehavior.disableVertical()
   })
   .catch((e: unknown) => {
     console.error('Something went wrong mounting the viewport', e)
   })

 debug && import('eruda').then((lib) => lib.default.init()).catch(console.error)
}
Enter fullscreen mode Exit fullscreen mode

Then the AppInitProvider logic is executed, where we show the download status instead of the content, as our app is not ready to use yet. Here we already have access to the Telegram API, so we get the data we need (e.g. user data). Next, we will either record the user ID in cookies or perform an authorisation request, passing the necessary data from Telegram.

Once all the manipulations are done, we can display the rest of the application.

An AppInitProvider could look like this:

'use client'

import { initData, useSignal } from '@telegram-apps/sdk-react'
import { setCookie } from 'cookies-next'
import type { PropsWithChildren } from 'react'
import { useCallback, useEffect, useState } from 'react'

import { postAuthLogin } from '@/src/api/schema'
import TelegramMiniAppLoader from '@/src/components/ui/TelegramMiniAppLoader'
import Title from '@/src/components/ui/Title'
import { AUTH_TOKEN, TELEGRAM_USER_ID } from '@/src/constants'

const AppInitProvider = ({ children }: PropsWithChildren) => {
 const [isAppReady, setIsAppReady] = useState(false)
 const [error, setError] = useState(null)

 const initDataState = useSignal(initData.state)
 const initDataStateRaw = useSignal(initData.raw)

 const userId = initDataState?.user?.id

 const logError = useCallback((message: string) => {
   console.error(message)
   setError(message)
 }, [])

 const authenticateUser = useCallback(
   async (rawData: string) => {
     try {
       const { data } = await postAuthLogin({ init_data: rawData })

       if (data) {
         setCookie(AUTH_TOKEN, data.token)
         setIsAppReady(true)
       } else {
         logError(`Auth token is missing. Response: ${JSON.stringify(data)}`)
       }
     } catch (err) {
       logError(
         `Authentication failed: ${err instanceof Error ? err.message : JSON.stringify(err, null, 2)}`,
       )
     }
   },
   [logError],
 )

 const initApp = useCallback(() => {
   if (!userId) {
     logError(`Telegram user ID is absent. User ID: ${userId}`)
     return
   }

   setCookie(TELEGRAM_USER_ID, userId)

   if (!initDataStateRaw) {
     logError('Telegram raw data is undefined.')
     return
   }

   authenticateUser(initDataStateRaw)
 }, [userId, initDataStateRaw, authenticateUser, logError])

 useEffect(() => {
   console.log('Initializing app with raw data:', initDataStateRaw)
   initApp()
 }, [initApp, initDataStateRaw])

 if (error) {
   return {error}
 }

 return isAppReady ? <>{children} : 
}

export default AppInitProvider
Enter fullscreen mode Exit fullscreen mode

Our application is now fully initialised and ready to run.

Specifics of Telegram mini app development

Let's look at the key issues and challenges you need to understand to ensure a stable application and user experience.

WebView limitations

Since Telegram mini apps are based on the WebView interface, not all of the features we use in browsers will work here.

In our app we use iFrame. And if we want to open it in full screen, we can use the requestFullScreen method. Unfortunately, this doesn't work for WebView. Also, some icons may display differently or not at all. Especially if gradients are used inside. Problems can also occur when using WebSocket, which might requires additional configuration. It is therefore important to spend extra time on development to fix any bugs.

Image description

Also, I have often seen comments on forums that using WebView causes performance degradation. Especially on low-power devices and when there are a lot of complex animations. Some developers even include the ability to disable animations in the app so users don't experience performance issues.

Dependency on Telegram API

Despite the popularity of Telegram, the platform also has its own bugs that can affect your app. Here's a case study example.
Telegram automatically handles situations where a virtual keyboard is opened on mobile devices and automatically moves content so that they do not overlap. On the one hand, this is very convenient – developers don't have to write additional logic if, for example, the input field is at the bottom of the screen. On the other hand, at the time of writing, this feature still does not work correctly on iOS devices, which can lead to some limitations for users.

Limitation in working with query parameters

If you try to pass initial parameters to a mini app link, Telegram will simply cut them out. According to the Telegram mini app documentation, we can only pass one parameter called startapp. The process looks like this: https://t.me/botusername/appname?startapp=someParamValue
But if you need to pass multiple parameters, you can use the following trick: we still pass one parameter in the reference, but it has many other parameters nested within it with a specific delimiter. For example, '_ _'.

https://t.me/botusername/appname?startapp=param1value__param2value__par
am3value
const [param1, param2, param3] = lp.startParam.split("__");
Enter fullscreen mode Exit fullscreen mode

Client-side nature of app

It is also important to remember that mini apps run primarily on the client side. This limits the use of server-side rendering (SSR), because to use the Telegram API you need access to the window object, which is only available on the client.

Peculiarities of the Telegram platform

To create a Telegram mini app, you need to understand the specifics of the platform itself and the specifics of working with WebView.
For beginners, I recommend to pay attention to the following:

  • Telegram WebApp API – interact with buttons, events, get user data. Study the library documentation if you plan to use it to work with Telegram;
  • Permission handling – proper handling of initData and signature verification. Authorisation is taken to a new level here, eliminating the need to constantly create and store tokens on the client side;
  • Platform limitations – specifics of rendering, navigation and integration with front-end frameworks (e.g. Next.js);

Interface customisation – working with dark and light themes, WebView customisation, and developing workarounds for platform limitations.

✍ This may seem complicated and time-consuming, but learning new things is an important part of being a programmer. In the future, Telegram will certainly continue to develop its API and expand support for device hardware capabilities.

The sooner you start working with this format, the sooner you'll learn to develop cross-platform solutions, get a deeper understanding of external API integration, and gain experience with scalable applications for an audience of billions.

The dev.family team has been in touch 💜💚
See you soon!

P.S. As per tradition, here are the links to the artefacts:
Telegram mini apps documentation:

Top comments (0)