<?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: ShatilKhan</title>
    <description>The latest articles on DEV Community by ShatilKhan (@siren).</description>
    <link>https://dev.to/siren</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F558537%2F82f48ad4-591e-4a86-816a-10250ca198cb.jpg</url>
      <title>DEV Community: ShatilKhan</title>
      <link>https://dev.to/siren</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/siren"/>
    <language>en</language>
    <item>
      <title>Engineering of Small Things: Telegram Bots</title>
      <dc:creator>ShatilKhan</dc:creator>
      <pubDate>Mon, 08 Jun 2026 05:59:19 +0000</pubDate>
      <link>https://dev.to/siren/engineering-of-small-things-telegram-bots-5fme</link>
      <guid>https://dev.to/siren/engineering-of-small-things-telegram-bots-5fme</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/github-2026-05-21"&gt;GitHub Finish-Up-A-Thon Challenge&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Yunus&lt;/strong&gt; is a shared finance tracker for couples — built as a Telegram bot with a built-in web dashboard. My wife and I use it daily to track every expense across 14 categories (Bazar, Travel, Rent, Bills, and yes — a category literally called &lt;strong&gt;"Wife"&lt;/strong&gt;). Everything is shared. Everything is real-time. Zero passwords.&lt;/p&gt;

&lt;p&gt;The entire thing fits in under a thousand lines of TypeScript.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;flowchart LR
    U[You &amp;amp;amp; Your Wife] --&amp;gt;|Send /start| TB[Telegram Bot API]
    U --&amp;gt;|Open Dashboard| TM[Telegram Mini App]

    TB --&amp;gt;|Webhook POST| VW[Vercel: Bot Handler&amp;lt;br&amp;gt;/api/webhook]
    TM --&amp;gt;|Load HTML+JS| VS[Vercel: Static Dashboard]

    VS --&amp;gt;|API calls| VA[Vercel: API Layer&amp;lt;br&amp;gt;/api/stats /api/entries /api/categories]
    VW --&amp;gt;|Read/Write Entries &amp;amp;amp; Budgets| DB[(Turso Edge SQLite)]
    VA --&amp;gt;|Query Data| DB
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;System overview&lt;/strong&gt;: You interact with the bot through Telegram. The bot handler runs as a Vercel serverless function. The dashboard is a React SPA served as a Telegram Mini App. All data lives in Turso — a globally distributed SQLite database.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why Telegram?
&lt;/h3&gt;

&lt;p&gt;Most finance apps are designed for individuals. No one builds for couples. And the ones that exist (Splitwise, Honeydue) are over-engineered for what we needed: &lt;em&gt;"hey, how much did we spend on bazar this week?"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Telegram was the perfect foundation because:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Mini Apps&lt;/strong&gt;: Native WebView dashboards baked right into the chat app — no separate app to install.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Zero-login auth&lt;/strong&gt;: Telegram injects cryptographically signed user data into every Mini App. No signup flow.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cross-platform&lt;/strong&gt;: Works identically on iOS and Android through Telegram.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bot interface&lt;/strong&gt;: Adding an expense is as fast as typing a message.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;GitHub Repository&lt;/strong&gt;: &lt;a href="https://github.com/ShatilKhan/yunus" rel="noopener noreferrer"&gt;https://github.com/ShatilKhan/yunus&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Screenshots
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frexopjphwls2d3gzy7vw.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frexopjphwls2d3gzy7vw.jpg" alt="Start menu — the main command center" width="720" height="1604"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdwu84uk1y6vz8a0s8p5n.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdwu84uk1y6vz8a0s8p5n.jpg" alt="Budget view showing spent and over-since date" width="720" height="1604"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhflkbcu96vvz91uggr6t.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhflkbcu96vvz91uggr6t.jpg" alt="Dashboard with stats cards, pie chart, and table" width="720" height="1604"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2otu70r5a7ngyfss4nvv.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2otu70r5a7ngyfss4nvv.jpg" alt="Adding an expense with inline note parsing" width="720" height="1604"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The Comeback Story
&lt;/h2&gt;

&lt;p&gt;My original build was a 24-hour sprint. I sat down on May 1st, 2026, and cranked out &lt;strong&gt;20 commits in a single day&lt;/strong&gt;. The skeleton worked — you could add expenses, see summaries, and it had a budget feature. But it was rough.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;May  1 — 20 commits: initial scaffold, bot, API, dashboard, everything
May  5 —  3 commits: budgets, summaries, backfill (unfinished)
         ╰──── 26 days of nothing ────╯
May 31 —  3 commits: the comeback — 195 lines changed, 6 files
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The 26-day gap wasn't laziness. It was the realization that the product &lt;em&gt;almost&lt;/em&gt; worked, but the friction points were killing the experience. Every day I thought "I'll fix it tomorrow" — and I didn't.&lt;/p&gt;

&lt;p&gt;Then the GitHub Finish-Up-A-Thon challenge gave me the push I needed.&lt;/p&gt;

&lt;h3&gt;
  
  
  What was broken
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;1. The entry wizard took three steps when it should take two.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Original flow: Pick category → Type amount → &lt;em&gt;"Add a note? (Skip)"&lt;/em&gt; → Confirm. That extra note step with a Skip button was a tiny thing that added up to major friction. When you're logging 5-6 transactions a day, an extra tap per entry is &lt;strong&gt;150-180 extra taps a month&lt;/strong&gt; for skipping a step you usually skip anyway.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Budget editing didn't exist.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You could set a budget, but you couldn't adjust it. If you wanted to bump the amount up, you had to close the entire budget and create a new one — which triggered an auto-savings of the remainder.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. No "over since" date.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;When the bot alerted that we were over budget, it just said "Over by 5000 Taka." It never told us &lt;em&gt;when&lt;/em&gt; we went over. Was it day 3 of the month or day 25? That matters for next month's planning.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Bulk backfill was missing.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If I forgot to log a day's expenses, I had to add entries one at a time — going through the full wizard each time. The "Add Multiple" feature existed for today's date, but not for past dates.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. The dashboard had no CSS.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This was the most embarrassing one. I had beautiful shadcn components (cards, charts, tables) and the build was compiling them... but &lt;strong&gt;no one ever imported the CSS file&lt;/strong&gt;. The dashboard rendered as raw unstyled HTML. It &lt;em&gt;worked&lt;/em&gt;. It looked terrible.&lt;/p&gt;

&lt;h3&gt;
  
  
  The incremental note hack — my favorite fix
&lt;/h3&gt;

&lt;p&gt;This is the kind of fix that's obvious in hindsight but painful in practice.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;flowchart LR
    subgraph BEFORE["BEFORE (3 steps)"]
        A1["1. Pick Category&amp;lt;br&amp;gt;Bazar, Travel..."] --&amp;gt; A2["2. Enter Amount&amp;lt;br&amp;gt;500"]
        A2 --&amp;gt; A3["3. Add Note?&amp;lt;br&amp;gt;Skip / Type note"]
    end

    subgraph AFTER["AFTER (2 steps)"]
        B1["1. Pick Category&amp;lt;br&amp;gt;Bazar, Travel..."] --&amp;gt; B2["2. Enter Amount + Note&amp;lt;br&amp;gt;350 lunch with team"]
        B2 --&amp;gt; B3["Confirm &amp;amp;amp; Save"]
    end

    BEFORE --&amp;gt;|REFACTOR| AFTER
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The hack is &lt;strong&gt;parsing the amount and note from a single input&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// handlers.ts — the "amount" step now parses inline notes&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;trimmed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;firstSpace&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;trimmed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\s&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;amountStr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;firstSpace&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;trimmed&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;trimmed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;firstSpace&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;noteRaw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;firstSpace&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;trimmed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;firstSpace&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;amount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;amountStr&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;setSession&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`wizard:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;step&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;confirm&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;note&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;noteRaw&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// null = no note, no extra step&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you type &lt;code&gt;350 lunch with team&lt;/code&gt; — it parses &lt;code&gt;350&lt;/code&gt; as the amount and &lt;code&gt;lunch with team&lt;/code&gt; as the note. &lt;strong&gt;No extra step. Straight to confirmation.&lt;/strong&gt; I also deleted the entire &lt;code&gt;skip_note&lt;/code&gt; callback — 17 lines of dead code that existed only to handle a step that shouldn't have existed.&lt;/p&gt;

&lt;h3&gt;
  
  
  What the comeback changed
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Fix&lt;/th&gt;
&lt;th&gt;Lines changed&lt;/th&gt;
&lt;th&gt;Impact&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Inline note entry&lt;/td&gt;
&lt;td&gt;+47 / -22&lt;/td&gt;
&lt;td&gt;2-step wizard, removed Skip button entirely&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Editable budgets&lt;/td&gt;
&lt;td&gt;+14 / -0&lt;/td&gt;
&lt;td&gt;Change budget amount in-place&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Over-budget date&lt;/td&gt;
&lt;td&gt;+19 / -3&lt;/td&gt;
&lt;td&gt;See exact date budget was exceeded&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bulk backfill&lt;/td&gt;
&lt;td&gt;+52 / -3&lt;/td&gt;
&lt;td&gt;Bulk import for past dates&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dashboard CSS&lt;/td&gt;
&lt;td&gt;+15 / -2&lt;/td&gt;
&lt;td&gt;Tailwind styling + safe date parsing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Webhook migration&lt;/td&gt;
&lt;td&gt;+28 / -1&lt;/td&gt;
&lt;td&gt;Schema migration on cold starts&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  The Hack: Zero-Login Auth via Telegram initData
&lt;/h2&gt;

&lt;p&gt;This is the most interesting architectural decision: &lt;strong&gt;how authentication works without a single login screen&lt;/strong&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sequenceDiagram
    participant User as Telegram User
    participant MiniApp as Mini App (Browser)
    participant API as Vercel API
    participant DB as Turso DB

    User-&amp;gt;&amp;gt;MiniApp: Open Dashboard
    Note over MiniApp: Telegram injects&amp;lt;br&amp;gt;window.Telegram.WebApp.initData&amp;lt;br&amp;gt;(HMAC-SHA256 signed)

    MiniApp-&amp;gt;&amp;gt;API: GET /api/stats?days=30&amp;lt;br&amp;gt;Header: X-Telegram-Init-Data
    Note over API: verifyInitData()&amp;lt;br&amp;gt;HMAC-SHA256 with BOT_TOKEN

    API-&amp;gt;&amp;gt;DB: SELECT 1 FROM allowed_users WHERE id = ?
    DB--&amp;gt;&amp;gt;API: exists?
    API--&amp;gt;&amp;gt;MiniApp: 200 { data } or 401
    MiniApp--&amp;gt;&amp;gt;User: Render dashboard
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every Telegram Mini App receives a &lt;code&gt;window.Telegram.WebApp.initData&lt;/code&gt; string that's cryptographically signed with HMAC-SHA256 using your bot token. The frontend attaches this as an HTTP header:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// useAuth.ts — attaches initData to every fetch&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;apiFetch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useCallback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;X-Telegram-Init-Data&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;initData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Telegram-signed payload&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;initData&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On the backend, every endpoint verifies:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// auth.ts — HMAC-SHA256 verification&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;verifyInitData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;initData&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;URLSearchParams&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;initData&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;hash&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;hash&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;hash&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// Sort alphabetically, rebuild check string&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;dataCheckString&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;entries&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;localeCompare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(([&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// Derive secret key from bot token&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;secretKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;crypto&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createHmac&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sha256&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;WebAppData&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;botToken&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;digest&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="c1"&gt;// Verify hash&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;checkHash&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;crypto&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createHmac&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sha256&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;secretKey&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dataCheckString&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;digest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;hex&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;checkHash&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Tampered data&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;decodeURIComponent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Why this matters
&lt;/h3&gt;

&lt;p&gt;This pattern — &lt;strong&gt;cryptographic identity via the chat platform&lt;/strong&gt; — is incredibly powerful for internal tools:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No onboarding&lt;/strong&gt;: First person to message the bot becomes admin. Add whitelisted users via &lt;code&gt;/admin add &amp;lt;id&amp;gt;&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No password rotation&lt;/strong&gt;: Telegram handles security.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No session management&lt;/strong&gt;: Every request is self-verifying.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Shared by default&lt;/strong&gt;: All whitelisted users see the same data — exactly what you want for a couple's finances.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  The Why: A generalizable pattern
&lt;/h2&gt;

&lt;p&gt;This isn't just a finance tracker. The &lt;strong&gt;shared Telegram bot + Mini App&lt;/strong&gt; pattern applies to any two-person dataset:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Shared shopping lists&lt;/strong&gt; — both partners add items, check them off in real-time.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Chore tracking&lt;/strong&gt; — log who did what, when.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pet care schedules&lt;/strong&gt; — feeding, medication, walks.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Any "two people need to share one simple dataset" scenario.&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The key insight: &lt;strong&gt;if your users already use Telegram, you don't need a separate app.&lt;/strong&gt; You don't need login screens. You don't need onboarding flows. You just need a bot that responds to messages and a Mini App that displays data.&lt;/p&gt;




&lt;h2&gt;
  
  
  My Experience with GitHub Copilot
&lt;/h2&gt;

&lt;p&gt;Every line of this project was written with GitHub Copilot. Not as a "generate the whole app" tool — more like a rubber duck that never gets tired.&lt;/p&gt;

&lt;p&gt;Copilot was most useful in three scenarios:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Boilerplate generation.&lt;/strong&gt; The initial scaffold — bot setup, database schema, API handlers — is the most tedious part. Copilot predicted the next 5-10 lines consistently, turning 30-minute tasks into 5-minute ones.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. SQL query construction.&lt;/strong&gt; I'm decent at TypeScript, but SQL is where I need the most help. Copilot's suggestions for aggregation queries were correct on the first try:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Copilot wrote the CASE WHEN inside SUM and GROUP BY&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;totalsResult&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`
  SELECT
    SUM(CASE WHEN c.type = 'expense' THEN e.amount ELSE 0 END) as total_expense,
    SUM(CASE WHEN c.type = 'saving' THEN e.amount ELSE 0 END) as total_saving,
    COUNT(*) as transaction_count
  FROM entries e
  JOIN categories c ON e.category_id = c.id
  WHERE e.created_at &amp;gt;= datetime('now', '-' || ? || ' days')
`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;days&lt;/span&gt;&lt;span class="p"&gt;)]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;3. The comeback push.&lt;/strong&gt; The May 31 commits were the most Copilot-heavy session. By then, Copilot understood the codebase well enough that suggesting &lt;code&gt;updateBudgetAmount()&lt;/code&gt; or the date-parsing fallback felt like autocomplete.&lt;/p&gt;

