<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Arppith Andrews</title>
    <description>The latest articles on DEV Community by Arppith Andrews (@atrx07).</description>
    <link>https://dev.to/atrx07</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4191793%2F93a1acc7-1ad9-4a7e-8da5-fdfcf94c008d.jpg</url>
      <title>DEV Community: Arppith Andrews</title>
      <link>https://dev.to/atrx07</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/atrx07"/>
    <language>en</language>
    <item>
      <title>How I structure a Python Telegram bot so it doesn't become spaghetti</title>
      <dc:creator>Arppith Andrews</dc:creator>
      <pubDate>Sun, 18 Oct 2026 09:14:42 +0000</pubDate>
      <link>https://dev.to/atrx07/how-i-structure-a-python-telegram-bot-so-it-doesnt-become-spaghetti-52gj</link>
      <guid>https://dev.to/atrx07/how-i-structure-a-python-telegram-bot-so-it-doesnt-become-spaghetti-52gj</guid>
      <description>&lt;p&gt;Most Telegram bot tutorials end with the same thing: one giant &lt;code&gt;main.py&lt;/code&gt; where handlers, keyboards, and logging are all tangled together. Works for a weekend project. Falls apart the moment you add your fifth command.&lt;/p&gt;

&lt;p&gt;Here's the structure I use for every bot now (python-telegram-bot v20):&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. One file per concern.&lt;/strong&gt; &lt;code&gt;main.py&lt;/code&gt; only wires things up. Handlers live in their own module, keyboards in another. Your future self will thank you.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. The skeleton is always the same:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;telegram&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Update&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;telegram.ext&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Application&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;CommandHandler&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Update&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reply_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Hello! I&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;m alive.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;token&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;YOUR_TOKEN&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;build&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;CommandHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;start&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run_polling&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Everything hangs off this. New command = new handler function + one &lt;code&gt;add_handler&lt;/code&gt; line. Nothing else touches anything else.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Keyboards are data, not logic.&lt;/strong&gt; Inline buttons and reply keyboards get their own builder functions that return markup — the handler just calls them. When Telegram changes button behavior (it happens), you fix it in one place.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Logging from day one.&lt;/strong&gt; Not print statements — the &lt;code&gt;logging&lt;/code&gt; module, configured once at startup. The first time your bot silently dies at 2am, you'll understand why.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Config out of code.&lt;/strong&gt; Token in an environment variable, never hardcoded. This is the mistake every beginner tutorial teaches.&lt;/p&gt;

&lt;p&gt;I got tired of rebuilding this scaffolding for every bot, so I packaged the whole structure — the wired-up &lt;code&gt;main.py&lt;/code&gt;, requirements, a setup guide, plus add-on templates for inline buttons, reply keyboards, and logging — into a starter kit: &lt;a href="https://shadowatrx.gumroad.com/l/dkyutc" rel="noopener noreferrer"&gt;https://shadowatrx.gumroad.com/l/dkyutc&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It's the exact skeleton above, ready to run. Grab a token from &lt;a class="mentioned-user" href="https://dev.to/botfather"&gt;@botfather&lt;/a&gt;, drop it in, and you're live in minutes.&lt;/p&gt;

&lt;p&gt;What does your bot structure look like? Always curious how other people organize theirs.&lt;/p&gt;

</description>
      <category>python</category>
      <category>telegram</category>
      <category>showdev</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>I built a free invoice generator with zero backend</title>
      <dc:creator>Arppith Andrews</dc:creator>
      <pubDate>Sun, 18 Oct 2026 09:05:21 +0000</pubDate>
      <link>https://dev.to/atrx07/i-built-a-free-invoice-generator-with-zero-backend-4kln</link>
      <guid>https://dev.to/atrx07/i-built-a-free-invoice-generator-with-zero-backend-4kln</guid>
      <description>&lt;p&gt;Freelancers lose hours to invoicing admin. I built a free tool that does it in seconds: business/client profiles, line items, tax, discounts, PDF download — all client-side, no signup, no server, no database.&lt;/p&gt;

&lt;p&gt;The whole thing is vanilla HTML/CSS/JS on Cloudflare Pages (free tier), with SEO basics baked in: semantic HTML, meta/OG tags, JSON-LD, sitemap. Total hosting cost: $0.&lt;/p&gt;

&lt;p&gt;Try it: &lt;a href="https://free-invoicegen.pages.dev" rel="noopener noreferrer"&gt;https://free-invoicegen.pages.dev&lt;/a&gt; — feedback welcome, especially from people who invoice for real.&lt;/p&gt;

</description>
      <category>freelance</category>
      <category>webdev</category>
      <category>showdev</category>
    </item>
  </channel>
</rss>
