<?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: Humphery </title>
    <description>The latest articles on DEV Community by Humphery  (@humphery7).</description>
    <link>https://dev.to/humphery7</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%2F3526207%2F81ed5dbf-d2b3-4a39-a05f-af87853a30e4.jpeg</url>
      <title>DEV Community: Humphery </title>
      <link>https://dev.to/humphery7</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/humphery7"/>
    <language>en</language>
    <item>
      <title>Building “Exhibit”: An AI-Powered Portfolio Agent with Mastra, A2A, and Telex</title>
      <dc:creator>Humphery </dc:creator>
      <pubDate>Mon, 03 Nov 2025 12:52:47 +0000</pubDate>
      <link>https://dev.to/humphery7/building-exhibit-an-ai-powered-portfolio-agent-with-mastra-a2a-and-telex-5d15</link>
      <guid>https://dev.to/humphery7/building-exhibit-an-ai-powered-portfolio-agent-with-mastra-a2a-and-telex-5d15</guid>
      <description>&lt;p&gt;&lt;em&gt;By Humphery Otuoniyo&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;As developers, showcasing our work effectively can sometimes be as challenging as building the projects themselves. I recently built &lt;strong&gt;Exhibit&lt;/strong&gt;, an intelligent agent that generates personalized developer portfolios directly from GitHub repositories and a preferred tech stack. In this article, I’ll walk you through the process of building the agent using Mastra, setting up the A2A protocol for agent communication, and integrating it with Telex for a seamless front-end experience.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;1. Why Exhibit?&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;The motivation behind Exhibit was simple: developers often have many repositories on GitHub, but not all of them are portfolio-ready. Manually curating projects and writing polished descriptions can be time-consuming. Exhibit automates this process by:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Fetching GitHub repositories using the user’s username.&lt;/li&gt;