&lt;p&gt;The one thing Copilot couldn't help with was &lt;strong&gt;debugging production issues&lt;/strong&gt; (CSS not loading, missing webhook migrations). Those required understanding the deployment flow — where code runs vs. where it doesn't — which no AI had enough context for.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Stack
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;Why&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Runtime&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Bun 1.x&lt;/td&gt;
&lt;td&gt;Fast Node.js alternative, built-in bundler&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Bot Framework&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Grammy.js&lt;/td&gt;
&lt;td&gt;Telegram bot lib with TypeScript types&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Frontend&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;React 19&lt;/td&gt;
&lt;td&gt;SPA rendering&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;UI&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;shadcn/ui (New York)&lt;/td&gt;
&lt;td&gt;Copy-paste components, full control&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Charts&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Recharts 3.8.0&lt;/td&gt;
&lt;td&gt;Pie chart breakdown&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Styling&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Tailwind CSS v4&lt;/td&gt;
&lt;td&gt;Utility-first CSS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Database&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Turso (edge SQLite)&lt;/td&gt;
&lt;td&gt;Globally distributed SQLite via HTTP&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Hosting&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Vercel&lt;/td&gt;
&lt;td&gt;Serverless functions + static hosting&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Auth&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Telegram initData + HMAC&lt;/td&gt;
&lt;td&gt;Zero-login, no passwords&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Icons&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Lucide React&lt;/td&gt;
&lt;td&gt;Clean icon set&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  The unusual choice: Turso (edge SQLite) over PostgreSQL
&lt;/h3&gt;

&lt;p&gt;Turso is SQLite turned into a distributed database. It speaks the same SQL you already know, accessible via HTTP from serverless functions. The tradeoff: single-writer (no concurrent global writes) — irrelevant for a couple's finance tracker. The gain: zero connection pooling, zero migration tooling, zero ops, and the same SQLite syntax you'd use locally.&lt;/p&gt;




&lt;h2&gt;
  
  
  Production Gotchas
&lt;/h2&gt;

&lt;p&gt;These are the bugs that almost made me give up. &lt;strong&gt;This is the most bookmarked section — save it.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  1. CSS never loaded on the dashboard
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Symptom&lt;/strong&gt;: Dashboard rendered as unstyled HTML. Cards had no backgrounds. Tables had no borders.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cause&lt;/strong&gt;: &lt;code&gt;build.ts&lt;/code&gt; used &lt;code&gt;bun-plugin-tailwind&lt;/code&gt; but no file imported the CSS. &lt;code&gt;index.css&lt;/code&gt; existed. &lt;code&gt;globals.css&lt;/code&gt; existed. Neither was referenced.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fix&lt;/strong&gt;: One import line:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;  &lt;span class="c1"&gt;// frontend.tsx&lt;/span&gt;
  &lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./index.css&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Webhook cold starts missed schema migrations
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Symptom&lt;/strong&gt;: Budget tracking returned "No active budget" even though budgets existed. No errors.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cause&lt;/strong&gt;: &lt;code&gt;initSchema()&lt;/code&gt; only ran in the local dev server (&lt;code&gt;src/index.ts&lt;/code&gt;). The production webhook handler (&lt;code&gt;api/webhook.ts&lt;/code&gt;) never called it. New column = query failed = catch returned &lt;code&gt;null&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fix&lt;/strong&gt;: Two changes — added migration to webhook handler AND made the query resilient:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;  &lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getActiveBudget&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// Try with new column&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SELECT id, amount, start_date, alert_sent, over_budget_date FROM budgets WHERE end_date IS NULL&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
      &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// Fallback: column doesn't exist yet&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SELECT id, amount, start_date, alert_sent FROM budgets WHERE end_date IS NULL&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
      &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. &lt;code&gt;new Date()&lt;/code&gt; is not your friend
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Symptom&lt;/strong&gt;: Dashboard showed "Invalid Date" for every entry — but only on some devices.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cause&lt;/strong&gt;: SQLite returns &lt;code&gt;"2026-05-31 14:30:00"&lt;/code&gt; (space-separated). &lt;code&gt;new Date()&lt;/code&gt; behavior with space-separated strings varies by browser. Telegram's Mini App WebView (Safari on iOS) returns &lt;code&gt;Invalid Date&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fix&lt;/strong&gt;: Manual parsing:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;formatDate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dateStr&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;parts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;dateStr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;[\s&lt;/span&gt;&lt;span class="sr"&gt;T&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;dateParts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;parts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;-&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;timeParts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;parts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;00:00:00&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;date&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nx"&gt;dateParts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dateParts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;dateParts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;timeParts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;timeParts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;timeParts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;isNaN&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getTime&lt;/span&gt;&lt;span class="p"&gt;()))&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;dateStr&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// fallback to raw string&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toLocaleDateString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;en-US&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* ... */&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4. Amount columns returned &lt;code&gt;NaN&lt;/code&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Symptom&lt;/strong&gt;: Random entries showed &lt;code&gt;NaN&lt;/code&gt; instead of &lt;code&gt;500.00&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cause&lt;/strong&gt;: &lt;code&gt;Number(entry.amount).toFixed(2)&lt;/code&gt; — if &lt;code&gt;amount&lt;/code&gt; is &lt;code&gt;undefined&lt;/code&gt; or &lt;code&gt;null&lt;/code&gt;, &lt;code&gt;Number(undefined).toFixed(2)&lt;/code&gt; = &lt;code&gt;"NaN"&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fix&lt;/strong&gt;:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;  &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nc"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;amount&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toFixed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  5. The over_budget_date column didn't exist in production
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Symptom&lt;/strong&gt;: After deploying, the bot couldn't find active budgets.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cause&lt;/strong&gt;: The ALTER TABLE migration ran only in the local dev server. Vercel serverless functions are fresh processes — the webhook handler never ran it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fix&lt;/strong&gt;:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;  &lt;span class="c1"&gt;// webhook.ts — runs on every cold start&lt;/span&gt;
  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;runMigrations&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ALTER TABLE budgets ADD COLUMN over_budget_date DATE&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// Column already exists — fine&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Quick Reference
&lt;/h2&gt;

&lt;p&gt;Deploy your own in 8 steps:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# 1. Clone&lt;/span&gt;
git clone https://github.com/ShatilKhan/yunus
&lt;span class="nb"&gt;cd &lt;/span&gt;yunus

&lt;span class="c"&gt;# 2. Install dependencies&lt;/span&gt;
curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://bun.sh/install | bash
bun &lt;span class="nb"&gt;install&lt;/span&gt;

&lt;span class="c"&gt;# 3. Create a Telegram bot&lt;/span&gt;
&lt;span class="c"&gt;#    Message @BotFather → /newbot → save the token&lt;/span&gt;

&lt;span class="c"&gt;# 4. Set up Turso database&lt;/span&gt;
turso auth login
turso db create yunus
turso db show yunus &lt;span class="nt"&gt;--url&lt;/span&gt;       &lt;span class="c"&gt;# → TURSO_DATABASE_URL&lt;/span&gt;
turso db tokens create yunus    &lt;span class="c"&gt;# → TURSO_AUTH_TOKEN&lt;/span&gt;

&lt;span class="c"&gt;# 5. Set environment variables&lt;/span&gt;
&lt;span class="c"&gt;#    BOT_TOKEN, TURSO_DATABASE_URL, TURSO_AUTH_TOKEN&lt;/span&gt;
&lt;span class="c"&gt;#    WEBHOOK_SECRET (random string for webhook auth)&lt;/span&gt;
&lt;span class="c"&gt;#    DASHBOARD_URL (your Vercel URL)&lt;/span&gt;

&lt;span class="c"&gt;# 6. Deploy to Vercel&lt;/span&gt;
vercel &lt;span class="nt"&gt;--prod&lt;/span&gt;

&lt;span class="c"&gt;# 7. Set Telegram webhook&lt;/span&gt;
&lt;span class="c"&gt;#    POST https://api.telegram.org/bot&amp;lt;TOKEN&amp;gt;/setWebhook&lt;/span&gt;
&lt;span class="c"&gt;#    ?url=https://&amp;lt;your-domain&amp;gt;/api/webhook&lt;/span&gt;
&lt;span class="c"&gt;#    &amp;amp;secret_token=&amp;lt;WEBHOOK_SECRET&amp;gt;&lt;/span&gt;

&lt;span class="c"&gt;# 8. Start using it&lt;/span&gt;
&lt;span class="c"&gt;#    Open Telegram → message your bot → /start&lt;/span&gt;
&lt;span class="c"&gt;#    You're admin. Add your partner:&lt;/span&gt;
&lt;span class="c"&gt;#    /admin add &amp;lt;their-telegram-id&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Build it. Use it. Stop wondering where your money went.&lt;/strong&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Built with TypeScript, Bun, Grammy.js, Turso, Tailwind CSS v4, shadcn/ui, Recharts, React 19, and GitHub Copilot. Open source at &lt;a href="https://github.com/ShatilKhan/yunus" rel="noopener noreferrer"&gt;github.com/ShatilKhan/yunus&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>githubchallenge</category>
    </item>
    <item>
      <title>Engineering of Small Things: Hermes Plugin</title>
      <dc:creator>ShatilKhan</dc:creator>
      <pubDate>Mon, 01 Jun 2026 06:11:03 +0000</pubDate>
      <link>https://dev.to/siren/engineering-of-small-things-hermes-plugin-opp</link>
      <guid>https://dev.to/siren/engineering-of-small-things-hermes-plugin-opp</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/hermes-agent-2026-05-15"&gt;Hermes Agent Challenge&lt;/a&gt;: Write About Hermes Agent&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Most "give your agent a new model provider" tutorials are stories of bravely subclassing things. You inherit from a base class, you override three methods, you read the wire-format docs of two vendors, you write an adapter, you handle the streaming chunks, you wire it into a settings page somewhere. By the time you've shipped, you've forgotten what you were trying to do.&lt;/p&gt;

&lt;p&gt;Hermes Agent's provider-plugin SDK refuses to play that game. The whole thing is a &lt;strong&gt;declarative dataclass and one registry call&lt;/strong&gt;. If the thing you want to add already speaks OpenAI on the wire which most modern gateways and aggregators do — you can be done in twenty-six lines.&lt;/p&gt;

&lt;p&gt;This post walks you through the pattern with a real working example: the &lt;code&gt;omnizen-provider&lt;/code&gt; plugin I shipped to expose &lt;a href="https://omnizen.ai" rel="noopener noreferrer"&gt;Omnizen&lt;/a&gt;'s gateway to Hermes. You don't have to care about Omnizen specifically the same shape works for &lt;em&gt;any&lt;/em&gt; OpenAI-compatible gateway (Together, Groq, Fireworks, your home-grown vLLM, your in-house router). Omnizen just happens to be a convenient real example because the URL is the only thing you'd swap out.&lt;/p&gt;

&lt;h2&gt;
  
  
  The shape Hermes wants
&lt;/h2&gt;

&lt;p&gt;A Hermes provider plugin lives at &lt;code&gt;plugins/model-providers/&amp;lt;your-name&amp;gt;/&lt;/code&gt; and ships two files:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;plugin.yaml&lt;/code&gt; — a short manifest so Hermes can discover and version the plugin.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;__init__.py&lt;/code&gt; — instantiates a &lt;code&gt;ProviderProfile&lt;/code&gt;, then calls &lt;code&gt;register_provider()&lt;/code&gt; on it.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That's it. There is no adapter class to subclass, no chat-completions method to implement, no streaming-chunk handler. The &lt;code&gt;ProviderProfile&lt;/code&gt; is &lt;em&gt;declarative&lt;/em&gt;: you describe what the provider is, and Hermes's existing OpenAI-compatible call path handles all the actual work.&lt;/p&gt;

&lt;p&gt;The fields &lt;code&gt;ProviderProfile&lt;/code&gt; cares about:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field&lt;/th&gt;
&lt;th&gt;What it's for&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;name&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Internal identifier; the key shown in &lt;code&gt;hermes model&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;aliases&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Short aliases users can type instead of the full name&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;env_vars&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Tuple of env vars the plugin reads — Hermes uses this for "is this provider configured?" detection&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;display_name&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Human-friendly name in &lt;code&gt;hermes model&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;description&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;One-line pitch under the name in the picker&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;signup_url&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Where Hermes sends users who don't have a key yet&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;base_url&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The OpenAI-compatible chat-completions endpoint&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;default_aux_model&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Model Hermes uses for internal calls (planning probes, tool description embeddings) when no model is specified&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;fallback_models&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Models Hermes tries in order when the primary fails or isn't picked&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;That's the whole API surface. If you can fill in those fields, you have a working provider.&lt;/p&gt;

&lt;h1&gt;
  
  
  My Plugin
&lt;/h1&gt;

&lt;p&gt;Here's the entire &lt;code&gt;__init__.py&lt;/code&gt; for the Omnizen plugin — no abbreviation, no "…and so on":&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4xw2cw5s35jl9edhgajz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4xw2cw5s35jl9edhgajz.png" alt="Initial omnizen plugin for hermes" width="800" height="566"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And the manifest next to it:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Faq4mcnr9uxoebe13981d.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Faq4mcnr9uxoebe13981d.png" alt="manifest write for hermese plugin" width="681" height="330"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can checkout the code here:&lt;br&gt;
&lt;a href="https://github.com/anchorblock/hermes-agent/commit/3c086646078472d3216310be5aee79c29beb8ed0" rel="noopener noreferrer"&gt;Hermes-Omnizen&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What happens at runtime
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnts05zlenhrkmnle6s3z.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnts05zlenhrkmnle6s3z.png" alt="Hermes Agent -&gt; Omnizen AI integration" width="800" height="1292"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The flow is symmetric on both ends:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;At Hermes startup&lt;/strong&gt;, the plugin's &lt;code&gt;__init__.py&lt;/code&gt; executes once. The &lt;code&gt;register_provider(omnizen)&lt;/code&gt; call drops the &lt;code&gt;ProviderProfile&lt;/code&gt; into Hermes's in-memory registry. From Hermes's point of view, the provider now exists; nothing more is needed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The user runs &lt;code&gt;hermes model&lt;/code&gt;&lt;/strong&gt;, picks the provider from the menu, and Hermes stores their choice.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The user runs &lt;code&gt;hermes chat&lt;/code&gt;&lt;/strong&gt; — or invokes a tool, or kicks off a multi-step plan, or hops to another agent through the Agent Communication Protocol. Hermes builds a standard OpenAI chat-completions request, reads &lt;code&gt;OMNIZEN_API_KEY&lt;/code&gt; from the env, and POSTs to &lt;code&gt;base_url&lt;/code&gt;. The gateway answers in OpenAI's SSE envelope. Hermes's existing parser handles the stream and the tool-call frames. None of that code knows the difference between Omnizen and OpenAI proper — the wire format is identical, so the same call path serves both.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The reason this works is the OpenAI Chat Completions API has become the lingua franca for "talk to an LLM." If the thing you're building a Hermes plugin for already speaks OpenAI — and most modern gateways do — your job is &lt;em&gt;describe the gateway&lt;/em&gt;, not implement an adapter. The runtime does the rest.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the pattern matters (even if you skip the rest of the post)
&lt;/h2&gt;

