Building a Telegram Mini App with Vue 3 involves integrating the Telegram WebApp JavaScript API, managing the user interface based on the Telegram theme, handling viewport changes, and securely communicating data back to your bot. This tutorial focuses on the client-side Vue 3 implementation, covering the essential steps for setting up your Mini App and sending data. We will not cover the server-side bot implementation in detail, but we will discuss the principles of initData validation.
1. Setting up the Vue 3 Project
First, ensure you have a basic Vue 3 project set up. You can create one using Vite:
npm init vue@latest
# Follow the prompts, choose Vue Router if you need routing
cd your-mini-app
npm install
npm run dev
This will give you a standard Vue 3 application. The next step is to integrate the Telegram WebApp script.
2. Integrating Telegram WebApp JS
Telegram Mini Apps run within a special WebView that injects the Telegram.WebApp object into the global scope. To access its functionalities, you need to include the Telegram WebApp script in your index.html file. It's crucial to load this script *before* your Vue application script so that Telegram.WebApp is available when your Vue components mount.
Modify your public/index.html (or index.html in the root for Vite projects) to include the script:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<link rel="icon" href="/favicon.ico">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>My Telegram Mini App</title>
<script src="https://telegram.org/js/telegram-web-app.js"></script>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
</body>
</html>
Important Note: The telegram-web-app.js script is only available when your Mini App is opened within Telegram. When developing locally in a browser, Telegram.WebApp will be undefined. You'll need to handle this gracefully in your code, typically by checking window.Telegram?.WebApp before accessing its properties or methods.
3. Initializing and Accessing WebApp Properties in Vue
Once the script is loaded, you can access Telegram.WebApp within your Vue components. A good practice is to create a composable or a global plugin to manage its state and reactivity.
Let's create a simple composable useWebApp.js:
// src/composables/useWebApp.js
import { ref, onMounted, onUnmounted, readonly } from 'vue';
export function useWebApp() {
const webApp = ref(null);
const themeParams = ref({});
const viewportHeight = ref(window.innerHeight);
const viewportStableHeight = ref(window.innerHeight);
const initWebApp = () => {
if (window.Telegram?.WebApp) {
webApp.value = window.Telegram.WebApp;
webApp.value.ready(); // Notify Telegram that the Mini App is ready
themeParams.value = webApp.value.themeParams;
viewportHeight.value = webApp.value.viewportHeight;
viewportStableHeight.value = webApp.value.viewportStableHeight;
// Set the background color to match Telegram's theme
document.body.style.backgroundColor = webApp.value.themeParams.bg_color;
// Event listeners for theme and viewport changes
webApp.value.onEvent('themeChanged', handleThemeChanged);
webApp.value.onEvent('viewportChanged', handleViewportChanged);
// Optional: Expand the Mini App to full height if needed
// webApp.value.expand();
} else {
console.warn('Telegram WebApp object not found. Running in standalone browser mode.');
// Provide mock data for local development if necessary
themeParams.value = {
bg_color: '#ffffff',
text_color: '#000000',
hint_color: '#808080',
link_color: '#2481cc',
button_color: '#2481cc',
button_text_color: '#ffffff'
};
}
};
const handleThemeChanged = () => {
if (webApp.value) {
themeParams.value = webApp.value.themeParams;
document.body.style.backgroundColor = webApp.value.themeParams.bg_color;
}
};
const handleViewportChanged = () => {
if (webApp.value) {
viewportHeight.value = webApp.value.viewportHeight;
viewportStableHeight.value = webApp.value.viewportStableHeight;
}
};
onMounted(() => {
initWebApp();
});
onUnmounted(() => {
if (webApp.value) {
webApp.value.offEvent('themeChanged', handleThemeChanged);
webApp.value.offEvent('viewportChanged', handleViewportChanged);
}
});
return {
webApp: readonly(webApp),
themeParams: readonly(themeParams),
viewportHeight: readonly(viewportHeight),
viewportStableHeight: readonly(viewportStableHeight),
};
}
Now, in your App.vue or any component, you can use this composable:
<script setup>
import { useWebApp } from './composables/useWebApp';
import { ref, computed } from 'vue';
const { webApp, themeParams, viewportHeight, viewportStableHeight } = useWebApp();
const message = ref('');
const sendDataToBot = () => {
if (webApp.value) {
// Ensure data is a string. JSON.stringify is common.
webApp.value.sendData(JSON.stringify({ type: 'user_message', payload: message.value }));
webApp.value.close(); // Optionally close the Mini App after sending data
} else {
console.warn('Cannot send data: Telegram WebApp not available.');
alert('Simulating data send: ' + message.value);
}
};
const mainButtonText = computed(() => {
return webApp.value?.MainButton.text || 'Send Data';
});
const showMainButton = () => {
if (webApp.value) {
webApp.value.MainButton.setText('Submit Message');
webApp.value.MainButton.show();
webApp.value.MainButton.onClick(sendDataToBot);
}
};
// Call showMainButton when the component is mounted or when conditions are met
// For simplicity, we'll call it directly here. In a real app, you might trigger it
// based on user input or form validity.
import { onMounted } from 'vue';
onMounted(() => {
showMainButton();
});
</script>
<template>
<div :style="{ backgroundColor: themeParams.bg_color, color: themeParams.text_color, minHeight: viewportHeight + 'px' }" class="mini-app-container">
<h1>My Awesome Mini App</h1>
<p>Theme Background: {{ themeParams.bg_color }}</p>
<p>Viewport Height: {{ viewportHeight }}px</p>
<p>Viewport Stable Height: {{ viewportStableHeight }}px</p>
<input
type="text"
v-model="message"
placeholder="Enter your message"
:style="{ backgroundColor: themeParams.bg_color, color: themeParams.text_color, borderColor: themeParams.hint_color }"
/>
<button
@click="sendDataToBot"
:style="{ backgroundColor: themeParams.button_color, color: themeParams.button_text_color }"
>
{{ mainButtonText }} (Fallback Button)
</button>
<p v-if="webApp?.initData">
**InitData (for server validation):**
<textarea readonly :value="webApp.initData" rows="5"></textarea>
</p>
<p v-else>
`initData` is not available in local development. It's crucial for server-side validation.
</p>
</div>
</template>
<style scoped>
.mini-app-container {
padding: 20px;
font-family: sans-serif;
display: flex;
flex-direction: column;
gap: 15px;
}
input[type="text"],
textarea {
width: 100%;
padding: 10px;
border: 1px solid;
border-radius: 5px;
box-sizing: border-box; /* Ensure padding doesn't increase width */
}
button {
padding: 10px 15px;
border: none;
border-radius: 5px;
cursor: pointer;
font-size: 16px;
}
</style>
In this example: - We use onMounted to initialize Telegram.WebApp and register event listeners. - webApp.ready() is called to signal to Telegram that the Mini App is loaded and ready to receive events. - themeParams provides colors for bg_color, text_color, etc., allowing your app to adapt to the user's Telegram theme. - viewportHeight and viewportStableHeight are useful for adjusting your layout, especially when the keyboard is open. viewportStableHeight represents the height without accounting for the keyboard. - document.body.style.backgroundColor is set to match the Telegram background color for a seamless experience. - We demonstrate webApp.sendData() to send a stringified JSON object back to the bot. This data will be received by your bot as part of an update object, specifically in message.web_app_data.data. - webApp.MainButton is used for a prominent action button at the bottom of the Mini App. It's generally preferred over a custom button for better UX.
4. Handling initData and Server-Side Validation (Optional but Recommended)
Telegram.WebApp.initData contains data about the user, the bot, and the Mini App launch parameters. It's a URL-encoded string that is cryptographically signed. This initData is crucial for server-side validation to ensure that requests coming from your Mini App are legitimate and haven't been tampered with.
When your Mini App sends data to your backend (e.g., via fetch or axios to your own API endpoint, not directly via sendData), you should include webApp.initData in the request headers or body.
Client-side (Vue component):
// ... inside your component where you make an API call
const submitFormToServer = async () => {
if (!webApp.value) {
console.warn('Telegram WebApp not available. Cannot send initData.');
return;
}
try {
const response = await fetch('/api/submit-data', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Telegram-Init-Data': webApp.value.initData // Send initData in a header
},
body: JSON.stringify({ userMessage: message.value, someOtherField: 'value' })
});
if (response.ok) {
const result = await response.json();
console.log('Server response:', result);
webApp.value.showAlert('Data sent successfully!');
webApp.value.close();
} else {
const errorData = await response.json();
console.error('Server error:', errorData);
webApp.value.showAlert('Failed to send data: ' + (errorData.message || 'Unknown error'));
}
} catch (error) {
console.error('Network or client error:', error);
webApp.value.showAlert('An error occurred. Please try again.');
}
};
// You would call submitFormToServer instead of sendDataToBot for API interactions
Server-side (Conceptual PHP example for validation):
<?php
// This is a conceptual example for server-side validation.
// In a real application, use a robust framework and environment variables for the bot token.
function validateTelegramInitData(string $initData, string $botToken): bool
{
$data = [];
parse_str($initData, $data);
if (!isset($data['hash'])) {
return false; // Hash is missing
}
$hash = $data['hash'];
unset($data['hash']);
// Sort data keys alphabetically and reconstruct the data string
ksort($data);
$dataCheckString = [];
foreach ($data as $key => $value) {
$dataCheckString[] = "{$key}={$value}";
}
$dataCheckString = implode("\n", $dataCheckString);
// Calculate secret key
$secretKey = hash_hmac('sha256', $botToken, 'WebAppData', true);
// Calculate hash of the data check string
$calculatedHash = hash_hmac('sha256', $dataCheckString, $secretKey);
// Compare calculated hash with the received hash
return $calculatedHash === $hash;
}
// Example usage in an API endpoint:
// $initData = $_SERVER['HTTP_X_TELEGRAM_INIT_DATA'] ?? '';
// $botToken = getenv('TELEGRAM_BOT_TOKEN'); // Always use environment variables!
// if (empty($initData) || !validateTelegramInitData($initData, $botToken)) {
// http_response_code(403);
// echo json_encode(['message' => 'Invalid initData or unauthorized.']);
// exit;
// }
// Process your form data here...
// echo json_encode(['status' => 'success', 'message' => 'Data received and validated.']);
?>
This server-side validation ensures that the initData has not been tampered with and originates from Telegram. The botToken used for validation must be the exact token of the bot that launched the Mini App.
5. Production Considerations
- HTTPS: Your Mini App must be served over HTTPS. Telegram will not load HTTP Mini Apps. - Error Handling: Implement robust error handling for Telegram.WebApp calls and API requests. - Loading States: Show loading indicators while data is being fetched or sent. - User Feedback: Use webApp.showAlert(), webApp.showConfirm(), and webApp.showPopup() for user feedback within the Telegram interface. - Main Button: Leverage webApp.MainButton for primary actions. It provides a consistent look and feel and is integrated with Telegram's UI. - Back Button: If your Mini App has multiple views or a navigation history, consider using webApp.BackButton to allow users to navigate back within your app, or webApp.close() to exit. - initDataUnsafe: While initData is for validation, initDataUnsafe provides a parsed object version of the data, which can be convenient for accessing user information directly on the client side (e.g., webApp.initDataUnsafe.user.first_name). However, never trust initDataUnsafe for security-critical operations without server-side validation of the full initData string.
By following these steps, you can build a functional and well-integrated Telegram Mini App using Vue 3, providing a rich user experience directly within Telegram. For further reading on the Telegram Bot API and Mini Apps, you can refer to the official documentation at https://botservice.biz/telegram-bot-api.
This tutorial was brought to you by BotCreator — studio that ships Telegram bots / Mini Apps.