&lt;li&gt;Selecting projects that match the user’s tech stack.&lt;/li&gt;
&lt;li&gt;Generating a clean, structured portfolio in Markdown format.&lt;/li&gt;
&lt;li&gt;Organizing sections such as Introduction, Tech Stack, Projects, and Contact information.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This ensures developers have a professional, ready-to-publish portfolio in minutes.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;2. Building the Agent with Mastra&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Mastra provides a framework for building intelligent agents that can combine AI models, tools, and memory. Here’s how I defined the Exhibit Agent:&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="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Agent&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@mastra/core/agent&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Memory&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@mastra/memory&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;LibSQLStore&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@mastra/libsql&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;githubRepoTool&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;../tools/exhibit-tool&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;scorers&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;../scorers/exhibit-scorer&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;exhibitAgent&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;Agent&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Exhibit Agent&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;instructions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`
    You are Exhibit — an AI that generates a professional portfolio from GitHub repositories...
  `&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;google/gemini-2.5-flash&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;githubRepoTool&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;scorers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;scorers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;memory&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Memory&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;storage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;LibSQLStore&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;file:../mastra.db&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;p&gt;Key highlights:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Tools integration:&lt;/strong&gt; The agent uses &lt;code&gt;githubRepoTool&lt;/code&gt; to fetch repositories and metadata.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Custom scoring:&lt;/strong&gt; I implemented scorers to evaluate the quality, completeness, and appropriateness of tool usage in generating the portfolio.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Memory:&lt;/strong&gt; Using &lt;code&gt;LibSQLStore&lt;/code&gt; ensures the agent remembers previous interactions, improving follow-up portfolio generations.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;3. Setting Up the A2A Protocol&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;A2A (Agent-to-Agent) is Mastra’s protocol for sending structured messages between clients and agents. It follows the JSON-RPC 2.0 specification. Here’s how the A2A route was defined:&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="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;registerApiRoute&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@mastra/core/server&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;a2aAgentRoute&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;registerApiRoute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/a2a/agent/:agentId&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="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;handler&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;c&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;mastra&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;c&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;mastra&lt;/span&gt;&lt;span class="dl"&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;agentId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;param&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;agentId&lt;/span&gt;&lt;span class="dl"&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;body&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;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&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;agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;mastra&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getAgent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;agentId&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;messages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;body&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="nx"&gt;messages&lt;/span&gt; &lt;span class="o"&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;mastraMessages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;messages&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;msg&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;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;role&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;msg&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="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt; &lt;span class="o"&gt;||&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;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&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="s2"&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;response&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;agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;mastraMessages&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;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;jsonrpc&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;2.0&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;result&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;response&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;p&gt;This route:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Receives user messages in JSON-RPC format.&lt;/li&gt;
&lt;li&gt;Converts them into the string format expected by &lt;code&gt;agent.generate&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Returns the agent’s response, including artifacts and portfolio text.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With this, any client can communicate with Exhibit securely and reliably.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;4. Integrating with Telex&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Telex acts as a front-end interface for your agents. It provides a low-code platform to create workflows and connect your A2A agent nodes. For Exhibit:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;I defined an &lt;strong&gt;A2A node&lt;/strong&gt; pointing to the deployed agent:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"exhibit_agent"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"exhibit agent"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"a2a/mastra-a2a-node"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://rapid-purple-library-8c383acc-5862-46c4-b9ea-3451fc36ac61.mastra.cloud/a2a/agent/exhibitAgent"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Created a workflow where users input:&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;GitHub username&lt;/li&gt;
&lt;li&gt;Preferred tech stack&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;The agent processes this input, fetches repositories, and outputs a ready-to-use Markdown portfolio.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This integration makes it possible to test and deploy Exhibit without building a full front-end from scratch.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;5. Testing the Agent&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Once deployed, you can test Exhibit via &lt;strong&gt;any HTTP client&lt;/strong&gt; (like Postman or curl) using JSON-RPC messages:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST &lt;span class="s2"&gt;"https://&amp;lt;your-mastra-domain&amp;gt;/a2a/agent/exhibitAgent"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{
  "jsonrpc": "2.0",
  "id": "test-1",
  "method": "generate",
  "params": {
    "message": {
      "role": "user",
      "parts": [{"kind": "text", "text": "GitHub username: octocat, tech stack: JavaScript, React"}]
    }
  }
}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The response will contain the portfolio Markdown text, ready to save or display.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;6. What I Learned&lt;/strong&gt;
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Mastra’s flexibility&lt;/strong&gt; allows for combining AI, tools, and memory efficiently.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A2A protocol&lt;/strong&gt; simplifies structured communication between agents and clients.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Telex integration&lt;/strong&gt; enables low-code testing and workflow management.&lt;/li&gt;
&lt;li&gt;Automating portfolio generation can save developers hours while ensuring professionalism.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  &lt;strong&gt;7. Next Steps&lt;/strong&gt;
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Add portfolio customization options, like themes or formats.&lt;/li&gt;
&lt;li&gt;Integrate more GitHub analytics (stars, commits) for project selection.&lt;/li&gt;
&lt;li&gt;Explore multi-agent workflows to build a complete developer dashboard.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Exhibit is now live and working! This project demonstrates the power of combining AI agents, structured messaging, and low-code workflows to automate developer-focused tasks. Code for the project can be found at my &lt;a href="https://github.com/Humphery7/Exhibit" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;&lt;/p&gt;

</description>
      <category>programming</category>
      <category>backend</category>
      <category>ai</category>
      <category>mastra</category>
    </item>
    <item>
      <title>Building a String Analyzer Service: From Concept to Deployment</title>
      <dc:creator>Humphery </dc:creator>
      <pubDate>Wed, 22 Oct 2025 18:03:07 +0000</pubDate>
      <link>https://dev.to/humphery7/building-a-string-analyzer-service-from-concept-to-deployment-4i3p</link>
      <guid>https://dev.to/humphery7/building-a-string-analyzer-service-from-concept-to-deployment-4i3p</guid>
      <description>&lt;p&gt;&lt;em&gt;By Humphery Otuoniyo&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I built a RESTful API service designed to analyze strings and store their computed properties. The project offered a hands-on experience in backend development, from schema design to deployment on Railway, and highlighted the importance of robust data validation and flexible querying.&lt;/p&gt;

&lt;h2&gt;
  
  
  Project Overview
&lt;/h2&gt;

&lt;p&gt;The service analyzes any submitted string and computes several properties:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Length&lt;/strong&gt;: Number of characters in the string&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Is Palindrome&lt;/strong&gt;: Boolean indicating whether the string reads the same forwards and backwards (case-insensitive)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Unique Characters&lt;/strong&gt;: Count of distinct characters&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Word Count&lt;/strong&gt;: Number of words separated by whitespace&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SHA-256 Hash&lt;/strong&gt;: Unique identifier for the string&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Character Frequency Map&lt;/strong&gt;: A dictionary mapping each character to its occurrence count&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This allowed the service to not only store strings efficiently but also provide analytical insights for further queries.&lt;/p&gt;

&lt;h2&gt;
  
  
  API Endpoints
&lt;/h2&gt;

&lt;p&gt;The service exposes five key endpoints, each serving a specific purpose:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Create/Analyze String
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;POST /strings&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Users can submit a string in JSON format, and the service computes its properties and stores it:&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="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"string to analyze"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Successful responses include the computed properties and the SHA-256 hash. The service handles conflicts and invalid input gracefully, returning appropriate status codes such as &lt;code&gt;409 Conflict&lt;/code&gt; if the string already exists, &lt;code&gt;400 Bad Request&lt;/code&gt; for missing fields, or &lt;code&gt;422 Unprocessable Entity&lt;/code&gt; if the value type is incorrect.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Get Specific String
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;GET /strings/{string_value}&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This endpoint retrieves a single string and its properties. If the string does not exist, a &lt;code&gt;404 Not Found&lt;/code&gt; is returned.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Get All Strings with Filtering
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;GET /strings?is_palindrome=true&amp;amp;min_length=5&amp;amp;max_length=20&amp;amp;word_count=2&amp;amp;contains_character=a&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The service allows powerful query filtering:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;is_palindrome&lt;/code&gt;: boolean&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;min_length&lt;/code&gt; and &lt;code&gt;max_length&lt;/code&gt;: integers&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;word_count&lt;/code&gt;: exact number of words&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;contains_character&lt;/code&gt;: a single character&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Responses include all matching strings, the total count, and a record of the filters applied. Invalid query parameters are rejected with &lt;code&gt;400 Bad Request&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Natural Language Filtering
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;GET /strings/filter-by-natural-language?query=all%20single%20word%20palindromic%20strings&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;One of the most interesting features is the ability to parse natural language queries. The service interprets queries like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;"all single word palindromic strings"&lt;/code&gt; → &lt;code&gt;word_count=1&lt;/code&gt;, &lt;code&gt;is_palindrome=true&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;"strings longer than 10 characters"&lt;/code&gt; → &lt;code&gt;min_length=11&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;"palindromic strings that contain the first vowel"&lt;/code&gt; → &lt;code&gt;is_palindrome=true&lt;/code&gt;, &lt;code&gt;contains_character=a&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If the query cannot be parsed or results in conflicting filters, the API returns &lt;code&gt;400 Bad Request&lt;/code&gt; or &lt;code&gt;422 Unprocessable Entity&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Delete String
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;DELETE /strings/{string_value}&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Users can remove a specific string from the system. If the string does not exist, a &lt;code&gt;404 Not Found&lt;/code&gt; is returned. Importantly, the endpoint ensures safety by not deleting all data if the parameter is missing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Development Highlights
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Data Validation and Type Safety
&lt;/h3&gt;

&lt;p&gt;Handling numeric filters such as &lt;code&gt;min_length&lt;/code&gt;, &lt;code&gt;max_length&lt;/code&gt;, and &lt;code&gt;word_count&lt;/code&gt; required strict integer validation. Using &lt;code&gt;Number&lt;/code&gt; combined with &lt;code&gt;Number.isInteger()&lt;/code&gt; ensured that the API rejected decimals, negative numbers, and non-numeric input while accepting valid integers.&lt;/p&gt;

&lt;h3&gt;
  
  
  Palindrome Detection and Character Analysis
&lt;/h3&gt;

&lt;p&gt;For palindromes, the service ignores case and spaces, providing an accurate boolean flag. Character frequency maps and unique character counts are computed efficiently using simple iteration over the string.&lt;/p&gt;

&lt;h3&gt;
  
  
  Hashing for Unique Identification
&lt;/h3&gt;

&lt;p&gt;SHA-256 hashing provides a reliable way to identify strings uniquely, which is especially useful for avoiding duplicates and quickly locating records.&lt;/p&gt;

&lt;h3&gt;
  
  
  Deployment Considerations
&lt;/h3&gt;

&lt;p&gt;Deploying on Railway introduced the common challenge of environment variable handling. Railway sometimes wraps values in quotes, which would break the MongoDB connection. This was handled by sanitizing the &lt;code&gt;MONGO_URI&lt;/code&gt; in the connection logic:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;mongoUri&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;MONGO_URI&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;mongoUri&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;"&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;mongoUri&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;endsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;"&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="nx"&gt;mongoUri&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;mongoUri&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;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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This approach ensured a smooth connection to MongoDB Atlas without manual intervention.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lessons Learned
&lt;/h2&gt;

&lt;p&gt;Building the String Analyzer Service reinforced several best practices:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Always validate and sanitize input, especially for numeric and string-based filters&lt;/li&gt;
&lt;li&gt;Use unique identifiers like hashes to prevent duplicate entries&lt;/li&gt;
&lt;li&gt;Implement comprehensive error handling to guide API users&lt;/li&gt;
&lt;li&gt;Consider deployment quirks like environment variable formatting early in development&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;This project demonstrates how a well-structured backend service can provide both analytical functionality and robust, user-friendly endpoints. The combination of computed string properties, filtering capabilities, and natural language interpretation makes it a versatile tool for string analysis. Deploying to Railway and ensuring smooth operation in a cloud environment highlighted real-world considerations that are crucial for any backend engineer.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/Humphery7/String-Analyzer-Service" rel="noopener noreferrer"&gt;Github code&lt;/a&gt;&lt;/p&gt;

</description>
      <category>programming</category>
      <category>backend</category>
      <category>node</category>
      <category>hng</category>
    </item>
    <item>
      <title>Building a Dynamic Profile Endpoint</title>
      <dc:creator>Humphery </dc:creator>
      <pubDate>Sat, 18 Oct 2025 20:01:57 +0000</pubDate>
      <link>https://dev.to/humphery7/building-a-dynamic-profile-endpoint-ona</link>
      <guid>https://dev.to/humphery7/building-a-dynamic-profile-endpoint-ona</guid>
      <description>&lt;p&gt;&lt;em&gt;By Otuoniyo Ufuoma-Oghene Humphery&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;As part of the &lt;strong&gt;HNG 2025 Backend Wizards program&lt;/strong&gt;, I recently completed &lt;strong&gt;Stage 0&lt;/strong&gt;, where the task was to build a &lt;strong&gt;dynamic profile endpoint&lt;/strong&gt;. The goal was simple yet powerful: create a RESTful API endpoint that returns my profile information along with a &lt;strong&gt;random cat fact fetched from an external API&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This task taught me not just the technicalities of building endpoints, but also how to &lt;strong&gt;consume third-party APIs, handle dynamic data, and structure JSON responses&lt;/strong&gt; professionally.&lt;/p&gt;




&lt;h2&gt;
  
  
  Task Overview
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Requirements:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create a &lt;strong&gt;GET&lt;/strong&gt; endpoint at &lt;code&gt;/me&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Return JSON with &lt;code&gt;Content-Type: application/json&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Include a &lt;strong&gt;dynamic cat fact&lt;/strong&gt; fetched from &lt;a href="https://catfact.ninja/fact" rel="noopener noreferrer"&gt;Cat Facts API&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;JSON response format:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"success"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"user"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"email"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;your email&amp;gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;your full name&amp;gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"stack"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;your backend stack&amp;gt;"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"timestamp"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;current UTC time in ISO 8601 format&amp;gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"fact"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;random cat fact from Cat Facts API&amp;gt;"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Handle API errors gracefully.&lt;/li&gt;
&lt;li&gt;Follow &lt;strong&gt;best practices&lt;/strong&gt; for code structure, environment variables, and logging.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  My Work Process
&lt;/h2&gt;

&lt;p&gt;Here’s how I approached this task step by step:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Setting Up the Server
&lt;/h3&gt;

&lt;p&gt;I used &lt;strong&gt;Node.js and Express&lt;/strong&gt; for this project. First, I initialized the server and created a simple route handler for &lt;code&gt;/me&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;app&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;/me&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;getUsers&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Fetching Dynamic Cat Facts
&lt;/h3&gt;

&lt;p&gt;I implemented a &lt;code&gt;fetchWithTimeout&lt;/code&gt; utility to safely fetch data from the Cat Facts API, handling timeouts and network errors:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;catFactJson&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;fetchWithTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;CAT_API_URL&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;catFact&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;catFactJson&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;fact&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Could not fetch a cat fact right now.&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;p&gt;This ensures the endpoint &lt;strong&gt;never crashes&lt;/strong&gt;, even if the external API is down.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Formatting the JSON Response
&lt;/h3&gt;

&lt;p&gt;Next, I structured the JSON response to strictly match the Stage 0 requirements:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;success&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;humpheryufuoma@gmail.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Otuoniyo Ufuoma-Oghene Humphery&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;stack&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Node.js/Express&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;timestamp&lt;/span&gt;&lt;span class="p"&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="nf"&gt;toISOString&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="na"&gt;fact&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;catFact&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I made sure the &lt;code&gt;timestamp&lt;/code&gt; was &lt;strong&gt;dynamic and in UTC ISO 8601 format&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Environment Variables
&lt;/h3&gt;

&lt;p&gt;For local development, I used &lt;code&gt;.env&lt;/code&gt; to store the &lt;strong&gt;Cat Facts API URL&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;CAT_API_URL=https://catfact.ninja/fact
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Note: Railway automatically adds quotes to environment variables, so in production I strip them in code before using the URL.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  5. Handling Errors Gracefully
&lt;/h3&gt;

&lt;p&gt;I added error handling to ensure a &lt;strong&gt;fallback cat fact&lt;/strong&gt; is returned if the API fails:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;error&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Failed to fetch cat fact&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This guarantees a smooth user experience at all times.&lt;/p&gt;




&lt;h2&gt;
  
  
  Lessons Learned
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Environment variables&lt;/strong&gt; are crucial for separating config from code, but deployment platforms like Railway may add quirks (like automatic quotes) that you need to handle.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dynamic data fetching&lt;/strong&gt; requires proper error handling — always expect the external API to fail occasionally.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;JSON schema validation&lt;/strong&gt; is important; even small typos in field names can break automated checks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Small tasks teach discipline&lt;/strong&gt;: Stage 0 reinforced best practices in endpoint design, logging, and clean code.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Screenshots / GIFs
&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%2Fgl5lk81o6ipx6is1cefu.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%2Fgl5lk81o6ipx6is1cefu.png" alt="Response test on postman" width="800" height="268"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Outcome
&lt;/h2&gt;

&lt;p&gt;The final endpoint returns JSON like this:&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="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"success"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"user"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"email"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"humpheryufuoma@gmail.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Otuoniyo Ufuoma-Oghene Humphery"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"stack"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Node.js/Express"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"timestamp"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2025-10-18T19:50:29.028Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"fact"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"A cat usually has about 12 whiskers on each side of its face."&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Meets all Stage 0 requirements.&lt;/li&gt;
&lt;li&gt;Dynamic timestamp and cat fact. &lt;/li&gt;
&lt;li&gt;Graceful error handling.&lt;/li&gt;
&lt;li&gt;Ready for deployment on Railway.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Stage 0 of &lt;strong&gt;HNG Backend Wizards&lt;/strong&gt; was packed with learning: working with &lt;strong&gt;REST APIs, dynamic data, and deployment quirks&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;I’m excited to carry these best practices forward into &lt;strong&gt;Stage 1 and beyond&lt;/strong&gt;, building more complex backend systems that are robust, maintainable, and dynamic.&lt;/p&gt;




&lt;h2&gt;
  
  
  GitHub Repository
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/Humphery7/Dynamic-Profile-Endpoint" rel="noopener noreferrer"&gt;View the code on GitHub&lt;/a&gt;&lt;/p&gt;




</description>
      <category>backend</category>
      <category>hng</category>
      <category>programming</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Introduction to Using Supabase with Node.js: Build a Simple Todo App</title>
      <dc:creator>Humphery </dc:creator>
      <pubDate>Wed, 24 Sep 2025 08:34:52 +0000</pubDate>
      <link>https://dev.to/humphery7/introduction-to-using-supabase-with-nodejs-build-a-simple-todo-app-2210</link>
      <guid>https://dev.to/humphery7/introduction-to-using-supabase-with-nodejs-build-a-simple-todo-app-2210</guid>
      <description>&lt;p&gt;Hey there! 👋 Today, we’re going to have some fun building a &lt;strong&gt;simple Todo app&lt;/strong&gt; using &lt;strong&gt;Supabase&lt;/strong&gt; and &lt;strong&gt;Node.js&lt;/strong&gt;. Don’t worry if you’re a beginner, I’ll explain everything step by step!&lt;/p&gt;




&lt;h2&gt;
  
  
  What is Supabase?
&lt;/h2&gt;

&lt;p&gt;Supabase is an open-source platform that provides:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A real-time database &lt;/li&gt;
&lt;li&gt;Authentication for users
&lt;/li&gt;
&lt;li&gt;Storage for files
&lt;/li&gt;
&lt;li&gt;Serverless functions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It helps you build backend features quickly without writing everything from scratch.&lt;/p&gt;




&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;Before we start, make sure you have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Node.js installed&lt;/li&gt;
&lt;li&gt;Basic knowledge of JavaScript&lt;/li&gt;
&lt;li&gt;A Supabase account (sign up free at &lt;a href="https://supabase.com" rel="noopener noreferrer"&gt;supabase.com&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Step 1: Create a Supabase Project
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Go to the Supabase dashboard.&lt;/li&gt;
&lt;li&gt;Click “New Project”.&lt;/li&gt;
&lt;li&gt;Give it a name, pick a password, and wait for it to create.&lt;/li&gt;
&lt;li&gt;Once created, go to Settings → API and copy:&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Project URL&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Anon key&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We’ll need these in our code!&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 2: Create a Table
&lt;/h2&gt;

&lt;p&gt;We need a table to store our todos.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Go to &lt;strong&gt;Database → Tables → New Table&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Call it &lt;code&gt;todos&lt;/code&gt; and add columns:&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Column Name&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;id&lt;/td&gt;
&lt;td&gt;uuid&lt;/td&gt;
&lt;td&gt;Primary key, default &lt;code&gt;gen_random_uuid()&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;task&lt;/td&gt;
&lt;td&gt;text&lt;/td&gt;
&lt;td&gt;The todo description&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;inserted_at&lt;/td&gt;
&lt;td&gt;timestamptz&lt;/td&gt;
&lt;td&gt;Default &lt;code&gt;now()&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Save the table, and we’re ready to code! 🎉&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%2F2870umjl4edomui8m8is.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%2F2870umjl4edomui8m8is.png" alt="Screenshot of the Supabase todos table with columns id, task, and inserted_at" width="800" height="440"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Here we see the todos table in Supabase with columns id, task, and inserted_at.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 3: Setup Node.js Project
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Open a terminal and create a folder:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir &lt;/span&gt;supabase-todo
&lt;span class="nb"&gt;cd &lt;/span&gt;supabase-todo
npm init &lt;span class="nt"&gt;-y&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Install &lt;strong&gt;Supabase JS&lt;/strong&gt;:
&lt;/li&gt;
&lt;/ol&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; @supabase/supabase-js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Step 4: Write Your First Script
&lt;/h2&gt;

&lt;p&gt;Create a file called &lt;code&gt;index.js&lt;/code&gt; and paste this code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Import Supabase&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;supabase&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@supabase/supabase-js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;createClient&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Replace with your own URL + anon key&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;supabaseUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://YOUR_PROJECT_URL.supabase.co&lt;/span&gt;&lt;span class="dl"&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;supabaseKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;YOUR_ANON_KEY&lt;/span&gt;&lt;span class="dl"&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;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;supabase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;supabaseUrl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;supabaseKey&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Insert a todo&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;addTodo&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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="p"&gt;}&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;client&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;todos&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;insert&lt;/span&gt;&lt;span class="p"&gt;([{&lt;/span&gt; &lt;span class="na"&gt;task&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Learn Supabase basics&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}]);&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Insert:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Fetch all todos&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;fetchTodos&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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="p"&gt;}&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;client&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;todos&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;select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&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;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Todos:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Run example&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="o"&gt;=&amp;gt;&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;addTodo&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;fetchTodos&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;
  
  
  Step 5: Run Your Code
&lt;/h2&gt;

&lt;p&gt;In your terminal, run:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;If everything works, you’ll see something like this:&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="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"990e6e73-e1fa-4d0b-ab69-be85ebf192e6"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"task"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Learn Supabase basics"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"inserted_at"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2025-09-24T08:03:58.157706+00:00"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;The array shows your todos&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;null&lt;/code&gt; means no errors 🎉&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Step 6: Next Steps
&lt;/h2&gt;

&lt;p&gt;You can now:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Add more todos with &lt;code&gt;addTodo()&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Fetch and display them using &lt;code&gt;fetchTodos()&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Later, you can build a web or CLI app around it&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Supabase makes it super easy to expand your project. 🚀&lt;/p&gt;




&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Congrats! 🥳 You just built your &lt;strong&gt;first Supabase Todo app&lt;/strong&gt; using Node.js.&lt;/p&gt;

&lt;p&gt;Supabase + Node.js is a powerful combo for beginners and pros alike. Now you can explore authentication, storage, or even real-time updates.&lt;/p&gt;

&lt;p&gt;Go ahead, play around, and build something amazing!&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>node</category>
      <category>supabase</category>
      <category>programming</category>
    </item>
  </channel>
</rss>