&lt;p&gt;A few lessons that generalise beyond Hermes:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Treat aggregators as a single "provider" in your agent's mental model.&lt;/strong&gt; It keeps the pluggable-model interface clean — one wire format, one auth, one place to swap. Don't try to make your agent multiplex five providers itself if a gateway is already doing that work.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;OpenAI-compatible is the lingua franca, even when the backend isn't OpenAI.&lt;/strong&gt; If the thing you're calling speaks OpenAI on the wire, your agent doesn't need to know what's behind it — Anthropic, MiniMax, your home-grown Llama, doesn't matter.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Provider plugins should be declarative, not procedural.&lt;/strong&gt; Hermes gets this right: I described what the provider &lt;em&gt;is&lt;/em&gt;, I didn't write any code about what to &lt;em&gt;do&lt;/em&gt;. The runtime knows what to do because the wire format is fixed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You shouldn't have to maintain a fork to ship a vendor.&lt;/strong&gt; When your provider SDK is this small (&lt;code&gt;ProviderProfile&lt;/code&gt; + &lt;code&gt;register_provider&lt;/code&gt;), adding a new vendor is a PR-sized commit, not a months-long integration. Hermes ships fifteen or so of these out of the box — the cost is so low it might as well be free.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you're building anything that wants to be "model-agnostic," this is the seam to expose.&lt;/p&gt;

&lt;h2&gt;
  
  
  Gotchas
&lt;/h2&gt;

&lt;p&gt;The things you only learn by shipping one of these:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The &lt;code&gt;default_aux_model&lt;/code&gt; matters more than you think.&lt;/strong&gt; Hermes uses it for any internal call where the user didn't pick a model — planning probes, tool-description embeddings, name-it. If you set it to a heavyweight model, every interaction feels slow and twice as expensive &lt;em&gt;before the user has even said anything&lt;/em&gt;. Pick a cheap-fast model for the aux; let the user spend on the chat call. I default to &lt;code&gt;kimi-k2&lt;/code&gt; here because it's roughly the speed of a thought.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fallbacks are silent.&lt;/strong&gt; &lt;code&gt;fallback_models&lt;/code&gt; swap in automatically when the primary fails (rate limit, 5xx). Great — until you're debugging "why does my answer have a different vibe today?" and realise Hermes quietly fell back two models down the chain. Log the &lt;em&gt;actual&lt;/em&gt; responding model: the &lt;code&gt;usage.model&lt;/code&gt; field on the SSE stream tells you the truth; the picker only tells you the intent.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The plugin registers at import time.&lt;/strong&gt; Which means if the plugin module isn't on Hermes's discovery path, Hermes won't see it. Symptom: &lt;code&gt;hermes model&lt;/code&gt; doesn't list your provider. Cause: missing &lt;code&gt;__init__.py&lt;/code&gt; in a parent directory, wrong working directory at launch, or the plugin folder is in &lt;code&gt;~/Documents/cool-hermes-stuff&lt;/code&gt; and Python can't see it. Fix: install the plugin as a module so it's on &lt;code&gt;sys.path&lt;/code&gt;, don't just copy-paste it into a random folder and hope.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;OpenAI-compatible ≠ identical to OpenAI.&lt;/strong&gt; Most gateways disagree with the OpenAI SDK on at least one streaming-chunk shape — usually the final &lt;code&gt;usage&lt;/code&gt; frame, sometimes the role on the first delta. Hermes is forgiving here, but if you build your own provider plugin and watch your assistant's last token vanish into thin air, this is where to start looking. Send one real chat through and watch the stream end-to-end before you ship.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;References&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Hermes Agent: &lt;a href="https://github.com/NousResearch/hermes-agent" rel="noopener noreferrer"&gt;&lt;code&gt;NousResearch/hermes-agent&lt;/code&gt;&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;The worked example plugin: &lt;a href="https://github.com/anchorblock/hermes-agent" rel="noopener noreferrer"&gt;&lt;code&gt;anchorblock/hermes-agent&lt;/code&gt;&lt;/a&gt; · &lt;code&gt;plugins/model-providers/omnizen/&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Omnizen gateway: &lt;a href="https://omnizen.ai" rel="noopener noreferrer"&gt;&lt;code&gt;omnizen.ai&lt;/code&gt;&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;HermsexOmnizen: &lt;a href="https://omnizen.ai/download" rel="noopener noreferrer"&gt;&lt;code&gt;hermes-omnizen&lt;/code&gt;&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;The gateway-side of this — how the Omnizen API actually fans one virtual key out to multiple model brands behind the curtain — is a separate post for another day. For this tutorial, all you need to know is it speaks OpenAI on the wire. The pattern works the same for any gateway that does.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>hermesagentchallenge</category>
      <category>devchallenge</category>
      <category>agents</category>
    </item>
    <item>
      <title>Hijacking OpenClaw with Claude</title>
      <dc:creator>ShatilKhan</dc:creator>
      <pubDate>Sat, 25 Apr 2026 21:55:41 +0000</pubDate>
      <link>https://dev.to/siren/hijacking-openclaw-with-claude-49dg</link>
      <guid>https://dev.to/siren/hijacking-openclaw-with-claude-49dg</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/openclaw-2026-04-16"&gt;OpenClaw Writing Challenge&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  ClawCon Michigan
&lt;/h2&gt;

&lt;h2&gt;
  
  
  Prelude
&lt;/h2&gt;

&lt;p&gt;My boss has been nagging me about "being more verbal" and "showing proof of work" and here I was pushing code when no one was seeing it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp0f5g1b6sb4hwvnaotwl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp0f5g1b6sb4hwvnaotwl.png" alt="gigachadvscrawny" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So I decided to try out OpenClaw for as our product manager (our human product manager ran away to Thailand, it's True!)&lt;/p&gt;

&lt;p&gt;I have no idea how to set it up or how it works. I had only seen tiktoks saying both good and bad stuff. And a Fireship video I saw weeks ago. That's pretty much my knowledge on this crab.&lt;br&gt;
So today we make it folks!&lt;br&gt;
Our Clawfficer!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flpjnmyh09klfyu1wfat1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flpjnmyh09klfyu1wfat1.png" alt=" " width="755" height="572"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  The Problem: We Had Claude Code, But Not Claude
&lt;/h2&gt;

&lt;p&gt;Here's the thing. My company gave us access to &lt;strong&gt;Claude Code&lt;/strong&gt; (the CLI tool) through our dev environments. But they didn't give us &lt;strong&gt;Claude web accounts&lt;/strong&gt; or API keys. No &lt;code&gt;console.anthropic.com&lt;/code&gt;. No OAuth flow. No nothing.&lt;/p&gt;

&lt;p&gt;So I'm sitting there with this powerful CLI tool installed, but I can't even log in the "normal" way because that requires a web account.&lt;/p&gt;

&lt;p&gt;Sound familiar? If your office IT is anything like mine, you probably have access to tools but not the accounts that make them "officially" work.&lt;/p&gt;

&lt;p&gt;Time to mad-scientist our way through this.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F67uovgarcm1hafigxeh3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F67uovgarcm1hafigxeh3.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  What even is OpenClaw?
&lt;/h2&gt;

&lt;p&gt;If you've come this far to read my post I'm assuming you know what &lt;a href="https://openclaw.ai/" rel="noopener noreferrer"&gt;OpenClaw&lt;/a&gt; is ¯_(ツ)&lt;em&gt;/¯&lt;br&gt;
I mean it's not like it has the largest growing repo in history ¯_(ツ)&lt;/em&gt;/¯&lt;/p&gt;

&lt;p&gt;But here's the catch: OpenClaw needs Claude(or any other AI model) to actually &lt;em&gt;do&lt;/em&gt; the thinking. And Claude usually needs an API key or web OAuth to work.&lt;/p&gt;

&lt;p&gt;Except... we already have Claude Code installed. And Claude Code has a trick.&lt;/p&gt;
&lt;h2&gt;
  
  
  The Symlink Hack
&lt;/h2&gt;

&lt;p&gt;When you install Claude Code globally:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; @anthropic-ai/claude-code
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It creates a &lt;code&gt;claude&lt;/code&gt; binary in your PATH. But most people don't realize, this binary carries its OWN AUTHENTICATION!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyeyb43xef49nw70crg7r.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyeyb43xef49nw70crg7r.gif" alt=" " width="498" height="330"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When you run &lt;code&gt;claude /login&lt;/code&gt; (which we &lt;em&gt;could&lt;/em&gt; do because we had the CLI but we can't because boss removed the email account  ┐( ˘_˘)┌ ), it opens a browser and does OAuth with your Claude Pro/Max subscription. It then stores a refresh token at:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;~/.claude/.credentials.json&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now here's the beautiful part: &lt;strong&gt;Claude Code can run in headless mode&lt;/strong&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Summarize these commits"&lt;/span&gt; | claude &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="nt"&gt;--output-format&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;text
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No API key. No &lt;code&gt;ANTHROPIC_API_KEY&lt;/code&gt; env var. No web console. Just the OAuth token that Claude Code manages itself, auto-refreshing forever.&lt;/p&gt;

&lt;p&gt;We basically &lt;strong&gt;hijacked&lt;/strong&gt; the Claude Code CLI's authentication mechanism and piped it into our OpenClaw workflow. The CLI becomes our "API layer" without us ever touching an API key.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fftciwhgxaegfea9cq85p.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fftciwhgxaegfea9cq85p.gif" alt=" " width="498" height="308"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is how Claude Code's OAuth flows into our OpenClaw setup without ever exposing an API key&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1p72ibopitioppxh5u83.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1p72ibopitioppxh5u83.png" alt="System Architecture for OpenClaw with Clause Code CLI" width="800" height="333"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But of course we ain't done yet!&lt;/p&gt;

&lt;h2&gt;
  
  
  LOOP!
&lt;/h2&gt;

&lt;p&gt;We have an agent, we have a runtime.&lt;br&gt;
So we still gotta make it survive restarts "( – ⌓ – )&lt;/p&gt;

&lt;p&gt;So we wrap it as a systemd user service. Voila! now it starts on boot and restarts if crashes&lt;/p&gt;

&lt;p&gt;We override &lt;code&gt;PATH&lt;/code&gt; in the systemd unit so it can find the &lt;code&gt;claude&lt;/code&gt; symlink.&lt;br&gt;
Without that PATH override, systemd's stripped-down environment can't find Claude Code, and the whole digest flow breaks. It's a one-line fix that took us an embarrassing amount of time to figure out.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjvvhs1yrjm6wx5gj4vct.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjvvhs1yrjm6wx5gj4vct.png" alt="openclaw as a systemd service" width="798" height="85"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Meet the Clawfficer
&lt;/h2&gt;

&lt;p&gt;So what does our "mad science" actually &lt;em&gt;do&lt;/em&gt;?&lt;/p&gt;

&lt;p&gt;We built a Discord bot that acts as our product manager. We call it the &lt;strong&gt;Clawfficer&lt;/strong&gt;.&lt;br&gt;
If you add clawfficer to your server channel , he will summarize daily and weekly github updates in Natural language.. SO not just generic auto generated bullet points, you could ask it "Hey what's the update on this project?" or "Hey we should add this to the roadmap and assign Shatil" or "I found this issue , can you assign Shatil to get a look at it?" , clawfficer will do his due diligence and assign the respective dev AND tag the message url in the github issue!&lt;/p&gt;

&lt;p&gt;Here's some photos we took of Clawfficer in Action!&lt;/p&gt;

&lt;p&gt;Here I ask Clawfficer to assign Mahim(my college) to an issue&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy87v9758z7b0ue6lwuay.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy87v9758z7b0ue6lwuay.jpeg" alt="assign to issue" width="800" height="535"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here we see the issue was created and assigned with the discord message refrence&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5mdcpya87ne1i4pvnej5.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5mdcpya87ne1i4pvnej5.jpeg" alt="issue with discord message" width="800" height="535"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It's shown that the issue was created by me because I used my own github PAT(personal access token) for the repo access.&lt;/p&gt;

&lt;p&gt;Here we see Clawfficer in his natural environment sharing summaries&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F24xpqxak7f4uoq79eeiq.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F24xpqxak7f4uoq79eeiq.jpeg" alt="daily summary" width="800" height="525"&gt;&lt;/a&gt;&lt;br&gt;
Every day at 9 AM and every Sunday at 10 AM, the bot pulls activity from all 8 of our repos across &lt;strong&gt;all branches&lt;/strong&gt; (not just main , we have more repos but just included the most active ones for testing on the agent).&lt;/p&gt;

&lt;p&gt;Then it pipes all that data into:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;claude &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="nt"&gt;--output-format&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;text
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And Claude spits back a human-readable prose summary. Not bullet points. Not raw JSON. Actual sentences like:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Medihelp saw 12 commits across 3 branches this week, mostly schema migrations by Mahim. The eyecraft landing page got its first PR review."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The bot posts this to &lt;code&gt;#roadmap&lt;/code&gt;. My boss sees it. I stop getting nagged about "proof of work."&lt;/p&gt;

&lt;p&gt;Everyone wins.&lt;/p&gt;

&lt;p&gt;The Claude Code symlink approach means you can:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Use your existing subscription&lt;/strong&gt; (Claude Pro/Max)- same billing bucket as your interactive usage&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Run headlessly&lt;/strong&gt; in scripts, cron jobs, and bots&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Never handle an API key&lt;/strong&gt; - OAuth tokens live in &lt;code&gt;~/.claude/&lt;/code&gt; and auto-refresh&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deploy anywhere&lt;/strong&gt; that has Node.js and your user context&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;It's not "officially supported" as an API replacement. But it works. And for internal tools that need to "just work" without procurement battles, that's gold.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F71mvqidypn3sr0vakg5j.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F71mvqidypn3sr0vakg5j.png" alt="OpenClaw Harness with Claude Code AI Engine in Node Runtime" width="800" height="345"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Stuff I Spent way too much time on
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Branch Coverage Matters
&lt;/h3&gt;

&lt;p&gt;GitHub's &lt;code&gt;/commits&lt;/code&gt; endpoint defaults to the default branch. If your team works on feature branches (and they should), your digest will look empty even when people are pushing code.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; Iterate all branches, dedupe by commit SHA. Obvious in hindsight, painful in production.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. The OAuth Token is Everything
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;~/.claude/.credentials.json&lt;/code&gt; file is the single point of auth failure. If it expires or gets corrupted, &lt;code&gt;claude -p&lt;/code&gt; exits with code 1 and your digest falls back to bullet points.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; Run &lt;code&gt;claude&lt;/code&gt; interactively once to re-auth. The CLI handles refresh tokens automatically after that.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. systemd PATH is Not Your Shell PATH
&lt;/h3&gt;

&lt;p&gt;Your interactive shell has &lt;code&gt;claude&lt;/code&gt; in PATH. systemd doesn't. Explicitly set &lt;code&gt;Environment=PATH=...&lt;/code&gt; in your unit file or the spawn fails silently.&lt;/p&gt;

