How to make full-stack from one device without SMS and registration
This article is written for lamers
I was learning to code as usual, and suddenly noticed that telegram released a web api and now there is a front…
At that point, I already owned React, Flask, and Django, and decided to raise the bar with FastAPI.
Believing in myself, I installed FastAPI and aiogram. Obviously, I should have used the native methods of the Telegram API, but aiogram provides many handy features that speed up development.
Problem
Django and Flask use templating engines (I used Jinja2), which means that the code that runs in the browser (HTML, JS, CSS, TS, etc.) is sent directly from the server. Thus, such an application does not have a separate server for the frontend.
however Fast-api and React need separate servers…
I understand that there will be people who will say: “Why use a separate server for React when you can add reactivity with Knockout.js and build a web application on a single server?” – Yes, it is possible, but it seems to me that it is better to use normal technologies, and not crutches on crutches, so that by the time the project is completed you have valuable experience in your resume, and not a reason for retirement))
I’ve come across enterprise projects written using legacy Knockout, js and Python 2, all on the same monolith. The scheme works, besides, you can hire juniors and pay them less, because everyone is taught Django in courses).
Solution
Here we have two servers – FastAPI and React. Next, we need to connect them with the Telegram API. Consider a specific example of such a connection: of course, FastAPI and Telegram interact via a webhook. To bring them together, we send a Telegram request to the server, in which we indicate the public address received from ngrok and the bot token from the .env file. In my case, I added the address of ngrok to .env and FastAPI automatically connects to Telegram on startup. The main problem is that ngrok, even in the paid version, only supports one address, while my frontend runs on a separate port. However, localhost.run comes to the rescue here. It has similar functionality to ngrok, but is a bit more configurable.
Here is the plan I would follow
Run localhost.run on port 3000 and add the address to the backend environment
run React on port 3000
Run ngrok on port 8000 and add the address to the environment
Run Fast-api locally on port 8000
The plan is simple and reliable, like a Swiss watch.
1.localhost.run
We go to https://admin.localhost.run/ sign up/log in.
Making shh key orig. instruction in English. The key consists of 2 parts public and private. go to the terminal and write
ssh-keygen -t rsa -b 2048 -C "<comment>"
*It is recommended to include an email address in the comments
choose the name of the key. and remember the path. if you do not enter a name, then the keys will be generated with the default names id_rsa and id_rsa.pub
go to the directory of the key, press ls -1 to make sure that we came to the right place and the keys are ready and waiting for us.
ls -1
id_rsa and id_rsa.pub are private and public keys respectively. Open id_rsa.pub with vim or nano. If there is no vim, then install vim))
vim id_rsa.pub
copy everything inside (starting with shh and up to the end of the text)
then exit vim to do this, press “:” and type q! and press enter.
go to browser https://admin.localhost.run/#/keys enter this key
then go to the console
ssh -R 80:localhost:3000 localhost.run
What does this command mean can be read in the service documentation
We get our white address on port 3000
add the received address to the backend .env
now it’s time for REACT
2. REACT
So in order to do without dancing, I made a wizzard script. I note that it allows you to choose js / jsx, yarn / npm, and creates a docker and docker compose with forwarded ports, and all code is moved to Valium, so the project does not require constant rebuilding.
So, create create_react_compose_app.sh or download from github.
We make it executable, on a poppy it is done like this:
chmod +x create_react_compose_app.sh
Let’s run the script:
bash create_react_copmpose_app.sh
Run docker, then enter in the console
docker-compose up
And now the application is open on port 3000, now the browser goes to the address that we received in localhost.run
Let’s go to
Open index.html and add the telegram library there
<script src="https://telegram.org/js/telegram-web-app.js"></script>
And replace
<p> Edit <code>src/App.js</code> and save to reload.</p>
<a
className="App-link"
href="https://reactjs.org"
target="_blank"
rel="noopener noreferrer"
>
Learn React
</a>
On
<p> Hellow Habr. </p>
In src/App.js
check at the address given by localhost.run
3. Ngrok
go to browser https://ngrok.com/ Registering / logging in
go to https://dashboard.ngrok.com/get-started/setup
do everything according to the instructions:
unzip zip
substitute the key in the config, it is convenient that the documentation immediately gives personal commands with the substituted key
run ngrok on port 8000
ngrok http 8000
4. Fast-API
go to source folder
create a Fast-API folder using the command
mkdir Fast-API
let’s get into it
cd Fast-API
create main.py And README.md, .env using the familiar touch command. It’s easy to remember about not having a song Daft Punk
create a file and put the address from localhost.run , ngrok , tokenbot …
touch .env
Create venv
Yes, normal boys use Poetrybut this text is exclusively for lamers, if you are ready to do it right away, then go ahead, it will be through venv.
python3 -m venv venv
activate it
source venv/bin/activate
update
pip install --upgrade pip
put uvicorn
pip install uvicorn
set fast-api
pip install fastapi
set aiogram
pip install aiogram
install python-dotenv, this is one of those packages that are not named in pip as they are imported
pip install python-dotenv
V README.md write start command
$ uvicorn main:app --reload
open main.py
write imports and load variables from .env
from dotenv import load_dotenv
from fastapi import FastAPI
load_dotenv() # загружем .env в оперативку
TOKEN = os.environ["TELEGRAM_TOKEN"] # получаем занчения переменных
BACK_URL = os.environ["BACK_URL"]
REACT_URL = os.environ["REACT_URL"]
WEBHOOK_PATH = f"/bot/{TOKEN}" # формируем системные переменные из переменных окружения
WEBHOOK_URL = BACK_URL + WEBHOOK_PATH
create an application and write a check function
app = FastAPI()
@app.get("/")
async def root():
return {"message": "Hello habr"}
start the uvicorn server with the application app = FastAPI with the command from README.md
uvicorn main:app --reload
go to BACK_URL in the browser, check that everything is working and we correctly installed and imported the .env file and launched the FastAPI correctly
import aiogram and create message handler functions And so on
@app.on_event("startup") #обработчик запуска приложения, засылает вебхук в телеграм
async def on_startup():
webhook_info = await bot.get_webhook_info()
if webhook_info.url != WEBHOOK_URL:
await bot.set_webhook(
url=WEBHOOK_URL
)
@app.post(WEBHOOK_PATH) #обработчик событий телгарма
async def bot_webhook(update: dict):
telegram_update = types.Update(**update)
Dispatcher.set_current(dp)
Bot.set_current(bot)
await dp.process_update(telegram_update)
add an old command handler in telegram
@dp.message_handler(commands="start")
async def new_message(message: types.Message):
text="REACT"
keyboard = types.InlineKeyboardMarkup()
keyboard.add(types.InlineKeyboardButton('Launch react', web_app=WebAppInfo(url=REACT_URL)))
await bot.send_message(message.chat.id, text, reply_markup=keyboard)
@app.on_event("shutdown") #и обработчик закрытия сесии
async def on_shutdown():
await bot.get_session()
await bot.session.close()
logging.info("Bot stopped")
add favicon.iso and here is the full code
import logging
import os
from aiogram import types, Dispatcher, Bot
from aiogram.types import WebAppInfo
from dotenv import load_dotenv
from fastapi import FastAPI
from fastapi.responses import FileResponse
load_dotenv()
TOKEN = os.environ["TELEGRAM_TOKEN"]
BACK_URL = os.environ["BACK_URL"]
REACT_URL = os.environ["REACT_URL"]
WEBHOOK_PATH = f"/bot/{TOKEN}"
WEBHOOK_URL = BACK_URL + WEBHOOK_PATH
bot = Bot(token=TOKEN)
dp = Dispatcher(bot)
app = FastAPI()
favicon_path="favicon.ico"
@app.get('/favicon.ico', include_in_schema=False)
async def favicon():
return FileResponse(favicon_path)
@app.get("/")
async def root():
return {"message": "Hello habr"}
@app.on_event("startup")
async def on_startup():
webhook_info = await bot.get_webhook_info()
if webhook_info.url != WEBHOOK_URL:
await bot.set_webhook(
url=WEBHOOK_URL
)
@app.post(WEBHOOK_PATH)
async def bot_webhook(update: dict):
telegram_update = types.Update(**update)
Dispatcher.set_current(dp)
Bot.set_current(bot)
await dp.process_update(telegram_update)
@app.on_event("shutdown")
async def on_shutdown():
await bot.get_session()
await bot.session.close()
logging.info("Bot stopped")
@dp.message_handler(commands="start")
async def new_message(message: types.Message):
text="REACT"
keyboard = types.InlineKeyboardMarkup()
keyboard.add(types.InlineKeyboardButton('Launch react', web_app=WebAppInfo(url=REACT_URL)))
await bot.send_message(message.chat.id, text, reply_markup=keyboard)
@app.post(WEBHOOK_PATH)
async def bot_webhook(update: dict):
telegram_update = types.Update(**update)
Dispatcher.set_current(dp)
Bot.set_current(bot)
await dp.process_update(telegram_update)
save, run, check
In this article, I described setting up a web application in the context of #Telegram and #Web Apps for Bots. If desired, you can also enable the use of the FAST-API in the container using #poetry, but this is no longer for beginners. In addition, we also touched on #ssh keys, #react, #python, #fast-api, #docker, #docker-compose, #ngrok, #localhost.run, #venv, #uvicorn and used #shell-script.