&lt;p&gt;The Clawfficer is intentionally minimal. I deliberately didn't build a lot of other regular features. (I mean I only built it yesterday in 8 hours without any prior knowledge of Claw so ¯_(ツ)_/¯ )&lt;br&gt;
But the foundation is there. And more importantly, the &lt;strong&gt;authentication pattern&lt;/strong&gt; is there.Claude Code's CLI isn't just an interactive coding assistant , it's a headless LLM engine that happens to already be authenticated.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;a href="https://github.com/ShatilKhan" rel="noopener noreferrer"&gt;Shatil Khan&lt;/a&gt; builds things at Anchorblock and argues with crabs on the internet. Follow for more questionable engineering decisions that somehow work.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8gc8wjm2n6pve2al0zvl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8gc8wjm2n6pve2al0zvl.png" alt="Yeah Baby!" width="320" height="276"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>openclawchallenge</category>
      <category>claude</category>
      <category>ai</category>
    </item>
    <item>
      <title>Giving Your Accounting App a Brain: NestJS "MCP"</title>
      <dc:creator>ShatilKhan</dc:creator>
      <pubDate>Thu, 23 Apr 2026 23:23:42 +0000</pubDate>
      <link>https://dev.to/siren/giving-your-accounting-app-a-brain-nestjs-mcp-4nfc</link>
      <guid>https://dev.to/siren/giving-your-accounting-app-a-brain-nestjs-mcp-4nfc</guid>
      <description>&lt;h2&gt;
  
  
  Recap
&lt;/h2&gt;

&lt;p&gt;Let's be real: in the &lt;a href="https://dev.to/siren/building-my-first-mcp-server-using-swagger-openapitools-38kg"&gt;last blog&lt;/a&gt; we were running a local MCP server on my pc, wiring it up to GitHub Copilot like mad scientists. It was awesome. But it was a lab experiment. The "brain" was tethered to my machine.&lt;/p&gt;

&lt;p&gt;So... what if we built the brain into the app itself?&lt;/p&gt;

&lt;h2&gt;
  
  
  The System
&lt;/h2&gt;

&lt;p&gt;So here's the setup. We have three players in this game:&lt;/p&gt;

&lt;p&gt;The Web App: React, Vite, Redux Toolkit. The chat lives at /magic-bookkeeping.&lt;br&gt;
The Backend: NestJS, PostgreSQL, Redis. This is where the real work happens.&lt;br&gt;
The AI Service: Claude Haiku 3.5 via Requesty.AI, sitting in between.&lt;/p&gt;

&lt;p&gt;User be like: Yo what items we got?&lt;br&gt;
Agent: Calls the itemAll tool to check the item inventory and then &lt;em&gt;cool pause&lt;/em&gt; RENDERS a Full UI Component with item details! Not texts! &lt;br&gt;
Also works for like Bengali and Voice inputs by the way&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbi0h822ytz01di06vfhx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbi0h822ytz01di06vfhx.png" alt="system" width="800" height="251"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The frontend Contract:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0aitxyfgfqv8ziigb75j.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0aitxyfgfqv8ziigb75j.png" alt="frontend connection to mcp service" width="800" height="383"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is what's keeping them connected.&lt;/p&gt;

&lt;h2&gt;
  
  
  The MCP Layer: But Not Actually MCP?
&lt;/h2&gt;

&lt;p&gt;Plot twist: we call it MCP, but it's not really the Model Context Protocol. There's no stdio. No SSE. No @modelcontextprotocol/sdk. No JSON-RPC. We basically built our own tool-calling façade that looks like OpenAI's function spec so Claude knows what to do with it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc1jd6zd0k5zfgfs7l5zx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc1jd6zd0k5zfgfs7l5zx.png" alt="mcp" width="800" height="253"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Since this was already a heavy enough ERP system with a lot of code that was written before the AI era,adapting it to a separate mcp server wouldn't be that easy, plus it would also mean we would have to maintain another server.&lt;/p&gt;

&lt;p&gt;The Controller (only two endpoints!):&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4h0133lbi0j9nc1q3uwy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4h0133lbi0j9nc1q3uwy.png" alt="mcp tool controller" width="800" height="678"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;when a user first requests any info &lt;code&gt;list_tool&lt;/code&gt; will list all available tools for the ai model&lt;br&gt;
&lt;code&gt;call_tool&lt;/code&gt; is where the magic happens, we pass in a system prompt for the ai service so that it knows how to select the tools for any specific operations or which tools to call for let's say getting items or adding new item to inventory. &lt;/p&gt;

&lt;p&gt;the tool dto:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp9hlsglcpq7upavmn25p.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp9hlsglcpq7upavmn25p.png" alt="call tool dto" width="799" height="498"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;why "any" ? &lt;br&gt;
Because every tool has a different structure.&lt;br&gt;
&lt;code&gt;arguments: any&lt;/code&gt; is probably the most honest TypeScript type ever written&lt;/p&gt;

&lt;p&gt;And here is how the tools are dispatched:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fix5md576njgwmfjfuym4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fix5md576njgwmfjfuym4.png" alt="mcp tool dispatch" width="800" height="530"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;tools.json&lt;/code&gt; is read from disk on every request, not cached. The switch pattern means adding a tool requires editing both the JSON file and this service. (cut me some slack , it was my first time building stuff like this :))&lt;/p&gt;

&lt;h3&gt;
  
  
  Anatomy of a mcp tool
&lt;/h3&gt;

&lt;p&gt;there's two types of tools for this project&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Data Tools&lt;/li&gt;
&lt;li&gt;Component Tools
data tools handles the actual service calls and response payloads while component tool is just used used for generative UI which we'll get to later.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;here's a data tool declared inside tools.json:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fht0k7e6uwkt49u9jdb5f.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fht0k7e6uwkt49u9jdb5f.png" alt="mcp tool declaration" width="800" height="495"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;this is how we handle the tool via our mcp service:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1nb8mslbc1j3yqhp5nog.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1nb8mslbc1j3yqhp5nog.png" alt="mcp data tool handler" width="800" height="435"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Result is the raw entity returned by the domain service.&lt;/p&gt;

&lt;p&gt;Now compare it to the component tool&lt;br&gt;
a create item tool in tools.json:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F830p19ud7o4lsbwrwpbf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F830p19ud7o4lsbwrwpbf.png" alt="mcp create item tool" width="799" height="383"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;handler in mcp.service.ts:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Faeo0988ede3e8l55d12k.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Faeo0988ede3e8l55d12k.png" alt="mcp service typescript" width="800" height="580"&gt;&lt;/a&gt;&lt;br&gt;
Instead of touching the database the backend returns a "render this component" envelope. The frontend's registry maps AiItemForm to the actual React component.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Cool Part: Generative UI
&lt;/h2&gt;

&lt;p&gt;Okay so the AI can fetch data. Great. But here's where it gets spicy. What if instead of returning boring JSON, the backend says: "Hey frontend, render this form"?&lt;/p&gt;

&lt;p&gt;But we can't just ask it to use html templates , no? The forms need to dynamic, the same form the user uses to enter item details or add a new entry to the inventory should also be rendered here basically. But how did I achieve this? &lt;br&gt;
Quite simply I wrap those already existing form components as separate render-able components , put them in a component registry and then it's just a matter of prompting the model correctly so it renders the right form based on user intent.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flijawj0l0z8mcn8nwg0g.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flijawj0l0z8mcn8nwg0g.png" alt="generative ui flow" width="800" height="113"&gt;&lt;/a&gt;&lt;br&gt;
The response type that makes this possible:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fixgvcmqjxxgbjjysx56k.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fixgvcmqjxxgbjjysx56k.png" alt="generative ui tool response schema" width="800" height="448"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When &lt;code&gt;render_type === 'component'&lt;/code&gt;, the frontend looks up a component by name in a static registry and renders it inline in the chat. No LLM-generated markup, no code-eval; the model only selects which component and fills its props from the Tool Registry:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4qk721dyo0effpfkhhpe.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4qk721dyo0effpfkhhpe.png" alt="tool registry" width="757" height="391"&gt;&lt;/a&gt;&lt;br&gt;
Adding a new generative component requires editing this file. There is no runtime discovery ,the server can only refer to components the frontend has already imported.&lt;/p&gt;

&lt;p&gt;Finally it all comes together inside the Chat!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd6szyjt7n27l0hhsps2d.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd6szyjt7n27l0hhsps2d.png" alt="generative chat ui" width="800" height="655"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If component_name isn't in the registry, the branch silently falls through to  , no error, no fallback UI.Callbacks are hard-wired for AiItemForm only.(Cause I'm lazy)&lt;br&gt;
The form-vs-list distinction is a string-includes check on the name.&lt;/p&gt;

&lt;p&gt;And lastly here's how a user's message would flow through the system and repond with generative components:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhogw5it6i1pjrul1rvxk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhogw5it6i1pjrul1rvxk.png" alt="generative-ui" width="800" height="502"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;it's 5 am in my place now so that's it for now, you can checkout the other blogs in the series or don't I don't care, this was a company project so I couldn't share the full code :)&lt;/p&gt;

&lt;p&gt;Sayonara people&lt;/p&gt;

&lt;p&gt;aurthor's note: maybe I'll add in a video later on of the working project, it's obviously not something you could just put in prod but something that at least works, some parts need more work, but if you've Actually read this far! Thanks! &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvw4w6wdktnxzvaxr1jgv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvw4w6wdktnxzvaxr1jgv.png" alt="pickachu waving sayonara" width="278" height="182"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>backend</category>
      <category>llm</category>
      <category>mcp</category>
    </item>
    <item>
      <title>Moneyshot#1: Month 1 till I make $1k MRR</title>
      <dc:creator>ShatilKhan</dc:creator>
      <pubDate>Sat, 21 Feb 2026 20:11:23 +0000</pubDate>
      <link>https://dev.to/siren/moneyshot1-month-1-till-i-make-1k-mrr-2o4a</link>
      <guid>https://dev.to/siren/moneyshot1-month-1-till-i-make-1k-mrr-2o4a</guid>
      <description>&lt;p&gt;Hello again Internet!&lt;br&gt;
I am writing  after a long while and this blog is a bit different from my engineering blogs. This is more of a practical blog of some goals I am going to set for myself, which I had previously done years ago for coding or learning to code &amp;amp; I guess and it has paid off and I wanted to do that again but with a different goal in mind.&lt;/p&gt;

&lt;p&gt;I made this video back in 2023 probably and around the end of 2023 or 2022 I think where I kind of publicly posted that I'd code every day&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/kakDtPbE7gg"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;which I mean I didn't code every single day but you know I'll share the GitHub repo or GitHub profile after I had posted that video,&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcqj4xr3e01u7lg3o30kz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcqj4xr3e01u7lg3o30kz.png" alt=" " width="800" height="218"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;and I actually committed to it, and I actually started doing some projects and contributing to some open source projects. And I did get to work with pretty amazing people. I did get to work on a project for AWS Open Source, which obviously none of them sprouted into success, or none of them sprouted into major things. But what happened was after that the following year I got my first job and then I have been working professionally with startups and agencies since then So yeah it didn't make sense at that moment when I actually posted the video that I code every day but it now makes sense that I did commit to something and then I started doing it. And I'd done that previously years ago as well when I first started to code. and when I first started I used to do digital art as well so when I first did digital art ,there's thing called Inktoberfest where you would like draw something for the entire month of October and I also did Hacktoberfest where you would code or contribute some code to some repositories (open source repositories) for the entire month of October and these things didn't mean that you would probably be successful at any specific coding area or any specific skilled area but the goal was to be consecutive and consecutively keep doing it without worrying about the results.&lt;/p&gt;

&lt;p&gt;After I started working professionally as a developer since the last  two years, the software industry as a whole has shifted drastically that I've experienced. I do three part-time jobs. One of them is an office job. And I think I'm doing more work than any developer would have done three or four years ago. that's mainly due to AI and AI has enabled people to do more but it has been demanding from people more as well. The one thing that I have looked and I have experienced is that the overall outlook of everyone and every industrial mechanism has changed from improving or focused on results to just making money So the key mechanism of the AI industry or the software industry as a whole has been to make money obviously. But there was a video from GitHub around 5 or 6 years ago, you can watch it on YouTube, it was basically an advertisement for GitHub, but not a typical advertisement. It wasn't advertised in a way like GitHub as a product, but rather it advertised code as a means to connect different parts of society. So, the video was mainly about a girl making a prosthetics arm for her brother, and in another part of the world, somebody else used that code to build something else that they needed.&lt;br&gt;
With the current industrial standards, I feel like that wave has faded. Like actually developing something for betterment, actually trying to, I mean people are still developing things to help people, but most of those things are more focused on profitability. So, they're helping people, but they're also focused on actually making money. And even though I say that the outlook has changed, I don't mean that in a negative way, but moreover it has become much easier for a single person, like for example, Open Claw or Peter Steinberger, people like those to make money or make profitable businesses out of their skillsets. And obviously not everyone can do it, you need a lot of expertise and global experience, but there's also people who have been building, indie hackers who have been developing for years. And I wanna build shitty products that's also one goal is to build a lot of shitty products and have a lot of fucking fun doing it for people, for myself, but mostly to enjoy the process that I have enjoyed before just building things not just software but anything.&lt;/p&gt;

&lt;p&gt;If money is the main goal or like the standards of the industry have changed to making money or making it easier for people to run businesses then let's do that. Let's just go for that and make as much as possible so that we can actually try it out. I've already said that I tried out everything. I tried out art, doing art and doing programming and going all in into it because it was so interesting. And right now this trend of building stuff is very intriguing. So that's why I feel like I should challenge myself, but this time with a specific number in mind to get to that goal.&lt;/p&gt;

&lt;p&gt;This goal is much different from the previous goals I set for myself. The primary one was to just learn things. Learning to code, learning to draw, learning anything basically. And the secondary goal was to earn something, earn some money or some monetary means by doing what I love or doing what I'm good at, which was the skills I had previously learned from my first goal.&lt;/p&gt;

&lt;p&gt;This time I am gonna set a specific amount or specific target in mind for the goal which is the 1k MRR which means that it's not just like a specific amount per month but more of it needs to be be set in some rules and those rules I do need to follow through. So the first and foremost would be that it obviously needs to be a MRR because it needs to be a monthly recurring review. It's not just I need to make that amount once and then the goal is set. It wouldn't work like that.It would need to be a sustainable MRR or a recurring revenue per month. That is why I set the goal with the $1000 in mind.&lt;/p&gt;

&lt;p&gt;These are the rules I’m setting for myself. The product or the way I’m going to reach that monetary target, will be by my own means. So it cannot be any part-time job, any full-time job, or something I’m doing for my employer. I am going to be the owner of the product.&lt;/p&gt;

&lt;p&gt;I’m also keeping the field a bit broad, and maybe I’ll broaden it later. Primarily because I don’t want to focus only on SaaS products. I can do more than SaaS, and I don’t have to confine myself to that. I could do research projects. I know how to program a microcontroller or a Raspberry Pi, I can figure it out. I’ve worked with Arduino and a bit of IoT. I’ve done basic UI/UX design and graphic design. So I can use any of those skills.&lt;/p&gt;

&lt;p&gt;That’s one rule. I’d say that’s the second rule—the first is that it needs to be my own thing, not from an employer. The second is that I can utilize any of the skills I have to produce that amount. The third is using any tools at my disposal. That means not banning AI, but using it efficiently, not as much as possible, because that could ruin things. I try to avoid using AI for everything, and instead use it where it makes sense, where it’s better than doing it myself. For example, I do some QA tasks with AI using specific tools, cloud code, and other workflows.&lt;/p&gt;

&lt;p&gt;Another rule is that working with clients is allowed, as long as I’m the one building the projects. I’ll still own them, even if I’m supplying a service. So it could be like a SaaS, but even if it’s not, there's IoT-based projects or other things I can produce.&lt;/p&gt;

&lt;p&gt;The final step would be to broaden the horizon later on, maybe investing in something beyond software or hardware. I’m not sure yet, but it’s something I think I could explore.&lt;/p&gt;

&lt;p&gt;That’s the primary direction I’m setting for myself, and that’s what I’m going to follow. I’m not sure how far I’ll get, but I’m going to keep going toward this goal.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flb7h03qhll8st15vq1pr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flb7h03qhll8st15vq1pr.png" alt=" " width="800" height="336"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>devjournal</category>
      <category>saas</category>
      <category>sideprojects</category>
      <category>startup</category>
    </item>
    <item>
      <title>Check out my new blog on MCP!</title>
      <dc:creator>ShatilKhan</dc:creator>
      <pubDate>Wed, 20 Aug 2025 10:02:39 +0000</pubDate>
      <link>https://dev.to/siren/-2n5</link>
      <guid>https://dev.to/siren/-2n5</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/siren/building-my-first-mcp-server-using-swagger-openapitools-38kg" class="crayons-story__hidden-navigation-link"&gt;Building my first Local MCP server using Swagger &amp;amp; OpenAPITools&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/siren" class="crayons-avatar  crayons-avatar--l  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F558537%2F82f48ad4-591e-4a86-816a-10250ca198cb.jpg" alt="siren profile" class="crayons-avatar__image" width="800" height="1200"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/siren" class="crayons-story__secondary fw-medium m:hidden"&gt;
              ShatilKhan
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                ShatilKhan
                
              
              &lt;div id="story-author-preview-content-2552018" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/siren" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F558537%2F82f48ad4-591e-4a86-816a-10250ca198cb.jpg" class="crayons-avatar__image" alt="" width="800" height="1200"&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;ShatilKhan&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/siren/building-my-first-mcp-server-using-swagger-openapitools-38kg" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Aug 3 '25&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/siren/building-my-first-mcp-server-using-swagger-openapitools-38kg" id="article-link-2552018"&gt;
          Building my first Local MCP server using Swagger &amp;amp; OpenAPITools
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/mcp"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;mcp&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/openapi"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;openapi&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/softwareengineering"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;softwareengineering&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/tooling"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;tooling&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
            &lt;a href="https://dev.to/siren/building-my-first-mcp-server-using-swagger-openapitools-38kg#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              &lt;span class="hidden s:inline"&gt;Add&amp;nbsp;Comment&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            6 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


</description>
      <category>mcp</category>
      <category>openapi</category>
      <category>softwareengineering</category>
      <category>tooling</category>
    </item>
    <item>
      <title>Building my first Local MCP server using Swagger &amp; OpenAPITools</title>
      <dc:creator>ShatilKhan</dc:creator>
      <pubDate>Sun, 03 Aug 2025 14:30:00 +0000</pubDate>
      <link>https://dev.to/siren/building-my-first-mcp-server-using-swagger-openapitools-38kg</link>
      <guid>https://dev.to/siren/building-my-first-mcp-server-using-swagger-openapitools-38kg</guid>
      <description>&lt;p&gt;Let's be real: enterprise software can feel like a labyrinth. Our app at AnchorBlock, &lt;a href="https://anchorbooks.ai/its-magic" rel="noopener noreferrer"&gt;AnchorBooks.ai&lt;/a&gt;, is a beast with hundreds of features for finance and bookkeeping. It's powerful, but finding what you need can mean a dozen clicks through a maze of menus.&lt;/p&gt;

&lt;p&gt;So, I got this crazy idea: what if you could just talk to the app? What if you could say, "Hey, create an invoice for client X with these items," and it would just... happen?&lt;/p&gt;

&lt;p&gt;That's the dream, right? To give our app a brain. This is the story of how I took the first step, moving from a complex API to a simple chat prompt. In this first part of our two-part series, we're going full mad-scientist-in-the-lab. We'll build a local tool server using the Model Context Protocol (MCP) and hook it up to GitHub Copilot, proving we can control our entire backend with plain English.&lt;/p&gt;

&lt;p&gt;Let's get our hands dirty.&lt;/p&gt;

&lt;p&gt;Step 1: The Rosetta Stone for AI - Turning an API into Tools&lt;/p&gt;

&lt;p&gt;An AI model, as smart as it is, has no idea what a POST /api/item request is. You can't just give it your backend URL and hope for the best. It needs a manual, an instruction book that clearly explains what's possible.&lt;/p&gt;

&lt;p&gt;For us, that manual was our Swagger (OpenAPI) documentation. With over 249 endpoints, our NestJS backend was well-documented, which turned out to be the secret ingredient. Each endpoint definition, with its inputs and outputs, was a potential "skill" we could teach our AI.&lt;/p&gt;

&lt;p&gt;But was I going to manually write a wrapper script for all 249 endpoints? Absolutely not. My laziness (I call it efficiency) led me to a game-changing tool: &lt;a href="https://openapitools.com/" rel="noopener noreferrer"&gt;OpenAPITools&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Think of OpenAPITools as a magic translator. You feed it your swagger.json file, and it spits out a ready-to-use MCP server structure, complete with Python scripts for every single API endpoint. It's like giving the AI a universal remote for our app, and OpenAPITools just programmed all the buttons for us.&lt;/p&gt;

&lt;p&gt;Here’s a quick look at that workflow:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqakpfjyx1gotadypzpt0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqakpfjyx1gotadypzpt0.png" alt="mcp flow" width="694" height="1317"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Step 2: A Peek Under the Hood: The Anatomy of a Single Tool&lt;/p&gt;

&lt;p&gt;Let's zoom in on one example to see how this actually works. Say we want to create a new item in our inventory.&lt;/p&gt;

&lt;p&gt;In our NestJS backend, we have a CreateItemDto that defines exactly what data is needed. It's decorated with @ApiProperty for Swagger, which is key.&lt;/p&gt;

&lt;p&gt;The Source of Truth: dto/create-item.dto.ts&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz9oqyox9c7vf8425bx62.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz9oqyox9c7vf8425bx62.png" alt="item dto" width="800" height="541"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When OpenAPITools reads this, it generates two crucial files:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The AI's Cheat Sheet: tools.json (Snippet)
This file is the manifest. It tells the AI, "Hey, there's a tool called ItemCreator. It needs a name, a type, a price, and so on." The schema here is a direct translation of our DTO.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo90e5aut21y8uqj18r57.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo90e5aut21y8uqj18r57.png" alt="toolsjson" width="800" height="837"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The Worker Bee: ItemCreator-main.py
This Python script is the muscle. Its job is dead simple: get the JSON input from the AI, build a real HTTP request with the right headers (including our JWT token for auth!), and fire it off to our actual NestJS backend.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Extract parameters from the AI's request
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbovozh0bw3r55bzlun4x.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbovozh0bw3r55bzlun4x.png" alt="boilerplate" width="800" height="469"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Build the payload for our NestJS API
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6f3574ay53fsdmnmcm96.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6f3574ay53fsdmnmcm96.png" alt="payload" width="800" height="546"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Get our secret JWT token from the environment
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4rjd6uhaamsqkb3zam33.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4rjd6uhaamsqkb3zam33.png" alt=" " width="800" height="465"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Make the actual API call!
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1fse2ys57dfwaaqm3drp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1fse2ys57dfwaaqm3drp.png" alt="api call" width="798" height="180"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Send the result back to the AI
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft659uvpmbblti8wery2a.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft659uvpmbblti8wery2a.png" alt=" " width="799" height="375"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And just like that, every single one of our 249 endpoints had its own little Python worker, ready and waiting for orders.&lt;/p&gt;

&lt;p&gt;Step 3: Firing Up the Local Brain&lt;/p&gt;

&lt;p&gt;With our tools ready, we needed a "switchboard operator" to direct traffic from the AI agent to the correct Python script. The generated project includes a lightweight Node.js server that does exactly this. For local testing, it runs in stdio mode, communicating silently in the background.&lt;/p&gt;

&lt;p&gt;The stdio-server.js file orchestrates everything, but the gist is simple:&lt;/p&gt;

&lt;p&gt;It starts up and reads our tools.json to know what tools are available.&lt;/p&gt;

&lt;p&gt;It listens for two main commands from an agent: ListTools (what can you do?) and CallTool (do this thing!).&lt;/p&gt;

&lt;p&gt;When CallTool comes in, it finds the right Python script, passes along the inputs, and pipes the result back to the agent.&lt;/p&gt;

&lt;p&gt;I just pop open my terminal, run node index.js, and my local AI "brain" is online.&lt;/p&gt;

&lt;p&gt;Step 4: The "It's Alive!" Moment: Hooking up GitHub Copilot&lt;/p&gt;

&lt;p&gt;This is where it gets really cool. We have a server running on our machine that knows how to use our app. Now, let's connect an AI to it. GitHub Copilot's agent mode is perfect for this.&lt;/p&gt;

&lt;p&gt;Here’s how you can do it too:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Light Up the Server
In your terminal, navigate to your MCP project folder and run the server.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;node index.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It will just sit there, waiting. That's what it's supposed to do!&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Tweak Your VS Code Settings&lt;br&gt;
Open your VS Code User settings.json file. The easiest way is to press Ctrl+Shift+P (or Cmd+Shift+P) and type Preferences: Open User Settings (JSON).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Tell Copilot About Your Server&lt;br&gt;
Add this snippet to your settings.json. This tells Copilot, "Hey, there's a tool server available. If I say @anchorbooks, you should run this command to talk to it."&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  // ...your other settings...
  "github.copilot.mcp.enabled": true,
  "github.copilot.mcp.servers": {
    "anchorbooks": { // Call it whatever you want
      "command": "node",
      "args": [
        // IMPORTANT: Use the FULL, absolute path to your index.js file
        "G:\\Office\\anchorbooks\\item-mcp-tool\\index.js"
      ]
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pro Tip: Make sure the path is correct! This trips up a lot of people.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Let the Magic Happen
Restart VS Code to make sure the settings take effect. Now, open the GitHub Copilot chat panel and type @—you should see your server's name (@anchorbooks) pop up!&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I sent my first command:&lt;/p&gt;

&lt;p&gt;@anchorbooks create a new item of type goods named "Premium Arabica Beans" with a selling price of 25 and purchase price of 15. Use unit "kg", sales account 1, purchase account 2, for organization 1.&lt;/p&gt;

&lt;p&gt;I held my breath. A few seconds later... success!&lt;/p&gt;

&lt;p&gt;Copilot parsed my sentence, correctly identified the ItemCreator tool, mapped all the parameters, and sent the request to my local Node.js server. The server executed the Python script, which made a real, authenticated API call to my backend. A new item was created in my database, and the JSON response appeared right in my chat window.&lt;/p&gt;

&lt;p&gt;(Placeholder: Screenshot of the Copilot chat with the prompt and the successful tool execution and JSON response)&lt;/p&gt;

&lt;p&gt;It felt like magic. I could now manage invoices, update bills, transfer stock—perform over 33 complex backend operations—just by chatting with Copilot.&lt;/p&gt;

&lt;p&gt;What's Next?&lt;/p&gt;

&lt;p&gt;We've built a powerful local playground. We proved that we can bridge the gap between human language and a complex API. But let's be honest, this is a lab experiment. The "brain" is tethered to my machine and depends on an external tool like VS Code.&lt;/p&gt;

&lt;p&gt;This setup is awesome, but it's not a product.&lt;/p&gt;

&lt;p&gt;In Part 2 of this series, we're cutting the cord. I’ll show you how we took this successful experiment and turned it into a real, integrated feature by:&lt;/p&gt;

&lt;p&gt;Ditching the local server and building the MCP logic directly into our NestJS backend.&lt;/p&gt;

&lt;p&gt;Creating our very own chat interface right inside the AnchorBooks.ai app.&lt;/p&gt;

&lt;p&gt;Plugging into powerful models like Claude 3.5 for smarter, faster tool use.&lt;/p&gt;

&lt;p&gt;Stay tuned, because that's when we truly give our application a mind of its own.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Author's Note:&lt;/em&gt; No code repos shared here since it was a company project. I wanted to share my experience building stuff &amp;amp; will continue the story in the 2nd blog along with more code examples for better understanding. This workflows also works with complex operations like invoice creation or generating new customer and multi stage workflows as well which we'll discuss in future blogs.&lt;/p&gt;

</description>
      <category>mcp</category>
      <category>openapi</category>
      <category>softwareengineering</category>
      <category>tooling</category>
    </item>
    <item>
      <title>Stop Your Code From Having a Mid-Life Crisis. An Introduction to OOP.</title>
      <dc:creator>ShatilKhan</dc:creator>
      <pubDate>Sun, 27 Jul 2025 18:34:46 +0000</pubDate>
      <link>https://dev.to/siren/stop-your-code-from-having-a-mid-life-crisis-an-introduction-to-oop-28j7</link>
      <guid>https://dev.to/siren/stop-your-code-from-having-a-mid-life-crisis-an-introduction-to-oop-28j7</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/siren/engineering-of-small-things-5-oop-basics-in-typescript-2k1h" class="crayons-story__hidden-navigation-link"&gt;Engineering of Small Things #5: OOP Basics in TypeScript&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/siren" class="crayons-avatar  crayons-avatar--l  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F558537%2F82f48ad4-591e-4a86-816a-10250ca198cb.jpg" alt="siren profile" class="crayons-avatar__image" width="800" height="1200"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/siren" class="crayons-story__secondary fw-medium m:hidden"&gt;
              ShatilKhan
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                ShatilKhan
                
              
              &lt;div id="story-author-preview-content-2188485" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/siren" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F558537%2F82f48ad4-591e-4a86-816a-10250ca198cb.jpg" class="crayons-avatar__image" alt="" width="800" height="1200"&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;ShatilKhan&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/siren/engineering-of-small-things-5-oop-basics-in-typescript-2k1h" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Jul 27 '25&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/siren/engineering-of-small-things-5-oop-basics-in-typescript-2k1h" id="article-link-2188485"&gt;
          Engineering of Small Things #5: OOP Basics in TypeScript
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/typescript"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;typescript&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/beginners"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;beginners&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/tutorial"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;tutorial&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/oop"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;oop&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
            &lt;a href="https://dev.to/siren/engineering-of-small-things-5-oop-basics-in-typescript-2k1h#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              &lt;span class="hidden s:inline"&gt;Add&amp;nbsp;Comment&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            5 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


</description>
      <category>typescript</category>
      <category>beginners</category>
      <category>tutorial</category>
      <category>oop</category>
    </item>
    <item>
      <title>Engineering of Small Things #5: OOP Basics in TypeScript</title>
      <dc:creator>ShatilKhan</dc:creator>
      <pubDate>Sun, 27 Jul 2025 14:30:00 +0000</pubDate>
      <link>https://dev.to/siren/engineering-of-small-things-5-oop-basics-in-typescript-2k1h</link>
      <guid>https://dev.to/siren/engineering-of-small-things-5-oop-basics-in-typescript-2k1h</guid>
      <description>&lt;p&gt;Ahoy!&lt;br&gt;
Back after quite a while, I originally planned to release this blog &amp;amp; couple others a long time ago but I guess life gets in the way.&lt;/p&gt;

&lt;p&gt;Today we are going to look into some basic concepts of Object Oriented Programming.&lt;br&gt;
And this isn't meant to be any kind of high level overview. Just me sharing my learning.&lt;br&gt;
So! with that , let's begin!&lt;/p&gt;
&lt;h2&gt;
  
  
  What is Object Oriented Programming anyway?
&lt;/h2&gt;

&lt;p&gt;Object-oriented programming (OOP) is a programming paradigm based on the concept of "objects", which can contain data and code: data in the form of fields (often known as attributes or properties), and code, in the form of procedures (often known as methods). &lt;/p&gt;
&lt;h3&gt;
  
  
  The Six Pillars of OOP: A Family Story
&lt;/h3&gt;
&lt;h4&gt;
  
  
  1. Classes: The Blueprint of You
&lt;/h4&gt;

&lt;p&gt;First things first, what's a class?&lt;/p&gt;

&lt;p&gt;Think of a &lt;strong&gt;Class&lt;/strong&gt; as a blueprint. It's not a person, but it &lt;em&gt;describes&lt;/em&gt; what makes a person. For instance, a &lt;code&gt;Human&lt;/code&gt; class would be a blueprint defining that all humans have properties like a &lt;code&gt;name&lt;/code&gt;, &lt;code&gt;age&lt;/code&gt;, and &lt;code&gt;eyeColor&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkbguhuz3qnwlx5p5a853.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkbguhuz3qnwlx5p5a853.png" alt="Class" width="799" height="623"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h4&gt;
  
  
  2. Objects: You, the Instance
&lt;/h4&gt;

&lt;p&gt;So if a Class is the blueprint, what are you? You, my friend, are an Object.&lt;/p&gt;

&lt;p&gt;An object is a real, living, breathing instance created from a class. While the Human class is the idea, you are the implementation. You have a specific name, a specific age, and so on. In code, we create a 'new' object from our class.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxsnixzkqxbmgj8h4uh78.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxsnixzkqxbmgj8h4uh78.png" alt="Object" width="800" height="446"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A better visualization of the difference between Objects &amp;amp; Classes:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuml0nml9crwwzhbzb5fm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuml0nml9crwwzhbzb5fm.png" alt="Class &amp;amp; Object" width="533" height="347"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h4&gt;
  
  
  3. Encapsulation: Your Personal Bubble
&lt;/h4&gt;

&lt;p&gt;Encapsulation is about bundling your properties (data) and your methods (behaviors) together in that neat little Human object. It also means you can have secrets!&lt;/p&gt;

&lt;p&gt;Some things about you are public, like your name. Anyone can ask for it. But some things are private, like your deepest, darkest thoughts. You keep those encapsulated. In OOP, this prevents outsiders from messing with the internal state of an object.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fietq0wado2zexw0nbkdl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fietq0wado2zexw0nbkdl.png" alt="Encapsulation" width="799" height="588"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Onward we go!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffaofs2m4uhfakeds5njc.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffaofs2m4uhfakeds5njc.gif" alt="meme" width="360" height="302"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h4&gt;
  
  
  4. Abstraction: Don't Overthink it!
&lt;/h4&gt;

&lt;p&gt;Abstraction means hiding the complexity and showing only the essentials.&lt;/p&gt;

&lt;p&gt;Think about meeting someone. You say "Hello!" and they say "Hello!" back. You don't need to know the complex biological processes of how their brain processed your greeting and formulated a response. You just need to know the simple interface: the greet() method. Abstraction hides the messy details.&lt;br&gt;
Similar stuff happens when you go for a walk, you don't need to understand how your body keeps balance or how it pushes you forward to follow Newton's law. You just need to put one feet in front of the other.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgt9roe7u1ypmk8bq368s.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgt9roe7u1ypmk8bq368s.png" alt="Abstraction" width="799" height="346"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You don't get the whole story, just the parts you need to interact with.&lt;/p&gt;
&lt;h4&gt;
  
  
  5. Inheritance: Your Family Legacy
&lt;/h4&gt;

&lt;p&gt;Serious Life Talk!&lt;br&gt;
Let's say you fall in love! And Decide to have babies!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhjuq1kh0a6smc2lgds2n.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhjuq1kh0a6smc2lgds2n.gif" alt="baby" width="240" height="240"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Your children will inherit traits from you. They might get your last name or your eye color.&lt;/p&gt;

&lt;p&gt;Inheritance lets a new class (a Child) adopt the properties and methods of an existing class (a Parent). This saves you from rewriting the same code over and over. The child can have all the parent's features, plus their own unique ones.&lt;/p&gt;

&lt;p&gt;So let's say YOU , The Parent, are a human:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6xzeqthwa1l8m5tin7ei.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6xzeqthwa1l8m5tin7ei.png" alt="Inheritance-1" width="800" height="366"&gt;&lt;/a&gt;&lt;br&gt;
You have your name, age &amp;amp; last name&lt;br&gt;
And then you decide to have your first child:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0dqw103hsyunzg29r55z.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0dqw103hsyunzg29r55z.png" alt="Inheritance-2" width="800" height="436"&gt;&lt;/a&gt;&lt;br&gt;
You name your first child "Itchy"&lt;br&gt;
So NOW your child will naturally inherit your last name:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyk0d57p4dw5w2mru23u7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyk0d57p4dw5w2mru23u7.png" alt="Inheritance-3" width="799" height="306"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now we can use the properties the Child inherited from the parent to carry out tasks:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffcsqu9qxfpg2wa4i3xq6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffcsqu9qxfpg2wa4i3xq6.png" alt="Inh-4" width="800" height="247"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Just like that your kid got your last name &amp;amp; other stuff from your DNA (Class). Kid could inherit your crippling depression &amp;amp; anxiety as well.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmvf2s9cycsw535vtufe3.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmvf2s9cycsw535vtufe3.gif" alt=" " width="480" height="530"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h4&gt;
  
  
  6. Polymorphism: Different Folks, Different Strokes
&lt;/h4&gt;

&lt;p&gt;Polymorphism is a big word for a simple idea: "many forms." It means you can treat objects of different classes in the same way, but they each behave in their own unique style.&lt;/p&gt;

&lt;p&gt;Imagine you ask everyone in the family to doTheirChore().&lt;br&gt;
The Parent might respond by "mowing the lawn", while the Child responds by "tidying up toys". The same instruction (doTheirChore()) produces different results. That's polymorphism in action!&lt;br&gt;
We could add a chore(funtion) to each respective Classes (Parent &amp;amp; Child)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj60pxfwrvax38s5znssi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj60pxfwrvax38s5znssi.png" alt="polymorphism1" width="800" height="691"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We can then assign the chores to the classes:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsdon1w14m2v5p5d4w0sh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsdon1w14m2v5p5d4w0sh.png" alt="polymorphism2" width="799" height="366"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And finally we can add new function calls &amp;amp; execute the Chores!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr89a6q2t8te9nzzg3273.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr89a6q2t8te9nzzg3273.png" alt="polymorphism3" width="800" height="414"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Wrapping Up
&lt;/h3&gt;

&lt;p&gt;And there you have it! The big ideas of OOP, explained through the story of you and your family. It's a powerful way to structure your code, and once you get the hang of it, you'll see how it makes building complex things so much more manageable.&lt;/p&gt;

&lt;p&gt;I'm still on this learning journey myself. A massive shout-out to the repo that got me started and helped me understand these concepts in depth:&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/jafari-dev" rel="noopener noreferrer"&gt;
        jafari-dev
      &lt;/a&gt; / &lt;a href="https://github.com/jafari-dev/oop-expert-with-typescript" rel="noopener noreferrer"&gt;
        oop-expert-with-typescript
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      A complete guide for learning object oriented programming pillars, SOLID principles and design patterns with TypeScript!
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Object Oriented Programming Expert with TypeScript&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;This repository is a complete guide and tutorial for the principles and techniques of object-oriented programming. It can be a reference for all interested in programming and software developers. You will find simple and practical examples in all sections to make the concepts easier to understand.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Table of Contents&lt;/h2&gt;
&lt;/div&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;a href="https://github.com/jafari-dev/oop-expert-with-typescript#fundamentals" rel="noopener noreferrer"&gt;Fundamentals&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/jafari-dev/oop-expert-with-typescript#whats-object-oriented-programming" rel="noopener noreferrer"&gt;What's Object-Oriented-Programming?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/jafari-dev/oop-expert-with-typescript#class" rel="noopener noreferrer"&gt;Class&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/jafari-dev/oop-expert-with-typescript#objects" rel="noopener noreferrer"&gt;Objects&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/jafari-dev/oop-expert-with-typescript#abstraction" rel="noopener noreferrer"&gt;Abstraction&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/jafari-dev/oop-expert-with-typescript#encapsulation" rel="noopener noreferrer"&gt;Encapsulation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/jafari-dev/oop-expert-with-typescript#inheritance" rel="noopener noreferrer"&gt;Inheritance&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/jafari-dev/oop-expert-with-typescript#polymorphism" rel="noopener noreferrer"&gt;Polymorphism&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/jafari-dev/oop-expert-with-typescript#solid-principles" rel="noopener noreferrer"&gt;SOLID Principles&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/jafari-dev/oop-expert-with-typescript#whats-solid-meaning" rel="noopener noreferrer"&gt;What's SOLID Meaning?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/jafari-dev/oop-expert-with-typescript#1-single-responsibility-srp" rel="noopener noreferrer"&gt;Single Responsibility (SRP)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/jafari-dev/oop-expert-with-typescript#2-openclosed-ocp" rel="noopener noreferrer"&gt;Open/Closed (OCP)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/jafari-dev/oop-expert-with-typescript#3-liskov-substitution-lsp" rel="noopener noreferrer"&gt;Liskov Substitution (LSP)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/jafari-dev/oop-expert-with-typescript#4-interface-segregation-isp" rel="noopener noreferrer"&gt;Interface Segregation (ISP)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/jafari-dev/oop-expert-with-typescript#5-dependency-inversion-dip" rel="noopener noreferrer"&gt;Dependency Inversion (DIP)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/jafari-dev/oop-expert-with-typescript#design-patterns" rel="noopener noreferrer"&gt;Design Patterns&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/jafari-dev/oop-expert-with-typescript#whats-a-design-pattern" rel="noopener noreferrer"&gt;What's a Design Pattern?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/jafari-dev/oop-expert-with-typescript#abstract-factory" rel="noopener noreferrer"&gt;Creational - Abstract Factory&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/jafari-dev/oop-expert-with-typescript#builder" rel="noopener noreferrer"&gt;Creational - Builder&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/jafari-dev/oop-expert-with-typescript#factory-method" rel="noopener noreferrer"&gt;Creational - Factory Method or Virtual Constructor&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/jafari-dev/oop-expert-with-typescript#prototype" rel="noopener noreferrer"&gt;Creational - Prototype or Clone&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/jafari-dev/oop-expert-with-typescript#singleton" rel="noopener noreferrer"&gt;Creational - Singleton&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/jafari-dev/oop-expert-with-typescript#adapter-wrapper" rel="noopener noreferrer"&gt;Structural - Adapter or Wrapper&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/jafari-dev/oop-expert-with-typescript#bridge" rel="noopener noreferrer"&gt;Structural - Bridge&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/jafari-dev/oop-expert-with-typescript#composite-object-tree" rel="noopener noreferrer"&gt;Structural - Composite or Object Tree&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/jafari-dev/oop-expert-with-typescript#decorator-wrapper" rel="noopener noreferrer"&gt;Structural - Decorator or Wrapper&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/jafari-dev/oop-expert-with-typescript#facade" rel="noopener noreferrer"&gt;Structural - Facade&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/jafari-dev/oop-expert-with-typescript#flyweight-cache" rel="noopener noreferrer"&gt;Structural - Flyweight or Cache&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/jafari-dev/oop-expert-with-typescript#proxy" rel="noopener noreferrer"&gt;Structural - Proxy&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/jafari-dev/oop-expert-with-typescript#chain-of-responsibility" rel="noopener noreferrer"&gt;Behavioral - Chain of Responsibility&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/jafari-dev/oop-expert-with-typescript#command" rel="noopener noreferrer"&gt;Behavioral - Command or&lt;/a&gt;…&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/jafari-dev/oop-expert-with-typescript" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;And if you want to see my personal study notes and the code examples I'm tinkering with, I've open-sourced them here:&lt;/p&gt;

&lt;p&gt;My OOP in TS Study Repo:&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/ShatilKhan" rel="noopener noreferrer"&gt;
        ShatilKhan
      &lt;/a&gt; / &lt;a href="https://github.com/ShatilKhan/OOP-TS-Study" rel="noopener noreferrer"&gt;
        OOP-TS-Study
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Study on Object Oriented Programming with TypeScript
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://private-user-images.githubusercontent.com/52494840/400137931-74af0377-5c90-4f14-b9b2-1ac214e4a2f7.jpg?jwt=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3NzkzMzQzODcsIm5iZiI6MTc3OTMzNDA4NywicGF0aCI6Ii81MjQ5NDg0MC80MDAxMzc5MzEtNzRhZjAzNzctNWM5MC00ZjE0LWI5YjItMWFjMjE0ZTRhMmY3LmpwZz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNjA1MjElMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjYwNTIxVDAzMjgwN1omWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPTE0ZWU2Y2NmZGUxNGViM2IyM2VhZGEzNGI5NjE2YWZiMDg0NmQzM2QwNDNjOTIwZTQ4OTNkYjY4MjU4MjQ2ZWYmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0JnJlc3BvbnNlLWNvbnRlbnQtdHlwZT1pbWFnZSUyRmpwZWcifQ.82cSvKHbdR55Qcj37ppsp4mc_UE3aC9XzL3TL0HQ-6s"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fprivate-user-images.githubusercontent.com%2F52494840%2F400137931-74af0377-5c90-4f14-b9b2-1ac214e4a2f7.jpg%3Fjwt%3DeyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3NzkzMzQzODcsIm5iZiI6MTc3OTMzNDA4NywicGF0aCI6Ii81MjQ5NDg0MC80MDAxMzc5MzEtNzRhZjAzNzctNWM5MC00ZjE0LWI5YjItMWFjMjE0ZTRhMmY3LmpwZz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNjA1MjElMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjYwNTIxVDAzMjgwN1omWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPTE0ZWU2Y2NmZGUxNGViM2IyM2VhZGEzNGI5NjE2YWZiMDg0NmQzM2QwNDNjOTIwZTQ4OTNkYjY4MjU4MjQ2ZWYmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0JnJlc3BvbnNlLWNvbnRlbnQtdHlwZT1pbWFnZSUyRmpwZWcifQ.82cSvKHbdR55Qcj37ppsp4mc_UE3aC9XzL3TL0HQ-6s" alt="EST#5 (1)"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;OOP-TS-Study&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;Study on Object Oriented Programming with TypeScript&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;What's Object-oriented-programming?&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;Object-oriented programming (OOP) is a programming paradigm based on the concept of "objects", which can contain data and code: data in the form of fields (often known as attributes or properties), and code, in the form of procedures (often known as methods). There are 6 pillars of OOP, includes:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Class&lt;/li&gt;
&lt;li&gt;Objects&lt;/li&gt;
&lt;li&gt;Data Abstraction&lt;/li&gt;
&lt;li&gt;Encapsulation&lt;/li&gt;
&lt;li&gt;Inheritance&lt;/li&gt;
&lt;li&gt;Polymorphism&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;We can use &lt;a href="https://www.typescriptlang.org/play" rel="nofollow noopener noreferrer"&gt;TypeScript Playground&lt;/a&gt; to run the code. We can also use VS Code directly.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Running TypeScript Code in Visual Studio Code&lt;/h1&gt;
&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Prerequisites&lt;/h2&gt;

&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://nodejs.org/" rel="nofollow noopener noreferrer"&gt;Node.js&lt;/a&gt; and npm installed on your machine.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Steps&lt;/h2&gt;

&lt;/div&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Install TypeScript&lt;/strong&gt;
Open the terminal in Visual Studio Code and run the following command to install TypeScript globally:&lt;/p&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;npm install -g typescript &lt;/pre&gt;

&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Check TypeScript Installation&lt;/strong&gt;:
Verify that TypeScript is installed by running:&lt;/p&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;tsc --version&lt;/pre&gt;

&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Create a &lt;code&gt;tsconfig.json&lt;/code&gt; File&lt;/strong&gt;
In your project directory, create a &lt;code&gt;tsconfig.json&lt;/code&gt; file to configure the…&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/ShatilKhan/OOP-TS-Study" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;You might see that some of the code is a few months old, as I stated at the beginning I initially planned to publish this blog a while ago but just couldn't find the time. &lt;br&gt;
And I'm also experimenting with writing techniques &amp;amp; how to better present coding topics. &lt;br&gt;
If you like this one, make sure to check out other blogs in this series.&lt;br&gt;
Engineering of Small Things Series is basically written up to share where I explore fundamental topics of how the web works.&lt;br&gt;
More to come soon!&lt;br&gt;
And Happy Coding!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0t8a2wwzthd3n6lzr5n7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0t8a2wwzthd3n6lzr5n7.png" alt="Ai-was-definitely-used" width="800" height="671"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>typescript</category>
      <category>beginners</category>
      <category>tutorial</category>
      <category>oop</category>
    </item>
    <item>
      <title>ক্লাউড কম্পিউটিং কি জিনিস ?</title>
      <dc:creator>ShatilKhan</dc:creator>
      <pubDate>Fri, 07 Feb 2025 06:27:30 +0000</pubDate>
      <link>https://dev.to/siren/klaaudd-kmpiuttin-ki-jinis--7pd</link>
      <guid>https://dev.to/siren/klaaudd-kmpiuttin-ki-jinis--7pd</guid>
      <description>&lt;p&gt;&lt;em&gt;Author's Note: This blog is a bit different compared to my other blogs. This blog is specifically meant for student who want to learn a basic idea about cloud computing. This is also part of my work as an AWS Cloud Club Captain at my Campus. So on this blog I try to give students a general idea about cloud. And since my primary audience for this blog is Bangladeshi students I thought I would try to explain things in Bengali to keep it fun!&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;এখন আসি আসল কথায়, ক্লাউড কম্পিউটিং জিনিস টা আসলে কি?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd4bame392gw09mqka6j0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd4bame392gw09mqka6j0.png" alt="What is cloud" width="272" height="510"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;আপনার Day-to-day লাইফের সবচেয়ে সহজ আর চেনার মত একটা উদাহরণ দিতে গেলে বলা যায় ড্রাইভ স্টোরেজ(ড্রপবক্স, গুগল ড্রাইভ ইত্যাদি) । এইধরণের সার্ভিস গুলা মোটামুটি সবাই ইউজ করে বা না করলেও ফিচার গুলা সবার ফোনেই এক্টিভ । এই ক্ষেত্রে আসলে কি হচ্ছে? &lt;br&gt;
ধরেন আপনি আপনার কোন ছবি ড্রাইভ স্টোরেজে রাখলেন, এখন সেটা আপনি যেকোন জায়গা থেকে এক্সেস করতে পারবেন।&lt;br&gt;
এই যে আপনার ফাইল/ছবিগুলা রাখলেন, এখন আপনার ফোন থেলে ডিলিট করে দিলেও সেটা পরে আপনি এক্সেস করতে পারবেন, right?&lt;br&gt;
এই ডাটা টা সেভ হচ্ছে কোন এক বান্দার সার্ভারে, আপনি যখন ডাটা চাইবেন তখন বেসিকেলি সার্ভার সেই ডাটা আপনাকে দিবে। এই যে দূরে কোন সার্ভারে ডাটা রাখার যেই ব্যাপার টা, এটাই ক্লাউড কম্পিউটিং এর সবচেয়ে বেসিক অংশ।&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm5guu64hq31m397ov0pn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm5guu64hq31m397ov0pn.png" alt="user server" width="523" height="566"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;আরো কিছু বেসিক উদারণ হচ্ছে ওয়েবসাইট হোস্ট করা। ওয়েবসাইট হোস্ট করলেও সেটা কোন সার্ভারে থাকে, পরে যে কেউ সেখান থেকে এক্সেস করতে পারে একটা পাবলিক ডোমেইন এড্রেস দিয়ে।&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;এখন আসি Amazon Web Services বা AWS নিয়ে&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;"Amazon Web Services, Inc. is a subsidiary of Amazon that provides on-demand cloud computing platforms and APIs to individuals, companies, and governments, on a metered, pay-as-you-go basis. Clients will often use this in combination with autoscaling." - Wikipedia&lt;/p&gt;

&lt;p&gt;এখন এই কথার মানে কি? on-demand, metered, pay-as-you-go, auto-scaling?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F44uq1it0e0mbmup9x2kz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F44uq1it0e0mbmup9x2kz.png" alt="what" width="257" height="346"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;এই ব্লগের উদ্দেশ্য , যে স্টুডেন্ট ক্লাউড নিয়ে কিছুই জানে না তাদের কে জানানো। তাই AWS কে সহজ ভাবে বুঝাচ্ছি।&lt;/p&gt;

&lt;p&gt;AWS  হচ্ছে বিশ্বের সবচেয়ে বড় ক্লাউড প্রোভাইডার। মানে AWS  বিভিন্ন ধরণের ক্লাউড রিলেটেড সার্ভিস দেয় কাস্টমার দের। এর মধ্যে কিছু বেসিক সার্ভিস হলো ওয়েবসাইট হোস্টিং, ডোমেইন সেটাপ , ডাটাবেস ম্যানেজ করা । &lt;br&gt;
এখন কথা হচ্ছে এগুলা দেয়ার কারণ কি?&lt;br&gt;
ধরলাম ক্লাউড কম্পিউটিং নামের কোন জিনিস নাই। এখন আপনই একটা কম্পানি খুলতে চাচ্ছেন, সেক্ষেত্রে আপনার ওয়েবসাইট লাগবেই। এখন আপনাকে সেই সাইট চালাইতে গেলে আগে নিজে একটা সার্ভার কম্পিউটার কেনা লাগবে, যেগুলার দাম লাখ লাখ টাকা। সেই সার্ভার রাখার জন্যে রুম লাগবে, রুমের আবার এসি লাগবে, নাহলে সার্ভারের মাথা গরম হয়ে যাবে।&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnkxwzmehyrfd4q0z5k5j.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnkxwzmehyrfd4q0z5k5j.png" alt="server on fire" width="367" height="316"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;তার মানে আপনার বিজনেস শুরু করতে গেলে আপনার বড় একটা এমাউন্টের ইনভেস্টমেন্ট লাগবে।&lt;br&gt;
এখন আসি ক্লাউডে। &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;আপনার সার্ভার লাগবে? ভাড়া নেন!&lt;/strong&gt;&lt;br&gt;
AWS  বেসিকেলি নিজেদের সার্ভার গুলা আপনাকে অল্প মূল্যে ভাড়া দিবে। যার বদলে আপনাকে pay-as-you-go  বা যতটুকু স্টোরেজ আপনার লাগবে সেই অনুযায়ী পেমেন্ট করবেন।&lt;/p&gt;

&lt;p&gt;এখন কিছু বেসিক AWS  সার্ভিস নিয়ে আলোচনা করি।&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzs4d184dy4ttd3nlo9n1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzs4d184dy4ttd3nlo9n1.png" alt="basic aws services" width="459" height="216"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;S3 বা &lt;em&gt;Simple Storage Service&lt;/em&gt; হচ্ছে AWS  র সবচেয়ে বেসিক অফারিং, যেটা আমরা মোটামুটি ডেইলি কোন না কোন ভাবে ইউজ করি। যেমন আপনই গুগল ড্রাইভে কোন ছবি রাখলে সেটার ড্রাইভ লিংক যে কারো সাথে শেয়ার করতে পারেন, S3  সেই সুবিধাটাই দেয়। তবে এক্ষেত্রে আরো অনেক ফিচার আছে, আমি ব্যাপার অনেক বেশি সিমপ্লিফাই করে বলছি।&lt;/p&gt;

&lt;p&gt;এটার সহজ একটা উদারণ দেই, এই যে ব্লগ টা পড়ছেন, যেই ইমেজ গুলা এখানে দেখতে পাচ্ছেন, এগুলা আসলে আমি যখন আপলোড করছি, জিনিস টা আগে S3  তে যায়, তারপর সেখান থেকে একটা লিংক জেনারেট হয়, তারপর সেই লিংক টা এখানে আসে, যেটার মাধ্যমে আপনারা ইমেজ গুলা দেখতে পান।&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn8vqp0gvb45ok6boapg1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn8vqp0gvb45ok6boapg1.png" alt="S3" width="800" height="352"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;এখন আসি &lt;em&gt;Amplify&lt;/em&gt; নিয়ে, এগুলা নিয়ে ওতটা বলবো না কারণ বিগিনার হিসেবে জাস্ট ক্লাউডের আইডিয়া দেয়ার জন্যে বলা।&lt;br&gt;
Amplify দিয়ে বেসিকেলি আমরা আমাদের যেকোন ওয়েবসাইট হোস্টিং আর ডিপ্লয়মেন্টের কাজ করতে পারি। যারা আগে কখনো নিজেদের কোন ওয়েবসাইট হোস্ট করে থাকবেন তাদের জন্যে জিনিস টা বোঝা সহজ হবে।&lt;br&gt;
বেসিকেলি আপনার GitHub রিপো বা কডের ফাইল আপলোড করলে Amplify সেটা হোস্ট করে একটা হোস্টেড ডিপ্লয়মেন্ট লিংক দেবে। ডোমেইন নেম সেটাপের জন্য আমরা AWS Route 53 র মত সার্ভিস ইউজ করতে পারি যেখানে আমরা কোন কাস্টম ডোমেইন নেম কিনে সেটাপ করতে পারবো। তবে সেগুলা এই ব্লগের বিষয় নয়, আগামী কোন ব্লগে এই রিলেটেড প্রসেস নিয়ে আলোচনা করবো।&lt;/p&gt;

&lt;p&gt;এবং সবশেষে EC2 বা &lt;em&gt;Elastic Compute Cloud&lt;/em&gt;, এটাও বিগিনারদের জন্য বোঝা একটু কমপ্লেক্স মনে হতে পারে। তবে আমি চেষ্টা করবো সহজভাবে বোঝানোর। &lt;br&gt;
ধরেন আপনার ল্যাপটপ বা পিসি টা মানধাতার আমলের, ৪ জিবি র‍্যাম , ৪ কোরের প্রসেসর ইত্যাদি। এখন আপনই আপনার পিসিতে কোন একটা রিসোর্স ইন্টেন্সিভ কাজ করতে চাচ্ছেন, যেটার জন্যে অনেক র‍্যাম লাগবে বা আরো বেশি প্রসেসিং পাওয়ার লাগবে।&lt;br&gt;
EC2 আপনার জন্যে একটা &lt;em&gt;ভার্চুয়াল পিসি / মেশিন (VM)&lt;/em&gt; রেডি করে দিবে। মানে ধরেন আপনার ৮ জিবি/১৬ জিবি র‍্যামের পিসি লাগবে। ওই যে আগে বলছিলাম যে সার্ভার ভাড়া নেয়ার ব্যাপার টা। এমাজন বেসিকেলি অনেক গুলা সার্ভার কম্পিউটার শেয়ারে ভাড়া দিচ্ছে। আর একটা সার্ভারের র‍্যাম কয়েকশ জিবি বা তার বেশি হয়, প্রসেসিং পাওয়ারও । তখন ওরা বেসিকেলি আপনাকে নির্দিষ্ট এমাউন্ট ভাড়া দিবে , যতটুকু আপনার দরকার (&lt;em&gt;৮ জিবি র‍্যাম/৪ কোর প্রসেসর, ১৬জিবি র‍্যাম / ৮ কোর প্রসেসর&lt;/em&gt;)। এবং আপনই যতটুকু ইউজ করবেন সেই অনুযায়ী আপনার খরচ আসবে। এখন প্রশ্ন আসতে পারে, তাহলে এই যে এত হাই লেভেলের মেশিন লার্নিং মডেল বা এডভান্স এলগিরদম নিয়ে বড় কম্পানিগুলায় কাজ হয়, এগুলা তাহলে ওরা এভাবে করে?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ঠিক ধরেছেন!&lt;/strong&gt;&lt;br&gt;
হ্যাঁ অনেকেই নিজস্ব সার্ভার কিনে,কিন্তু ছোট কম্পানি বা স্টার্টাপ গুলা ম্যাক্সিমাম এভাবেই কাজ করে। আপনার বেশি লার্নিং মডেল ট্রেইন করাতে যেই জিপিউ রিসোর্স লাগবে সেটাও ক্লাউডের মাধ্যমে এক্সেস নিয়ে করা হয়। তবে এগুলা বিগিনার টপিকের বাইরে পরে তাই অন্যদিন আলোচনা করবো।&lt;/p&gt;

&lt;p&gt;ব্লগ টা মেইনলি এবস্যুলুট বিগিনার দের উদ্দেশ্য করেই বানানো। কারণ অনেক ক্ষেত্রেই বাংলাদেশে বেশিরভাগ ভার্সিটি গুলোতে আলাদাভাবে কোন ক্লাউডের কোর্স বা এধরণের শেখার সুবিধা নাই। কিন্তু AI নিয়ে সবাই লাফায়, ওয়েব ডেভেলপমেন্ট নিয়ে এত মাতামাতি, দিনশেষে কোন না কোন পর্যায়ে যায়ে ক্লাউডের কাজ শিখতেই হবে। তাই এই ব্লগের উদ্দেশ্য ছিল স্টুডেন্ট দের সেই বিষয়ে বেসিক আইডিয়া দেয়া।&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Author's Note: If you want to learn more about the topic's mentioned &amp;amp; would love to see more blogs on this, I'd appreciate if you comment down below to show your interest.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>beginners</category>
      <category>cloud</category>
      <category>aws</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Engineering of Small Things #4 : WebSockets</title>
      <dc:creator>ShatilKhan</dc:creator>
      <pubDate>Fri, 27 Dec 2024 05:09:07 +0000</pubDate>
      <link>https://dev.to/siren/engineering-of-small-things-4-websockets-4n6</link>
      <guid>https://dev.to/siren/engineering-of-small-things-4-websockets-4n6</guid>
      <description>&lt;p&gt;Hello Again!&lt;/p&gt;

&lt;p&gt;Recently I was working on a website where I needed to pull data from the server periodically, it wasn't really required as the database updated only once a week or so. But still I thought it would be nice to have a way if I could periodically get data from server without having to request it from the client side. SO then I came across socket servers!&lt;br&gt;
It basically allows two-way communication between a client &amp;amp; a server. We can also setup timeout methods so that the server automatically sends data  to the client on  a regular interval.&lt;br&gt;
So I studied a bit on the topic , got some help from chatgpt :)&lt;br&gt;
But also I like to learn from experience engineers rather than regular courses. One of the engineers I admire is &lt;a class="mentioned-user" href="https://dev.to/husseinnasser"&gt;@husseinnasser&lt;/a&gt; . I followed a short crash course from Nasser about WebSockets as well. Because as a non-CSE student I didn't really know how the underlying connections work. So I needed to learn from scratch! &lt;br&gt;
And it was indeed a great experience. I first learned about protocols! HTTP, HTTP 1.1, TCP, UDP &lt;br&gt;
This gave me a good foundation to understand what's going on in a WebSocket server.&lt;br&gt;
Then I made a raw websokcet server using http. I set up certain breakpoints &amp;amp; tested the connection between the server &amp;amp; the client. It was unique way to learn because I didn't write much frontend code for this. Instead I tested the client by directly sending client messages from the browser console!&lt;/p&gt;

&lt;p&gt;First we'll need a basic http server : &lt;/p&gt;

&lt;p&gt;Basic Package Requirements &amp;amp; Connection :&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwbwjl59t4svl4x51qds7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwbwjl59t4svl4x51qds7.png" alt=" " width="800" height="314"&gt;&lt;/a&gt;&lt;br&gt;
Now there is a Null connection variable here, we'll get to it later.&lt;/p&gt;

&lt;p&gt;Base http server initiate:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg6ayjt2zr6ucw93jvjsk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg6ayjt2zr6ucw93jvjsk.png" alt=" " width="799" height="315"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But this is just a request-response server. Once a response is received, connection is gone. We need a reliable connection. This is where TCP comes in. When I pass in this http server into a WebSocket server it will then send an UPGRADE 1.1 header request to the server from the client side. It will request to switch protocol from http to http 1.1 &amp;amp; establish a TCP connection for full-duplex communication.&lt;br&gt;
Pass in the http server into the websocket server to create a TCP connection:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp22g0eqw5sya1jdm9idk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp22g0eqw5sya1jdm9idk.png" alt=" " width="800" height="365"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Server Listening:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F337104xvm5bzplhx2kyx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F337104xvm5bzplhx2kyx.png" alt=" " width="800" height="269"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;the websocket server can choose weather to accept the request or not , &lt;br&gt;
accepting sends back swithing protocol 101 response , which opens up the full duplex communication between client &amp;amp; server&lt;br&gt;
1st parameter is a custom protocol , could be for chatting , gaming, null means we'll accept anything, no specific protocol needed&lt;br&gt;
2nd paramter - we can check the origin of the request, generally the url the request was sent from , to checck if it's atrusted source&lt;br&gt;
Below are the events that we can use , open, close, message etc.The main point of WebSockets is having these events.&lt;br&gt;
When each event occurs we can get a certain response from server automatically without the client needing to initiate a request&lt;br&gt;
Socket Events:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwfj8i58x1ny4yj68rcpf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwfj8i58x1ny4yj68rcpf.png" alt=" " width="800" height="470"&gt;&lt;/a&gt;&lt;br&gt;
Notice we see the null connection here that we initiated in the beginning. This tells the server that we can accept ANYTHING. Any type of data is accepted as we are just testing. &lt;br&gt;
Now we can send &amp;amp; receive connection between the server &amp;amp; client&lt;br&gt;
From the Debug console in VS Code we can send a server message:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9lg34wod07g0i2ynb0sv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9lg34wod07g0i2ynb0sv.png" alt=" " width="800" height="357"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then from the browser console, we can send a client response:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo1blwuniiabvusrhpfy4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo1blwuniiabvusrhpfy4.png" alt=" " width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The connection stays open as long as nobody closes the connection.&lt;br&gt;
If the server shuts down, the connection will close&lt;br&gt;
if the client browser initiates the event &lt;code&gt;ws.close&lt;/code&gt; the connection will close.&lt;br&gt;
But this is just basic connection. Now We will make a function that will keep sending messages from the WebSocket server to the client on a regular  interval. Think of game streaming , twitch, video streaming etc.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7cyepuufrvltdrqxfbn7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7cyepuufrvltdrqxfbn7.png" alt=" " width="800" height="419"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Pass in the function after we open the connection:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmts9nfme3sv1sx088obn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmts9nfme3sv1sx088obn.png" alt=" " width="800" height="403"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We can log this stream of messages on the client side using &lt;code&gt;onmessage&lt;/code&gt; event&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz246i1dyr6prmq5zxev6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz246i1dyr6prmq5zxev6.png" alt=" " width="800" height="189"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Voila We just made a full-duplex two-way communication between a server &amp;amp; a client.&lt;/p&gt;

&lt;p&gt;Super fun stuff to learn!&lt;/p&gt;

&lt;p&gt;Codebase:&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/ShatilKhan" rel="noopener noreferrer"&gt;
        ShatilKhan
      &lt;/a&gt; / &lt;a href="https://github.com/ShatilKhan/socket-server" rel="noopener noreferrer"&gt;
        socket-server
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      exploring how socket servers work
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;socket-server&lt;/h1&gt;

&lt;/div&gt;
&lt;p&gt;exploring how socket servers work&lt;/p&gt;
&lt;/div&gt;



&lt;/div&gt;
&lt;br&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/ShatilKhan/socket-server" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;
&lt;br&gt;


&lt;p&gt;Check out the video to see the code in action:&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/7RaDgU1VP-U"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>node</category>
      <category>beginners</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Engineering of Small Things #3 : Small Language Models</title>
      <dc:creator>ShatilKhan</dc:creator>
      <pubDate>Thu, 19 Dec 2024 10:33:28 +0000</pubDate>
      <link>https://dev.to/siren/engineering-of-small-things-3-small-language-models-37kh</link>
      <guid>https://dev.to/siren/engineering-of-small-things-3-small-language-models-37kh</guid>
      <description>&lt;p&gt;Disclaimer! I am NOT an AI Influencer! This is literally just me learning about how to implement a hugging face model for the first time.&lt;br&gt;
And Boy Was it Hard! :)) &lt;/p&gt;

&lt;h2&gt;
  
  
  What is this even about?
&lt;/h2&gt;

&lt;p&gt;I built a document Question &amp;amp; answering bot for this demonstration. It takes an Image &amp;amp; we can query &amp;amp; ask questions regarding that Image.&lt;br&gt;
I had no idea how to implement language models going into this. So it was a really fun experience. Now this is part of a bigger project. Today I'm just sharing one part of it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Going Crazy
&lt;/h2&gt;

&lt;p&gt;This was for a hackathon project where I was trying to use &lt;a class="mentioned-user" href="https://dev.to/streamlit"&gt;@streamlit&lt;/a&gt; &amp;amp; &lt;a href="https://huggingface.co/" rel="noopener noreferrer"&gt;HuggingFace&lt;/a&gt; , I never even had a hugging face account &amp;amp; had only basic tutorial level experience on &lt;a class="mentioned-user" href="https://dev.to/streamlit"&gt;@streamlit&lt;/a&gt; . But I really wanted to learn &amp;amp; implement something on my own. I was tired of following tutorials &amp;amp; it didn't matter if it was a standard solution or not!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffsryyoxi35f1cox0whvt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffsryyoxi35f1cox0whvt.png" alt=" " width="220" height="307"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But like the heading says, I did go crazy a couple times :)&lt;/p&gt;

&lt;h2&gt;
  
  
  The beginning of Insanity
&lt;/h2&gt;

&lt;p&gt;Now I'm a web developer who had only heard about hugging face, didn't really care about the hype. But then I decided to experiment a little with this tech. I did not have the necessary setup on my local device , so firstly I had to install &lt;code&gt;PyTorch&lt;/code&gt; &amp;amp; &lt;code&gt;TesseractOCR&lt;/code&gt; on my local PC first. I will not be sharing this trauma :)&lt;br&gt;
Those who know...know 😫&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbuj3042h84lts1ftbhfz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbuj3042h84lts1ftbhfz.png" alt=" " width="750" height="422"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But I will take you through how I implemented it!&lt;/p&gt;

&lt;p&gt;First we need the basic ingredients! A transformer!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fykayb3xlqdmgdsp5wg5n.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fykayb3xlqdmgdsp5wg5n.png" alt=" " width="360" height="539"&gt;&lt;/a&gt;&lt;br&gt;
How does it work? - I have no f**king Idea!&lt;br&gt;
What does it do? - Makes language model go "brrrr"&lt;/p&gt;

&lt;p&gt;And a library to read Image files.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Basic Imports:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fywt1sdatss2te53f9vpd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fywt1sdatss2te53f9vpd.png" alt=" " width="800" height="345"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now to initialize our pipeline.&lt;br&gt;
What does that even mean? Basically we are selecting a language model from huggingface model catalogue. And also setting what type of model that is. Here comes the main point of this blog. Small Language Models. At first I did try to use a popular Large Language Model (Mistral) , but here's the thing, after we've initialized a pipeline, when I run the program for the first time, it needs to download the model onto my local device. But like I've stated before, I have shitty internet &amp;amp; the Mistral-8B was like 2GB+. Every time I would start the project, the model would download halfway &amp;amp; give up.&lt;br&gt;
So I opted to choose a Small Language Model called &lt;a href="https://huggingface.co/impira/layoutlm-document-qa" rel="noopener noreferrer"&gt;impira/layoutlm-document-qa&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is a 500 MB model that was a good enough job of answering questions from an uploaded document. But there's some parameters, like the uploaded document needs to be an image, hence the need for a separate library to read image files (PIL).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pipeline Initialization:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4d17isflpd3aic2plxce.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4d17isflpd3aic2plxce.png" alt=" " width="800" height="328"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But here's the hard lesson I learned. I used streamlit cloud to deploy the project. So it was running on a cloud gpu. But after a few minutes of usage it would become WAY TOO resource intensive &amp;amp; the project would shut down due to streamlit cloud's resource limitations. I couldn't figure this out for SOO long. But then streamlit came to the rescue again. Using &lt;a href="https://docs.streamlit.io/develop/api-reference/caching-and-state" rel="noopener noreferrer"&gt;&lt;code&gt;st.cache&lt;/code&gt;&lt;/a&gt; we can cache out data so that they become less resource intensive. But I was using &lt;a href="https://docs.streamlit.io/develop/api-reference/caching-and-state/st.cache_data" rel="noopener noreferrer"&gt;&lt;code&gt;st.cache_data&lt;/code&gt;&lt;/a&gt; at first, now this API only caches Images or other Data, NOT AI MODELS. Because AI Language Models are classified as resources. So later I had to switch to &lt;a href="https://docs.streamlit.io/develop/api-reference/caching-and-state/st.cache_resource" rel="noopener noreferrer"&gt; &lt;code&gt;st.cache_resource&lt;/code&gt;&lt;/a&gt; which finally solved the problem!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Caching Pipeline Initialization:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5oybhksndnxshqk38zwi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5oybhksndnxshqk38zwi.png" alt=" " width="800" height="322"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The next steps were pretty easy once I solved the main problem.&lt;br&gt;
On the next stage I used an &lt;code&gt;if-statement&lt;/code&gt; to check if image file is uploaded &amp;amp; loaded the pipeline so the SLM could read the file.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Verify Image Upload:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F13vg4cvm8ln4r1kbl6c1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F13vg4cvm8ln4r1kbl6c1.png" alt=" " width="799" height="271"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After that I initialized a form using &lt;a href="https://docs.streamlit.io/develop/api-reference/execution-flow/st.form" rel="noopener noreferrer"&gt;&lt;code&gt;st.form&lt;/code&gt;&lt;/a&gt; to submit a question&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Initialize form:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fit78gj7yhcur7c5wh3sj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fit78gj7yhcur7c5wh3sj.png" alt=" " width="800" height="322"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If a question is submitted , the image will be opened &amp;amp; the AI Model will query the image.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Open Query:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi4zwwaduqesijxbrsc0u.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi4zwwaduqesijxbrsc0u.png" alt=" " width="800" height="412"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now the query performed doesn't just produce one answer. Remember, this is a language model, it doesn't understand human language, but can only predict the next word based on training data. So in this case it will produce multiple answer. &lt;br&gt;
Finally it will choose the most probable answer and show it to the user!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Get the best answer:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1cffkrwn6r6opwkakgr5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1cffkrwn6r6opwkakgr5.png" alt=" " width="800" height="438"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here's the main file: &lt;a href="https://github.com/ShatilKhan/Hemo/blob/main/hemo.py" rel="noopener noreferrer"&gt;https://github.com/ShatilKhan/Hemo/blob/main/hemo.py&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There's a lot of other features as it is part of a larger project, I just explained the part where I used a Language Model is all.&lt;br&gt;
Hopefully I'll write more about other features of this project soon!&lt;br&gt;
Happy Coding!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl3kzbs69xor01rs7vnnv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl3kzbs69xor01rs7vnnv.png" alt=" " width="259" height="194"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>beginners</category>
      <category>tutorial</category>
      <category>learning</category>
    </item>
  </channel>
</rss>
