<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Sathwik's Blog]]></title><description><![CDATA[Sathwik's Blog]]></description><link>https://blog.sathwikreddygv.com</link><generator>RSS for Node</generator><lastBuildDate>Wed, 15 Apr 2026 19:46:26 GMT</lastBuildDate><atom:link href="https://blog.sathwikreddygv.com/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[From Prompt to Production with Lovable + Supabase]]></title><description><![CDATA[Imagine describing an app in plain English and having it fully built. Frontend, backend, database, security, and even AI features, all set up and deployed within minutes. That’s not a futuristic dream anymore. It’s what Lovable does when paired with ...]]></description><link>https://blog.sathwikreddygv.com/from-prompt-to-production-with-lovable-supabase</link><guid isPermaLink="true">https://blog.sathwikreddygv.com/from-prompt-to-production-with-lovable-supabase</guid><category><![CDATA[lovable]]></category><category><![CDATA[AI]]></category><category><![CDATA[#ai-tools]]></category><category><![CDATA[supabase]]></category><category><![CDATA[Edge-Functions]]></category><category><![CDATA[APIs]]></category><category><![CDATA[Prompt]]></category><category><![CDATA[Full Stack Development]]></category><category><![CDATA[full stack]]></category><category><![CDATA[Chatwithpdf]]></category><dc:creator><![CDATA[sathwikreddy GV]]></dc:creator><pubDate>Wed, 23 Jul 2025 13:05:08 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1753273757133/b83895b0-5b44-487d-aecb-b9374f88d2be.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Imagine describing an app in plain English and having it fully built. Frontend, backend, database, security, and even AI features, all set up and deployed within minutes. That’s not a futuristic dream anymore. It’s what Lovable does when paired with Supabase.</p>
<p>In this post, I’ll walk you through how Lovable turns natural language prompts into production-ready full-stack applications using Supabase as the infrastructure backbone. If you're building MVPs, this workflow changes everything.</p>
<h3 id="heading-the-setup-connecting-lovable-to-supabase">The Setup: Connecting Lovable to Supabase</h3>
<p>Lovable connects directly to your Supabase project using the project reference and API keys. You don’t need to manually configure database URLs, credentials, or CLI tools. Once connected, every schema change, edge function, and Row Level Security (RLS) policy is automatically deployed through Supabase's CLI, keeping your environment in sync as your app evolves. You can find the option to connect on the top right in lovable as shown in the image.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1753274693493/27ee78e5-f042-44dc-8901-2858856756bd.png" alt class="image--center mx-auto" /></p>
<h3 id="heading-designing-the-database-with-just-a-prompt">Designing the Database with Just a Prompt</h3>
<p>When you describe a feature, Lovable analyzes the underlying data needs and creates the necessary schema. It builds tables with proper relationships, sets up RLS policies, and even adds performance-enhancing indexes and foreign key constraints. All of this happens through natural language-to-SQL conversion, eliminating the need to write a single line of database code. Lovable asks you to review the Sql and then executes after your approval which is a great feature.</p>
<p>What stands out is how it applies <strong>Row Level Security (RLS)</strong> policies by default. Many developers skip this in early stages and expose sensitive data. Lovable ensures that doesn’t happen.</p>
<h3 id="heading-writing-and-deploying-edge-functions">Writing and Deploying Edge Functions</h3>
<p>If your app needs custom API logic, say uploading PDFs or triggering AI models, Lovable generates Supabase Edge Functions written in TypeScript. These are production-grade by default: they include proper CORS headers, error handling, and secrets management.</p>
<p>Within seconds, they’re deployed globally on Supabase’s edge network, ready to serve requests.</p>
<p>Here’s an example of an edge function generated by lovable in supabase</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1753275069773/f70e9c17-c50c-47b8-80f2-4d482de782b4.png" alt class="image--center mx-auto" /></p>
<p>As the database evolves, Lovable syncs the types across your frontend and backend using TypeScript. This means you can rely on compile-time safety rather than chasing runtime bugs. Changes to your schema are reflected instantly in your React components, ensuring both consistency and developer confidence.</p>
<h3 id="heading-adding-vector-search-and-ai-in-one-step">Adding Vector Search and AI in One Step</h3>
<p>In this project, a PDF-based AI chatbot, Lovable blew me away.</p>
<p>When I mentioned “AI chatbot” in the prompt, it automatically enabled the <code>pgvector</code> extension, created embedding columns, and hooked it up with OpenAI's embeddings API. It even created similarity search functions and integrated them into the app logic. The image below shows the table with vector embeddings stored.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1753275201156/ebc4fc11-a0a8-4bb9-bff9-0375a0ac4d79.png" alt class="image--center mx-auto" /></p>
<p>This level of abstraction makes advanced AI features accessible.</p>
<h3 id="heading-making-smart-architectural-choices">Making Smart Architectural Choices</h3>
<p>Lovable doesn't just generate code, it understands when and why to make specific architectural decisions. For instance, it knows when to move logic to edge functions vs keeping it on the client. It structures your database thoughtfully, avoids redundant queries, and chooses performant indexes based on your use case.</p>
<p>File uploads? It sets up Supabase Storage buckets, defines the right access rules, and configures CDN delivery without you needing to touch a dashboard.</p>
<p>Lovable also takes care of integrating external services from OpenAI to Stripe and Twilio. I does things like secret management, auth flow handling, error retries, and rate limiting strategies for you. You don’t just get the integration, you get a maintainable implementation that’s ready to scale.</p>
<h3 id="heading-conclusion">Conclusion</h3>
<p>Lovable + Supabase changes how MVPs are built. Instead of stitching together backend, auth, storage, and APIs, you describe your idea, and get a working app, fast.</p>
<p>For founders and small teams, it means faster launches, fewer distractions, and more time to validate what matters. If you’ve been stuck on setup, maybe all you need is the right prompt.</p>
]]></content:encoded></item><item><title><![CDATA[Build a RAG System Using Supabase and Lovable]]></title><description><![CDATA[What is RAG?
RAG stands for Retrieval-Augmented Generation.
Instead of hoping your LLM (Large Language Model) remembers everything, RAG retrieves the most relevant information in real-time and feeds that context into the model to generate an accurate...]]></description><link>https://blog.sathwikreddygv.com/build-a-rag-system-using-supabase-and-lovable</link><guid isPermaLink="true">https://blog.sathwikreddygv.com/build-a-rag-system-using-supabase-and-lovable</guid><category><![CDATA[RAG ]]></category><category><![CDATA[AI]]></category><category><![CDATA[AI Context Management]]></category><category><![CDATA[Chatwithpdf]]></category><category><![CDATA[Lovable ai]]></category><category><![CDATA[supabase]]></category><dc:creator><![CDATA[sathwikreddy GV]]></dc:creator><pubDate>Tue, 22 Jul 2025 09:11:19 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1753175352004/5888a8a1-4d00-4abe-991f-22b614bda099.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-what-is-rag">What is RAG?</h2>
<p>RAG stands for <strong>Retrieval-Augmented Generation</strong>.</p>
<p>Instead of hoping your LLM (Large Language Model) remembers everything, RAG retrieves the <strong>most relevant information in real-time</strong> and feeds that context into the model to generate an accurate response.</p>
<p>Think of it as combining <strong>search + AI</strong>, a Google-like brain with human-like understanding.</p>
<p>Here’s how I built a simple chat with pdf app using Lovable and Supabase.</p>
<h2 id="heading-the-architecture">The Architecture</h2>
<p>Here’s the tech stack I used:</p>
<ul>
<li><p><strong>Frontend</strong>: React (with a streaming chat interface)</p>
</li>
<li><p><strong>Backend</strong>: Supabase Edge Functions (powered by Deno)</p>
</li>
<li><p><strong>Database</strong>: PostgreSQL with <code>pgvector</code></p>
</li>
<li><p><strong>AI</strong>: OpenAI Embeddings + GPT-4o-mini</p>
</li>
</ul>
<p>The entire system works in <strong>three stages</strong>:</p>
<ol>
<li><p>Ingest</p>
</li>
<li><p>Store</p>
</li>
<li><p>Retrieve</p>
</li>
</ol>
<p>Let’s walk through each of them.</p>
<hr />
<h2 id="heading-stage-1-document-processing">Stage 1: Document Processing</h2>
<p>Whenever a user uploads a PDF:</p>
<ol>
<li><p>I extract the text using any text extraction package.</p>
</li>
<li><p>Then, I split that text into manageable chunks, around <strong>500 characters</strong> per chunk.</p>
</li>
<li><p>Finally, I generate <strong>embeddings</strong> for each chunk.</p>
</li>
</ol>
<h3 id="heading-what-are-embeddings">What are embeddings?</h3>
<p>Embeddings convert text into a vector of numbers that represent its meaning. This lets the system measure similarity between chunks of text, even if they use different words.</p>
<p>I use <strong>OpenAI’s Embedding API</strong> for this step. It’s simple, fast, and highly accurate.</p>
<hr />
<h2 id="heading-stage-2-storing-embeddings">Stage 2: Storing Embeddings</h2>
<p>This is where <strong>pgvector</strong> comes in.</p>
<p>Supabase supports the <code>pgvector</code> extension, which allows you to store and search high-dimensional vectors right inside your PostgreSQL database.</p>
<p>Each text chunk and its corresponding embedding are stored as a row in the database. This gives you full control over your knowledge base, and there’s no need for external vector DBs.</p>
<hr />
<h2 id="heading-stage-3-smart-retrieval">Stage 3: Smart Retrieval</h2>
<p>Now the fun part, asking questions and getting smart answers.</p>
<p>Here’s what happens when a user asks a question:</p>
<ol>
<li><p>The question is converted into an embedding.</p>
</li>
<li><p>A <strong>vector similarity search</strong> is run on the database.</p>
</li>
<li><p>The top 5 most relevant chunks are retrieved.</p>
</li>
<li><p>These chunks are sent as <strong>context</strong> to the LLM.</p>
</li>
</ol>
<hr />
<h2 id="heading-stage-4-response-generation">Stage 4: Response Generation</h2>
<p>The retrieved chunks are merged into a single context string.</p>
<p>That context, along with the user’s original question, is sent to <strong>GPT-4o-mini</strong>.</p>
<p>The response is streamed back to the frontend in real time, creating a smooth, chat-like experience.</p>
<hr />
<h2 id="heading-why-this-setup-works">Why This Setup Works</h2>
<ul>
<li><p><strong>No fine-tuning required</strong> — it adapts to any document.</p>
</li>
<li><p><strong>Highly accurate</strong> — thanks to embedding-based context.</p>
</li>
<li><p><strong>Real-time streaming</strong> — fast responses, no waiting.</p>
</li>
<li><p><strong>Scalable and cheap</strong> — built on Supabase + OpenAI.</p>
</li>
<li><p><strong>Prompt-powered</strong> — easy to evolve using Lovable.dev.</p>
</li>
</ul>
<hr />
<h2 id="heading-the-result">The Result</h2>
<p>What you get is a RAG system that:</p>
<ul>
<li><p>Understands your documents deeply.</p>
</li>
<li><p>Answers questions using real, relevant context.</p>
</li>
<li><p>Streams responses instantly.</p>
</li>
<li><p>Scales effortlessly.</p>
</li>
<li><p>Costs <strong>pennies per query</strong>.</p>
</li>
</ul>
<p>I’ll soon be sharing a follow-up on how you can do <strong>all of this</strong> using just <strong>prompts</strong> with Lovable.dev and Supabase, no complex backend required.</p>
<p><strong>Subscribe to newsletter</strong> <strong>so that you don’t miss it!</strong></p>
]]></content:encoded></item><item><title><![CDATA[WTF is MCP ?]]></title><description><![CDATA[What is MCP and why it matters
MCP is a protocol which standardizes how applications provide context to LLMs. MCP basically allows us to give our LLMs access to various external systems.
Imagine you're building a web application with Supabase as your...]]></description><link>https://blog.sathwikreddygv.com/wtf-is-mcp</link><guid isPermaLink="true">https://blog.sathwikreddygv.com/wtf-is-mcp</guid><category><![CDATA[mcp]]></category><category><![CDATA[Model Context Protocol]]></category><category><![CDATA[cursor]]></category><category><![CDATA[AI]]></category><category><![CDATA[#ai-tools]]></category><dc:creator><![CDATA[sathwikreddy GV]]></dc:creator><pubDate>Sat, 15 Mar 2025 10:09:09 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1741361792056/dd1bd9b2-998d-4e0e-af8a-32f46bf6e5ba.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-what-is-mcp-and-why-it-matters"><strong>What is MCP and why it matters</strong></h2>
<p>MCP is a protocol which standardizes how applications provide context to LLMs. MCP basically allows us to give our LLMs access to various external systems.</p>
<p>Imagine you're building a web application with <strong>Supabase</strong> as your database. When you need to create tables or run migrations, let’s say you asked an LLM to generate the SQL, then manually executed it.</p>
<p>But what if the LLM could <strong>run those migrations for you</strong>?</p>
<p>That’s where MCP comes in! With AI tools that support MCP—like <strong>Cursor, Windsurf, and Claude Desktop</strong>—you can let the LLM interact with your systems directly.</p>
<h2 id="heading-mcp-architecture">MCP Architecture</h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1741635115794/7422d69a-19ef-40ca-b424-c1f4e226f65f.png" alt class="image--center mx-auto" /></p>
<p>MCP follows a <strong>client-server architecture</strong> with four key components:</p>
<ul>
<li><p><strong>Hosts</strong> – AI tools like Cursor, Claude, etc.</p>
</li>
<li><p><strong>Clients</strong> – Applications that stay connected to MCP servers via hosts.</p>
</li>
<li><p><strong>MCP Servers</strong> – Provide context to clients by linking to data sources or external services.</p>
</li>
<li><p><strong>Data Sources</strong> – The actual databases, or remote services that MCP servers interact with.</p>
</li>
</ul>
<h2 id="heading-how-to-use-mcp">How to use MCP</h2>
<p>Let’s take cursor as an example. You can find an MCP section in cursor settings where you can add your MCP. For supabase, you can just add an MCP server by passing the CLI command <code>npx -y @modelcontextprotocol/server-postgres &lt;connection-string&gt;</code> . You can read more about this <a target="_blank" href="https://supabase.com/docs/guides/getting-started/mcp">here</a></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1741977752809/bd88ecb1-0771-4c40-b4b9-118b5d55f9d5.png" alt class="image--center mx-auto" /></p>
<p>Now you can ask the cursor chat to query your db or run any migrations in your supabase project and it does it for you.</p>
<h2 id="heading-how-to-build-an-mcp-server">How to build an MCP server</h2>
<p>We’ve seen how to use an MCP server in tools like cursor but do we build one? There are SDKs for building MCP clients and servers in various programming languages like Python, Typescript etc. Let’s use the Typescript SDK in order to build a weather forecast MCP. This is the same example provided in <a target="_blank" href="http://modelcontextprotocol.io">modelcontextprotocol.io</a> for building an MCP server. I’ll try to simplify it here.</p>
<h3 id="heading-setting-up-the-project"><strong>Setting Up the Project</strong></h3>
<p>First, ensure you have <strong>Node.js</strong> and <strong>npm</strong> installed. Then, create a project directory and install the necessary dependencies:. <code>modelcontextprotocol/sdk</code> is the typescript SDK which I was talking about earlier and <code>zod</code> is used for schema validation in typescript.</p>
<pre><code class="lang-bash">mkdir weather
<span class="hljs-built_in">cd</span> weather
npm init -y
npm install @modelcontextprotocol/sdk zod
npm install -D @types/node typescript
</code></pre>
<h3 id="heading-configuring-the-project">Configuring the Project</h3>
<p>Add the following to your <code>package.json</code> . Adding <code>"type": "module"</code> means that <strong>Node.js will treat your JavaScript files as ECMAScript Modules (ESM)</strong> instead of CommonJS (CJS). This helps us in using modern ES module syntax like import/export in our code. The “build” script is to build our project at the end.</p>
<pre><code class="lang-json"><span class="hljs-string">"type"</span>: <span class="hljs-string">"module"</span>,
<span class="hljs-string">"scripts"</span>: {
    <span class="hljs-attr">"build"</span>: <span class="hljs-string">"tsc &amp;&amp; chmod 755 build/index.js"</span>
 }
</code></pre>
<p>Create a <code>tsconfig.json</code> and paste the following. This file configures TypeScript compilation settings:</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"compilerOptions"</span>: {
    <span class="hljs-attr">"target"</span>: <span class="hljs-string">"ES2022"</span>,
    <span class="hljs-attr">"module"</span>: <span class="hljs-string">"Node16"</span>,
    <span class="hljs-attr">"moduleResolution"</span>: <span class="hljs-string">"Node16"</span>,
    <span class="hljs-attr">"outDir"</span>: <span class="hljs-string">"./build"</span>,
    <span class="hljs-attr">"rootDir"</span>: <span class="hljs-string">"./src"</span>,
    <span class="hljs-attr">"strict"</span>: <span class="hljs-literal">true</span>,
    <span class="hljs-attr">"esModuleInterop"</span>: <span class="hljs-literal">true</span>,
    <span class="hljs-attr">"skipLibCheck"</span>: <span class="hljs-literal">true</span>,
    <span class="hljs-attr">"forceConsistentCasingInFileNames"</span>: <span class="hljs-literal">true</span>
  },
  <span class="hljs-attr">"include"</span>: [<span class="hljs-string">"src/**/*"</span>],
  <span class="hljs-attr">"exclude"</span>: [<span class="hljs-string">"node_modules"</span>]
}
</code></pre>
<p>Now add this code in <code>src/index.ts</code></p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { McpServer } <span class="hljs-keyword">from</span> <span class="hljs-string">"@modelcontextprotocol/sdk/server/mcp.js"</span>;
<span class="hljs-keyword">import</span> { StdioServerTransport } <span class="hljs-keyword">from</span> <span class="hljs-string">"@modelcontextprotocol/sdk/server/stdio.js"</span>;
<span class="hljs-keyword">import</span> { z } <span class="hljs-keyword">from</span> <span class="hljs-string">"zod"</span>;

<span class="hljs-keyword">const</span> NWS_API_BASE = <span class="hljs-string">"https://api.weather.gov"</span>;
<span class="hljs-keyword">const</span> USER_AGENT = <span class="hljs-string">"weather-app/1.0"</span>;

<span class="hljs-comment">// Create server instance</span>
<span class="hljs-keyword">const</span> server = <span class="hljs-keyword">new</span> McpServer({
  name: <span class="hljs-string">"weather"</span>,
  version: <span class="hljs-string">"1.0.0"</span>,
});

<span class="hljs-comment">// Helper function for making NWS API requests</span>
<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">makeNWSRequest</span>&lt;<span class="hljs-title">T</span>&gt;(<span class="hljs-params">url: <span class="hljs-built_in">string</span></span>): <span class="hljs-title">Promise</span>&lt;<span class="hljs-title">T</span> | <span class="hljs-title">null</span>&gt; </span>{
    <span class="hljs-keyword">const</span> headers = {
      <span class="hljs-string">"User-Agent"</span>: USER_AGENT,
      Accept: <span class="hljs-string">"application/geo+json"</span>,
    };

    <span class="hljs-keyword">try</span> {
      <span class="hljs-keyword">const</span> response = <span class="hljs-keyword">await</span> fetch(url, { headers });
      <span class="hljs-keyword">if</span> (!response.ok) {
        <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">`HTTP error! status: <span class="hljs-subst">${response.status}</span>`</span>);
      }
      <span class="hljs-keyword">return</span> (<span class="hljs-keyword">await</span> response.json()) <span class="hljs-keyword">as</span> T;
    } <span class="hljs-keyword">catch</span> (error) {
      <span class="hljs-built_in">console</span>.error(<span class="hljs-string">"Error making NWS request:"</span>, error);
      <span class="hljs-keyword">return</span> <span class="hljs-literal">null</span>;
    }
  }

  <span class="hljs-keyword">interface</span> AlertFeature {
    properties: {
      event?: <span class="hljs-built_in">string</span>;
      areaDesc?: <span class="hljs-built_in">string</span>;
      severity?: <span class="hljs-built_in">string</span>;
      status?: <span class="hljs-built_in">string</span>;
      headline?: <span class="hljs-built_in">string</span>;
    };
  }

  <span class="hljs-comment">// Format alert data</span>
  <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">formatAlert</span>(<span class="hljs-params">feature: AlertFeature</span>): <span class="hljs-title">string</span> </span>{
    <span class="hljs-keyword">const</span> props = feature.properties;
    <span class="hljs-keyword">return</span> [
      <span class="hljs-string">`Event: <span class="hljs-subst">${props.event || <span class="hljs-string">"Unknown"</span>}</span>`</span>,
      <span class="hljs-string">`Area: <span class="hljs-subst">${props.areaDesc || <span class="hljs-string">"Unknown"</span>}</span>`</span>,
      <span class="hljs-string">`Severity: <span class="hljs-subst">${props.severity || <span class="hljs-string">"Unknown"</span>}</span>`</span>,
      <span class="hljs-string">`Status: <span class="hljs-subst">${props.status || <span class="hljs-string">"Unknown"</span>}</span>`</span>,
      <span class="hljs-string">`Headline: <span class="hljs-subst">${props.headline || <span class="hljs-string">"No headline"</span>}</span>`</span>,
      <span class="hljs-string">"---"</span>,
    ].join(<span class="hljs-string">"\n"</span>);
  }

  <span class="hljs-keyword">interface</span> ForecastPeriod {
    name?: <span class="hljs-built_in">string</span>;
    temperature?: <span class="hljs-built_in">number</span>;
    temperatureUnit?: <span class="hljs-built_in">string</span>;
    windSpeed?: <span class="hljs-built_in">string</span>;
    windDirection?: <span class="hljs-built_in">string</span>;
    shortForecast?: <span class="hljs-built_in">string</span>;
  }

  <span class="hljs-keyword">interface</span> AlertsResponse {
    features: AlertFeature[];
  }

  <span class="hljs-keyword">interface</span> PointsResponse {
    properties: {
      forecast?: <span class="hljs-built_in">string</span>;
    };
  }

  <span class="hljs-keyword">interface</span> ForecastResponse {
    properties: {
      periods: ForecastPeriod[];
    };
  }

  <span class="hljs-comment">// Register weather tools</span>
server.tool(
    <span class="hljs-string">"get-alerts"</span>,
    <span class="hljs-string">"Get weather alerts for a state"</span>,
    {
      state: z.string().length(<span class="hljs-number">2</span>).describe(<span class="hljs-string">"Two-letter state code (e.g. CA, NY)"</span>),
    },
    <span class="hljs-keyword">async</span> ({ state }) =&gt; {
      <span class="hljs-keyword">const</span> stateCode = state.toUpperCase();
      <span class="hljs-keyword">const</span> alertsUrl = <span class="hljs-string">`<span class="hljs-subst">${NWS_API_BASE}</span>/alerts?area=<span class="hljs-subst">${stateCode}</span>`</span>;
      <span class="hljs-keyword">const</span> alertsData = <span class="hljs-keyword">await</span> makeNWSRequest&lt;AlertsResponse&gt;(alertsUrl);

      <span class="hljs-keyword">if</span> (!alertsData) {
        <span class="hljs-keyword">return</span> {
          content: [
            {
              <span class="hljs-keyword">type</span>: <span class="hljs-string">"text"</span>,
              text: <span class="hljs-string">"Failed to retrieve alerts data"</span>,
            },
          ],
        };
      }

      <span class="hljs-keyword">const</span> features = alertsData.features || [];
      <span class="hljs-keyword">if</span> (features.length === <span class="hljs-number">0</span>) {
        <span class="hljs-keyword">return</span> {
          content: [
            {
              <span class="hljs-keyword">type</span>: <span class="hljs-string">"text"</span>,
              text: <span class="hljs-string">`No active alerts for <span class="hljs-subst">${stateCode}</span>`</span>,
            },
          ],
        };
      }

      <span class="hljs-keyword">const</span> formattedAlerts = features.map(formatAlert);
      <span class="hljs-keyword">const</span> alertsText = <span class="hljs-string">`Active alerts for <span class="hljs-subst">${stateCode}</span>:\n\n<span class="hljs-subst">${formattedAlerts.join(<span class="hljs-string">"\n"</span>)}</span>`</span>;

      <span class="hljs-keyword">return</span> {
        content: [
          {
            <span class="hljs-keyword">type</span>: <span class="hljs-string">"text"</span>,
            text: alertsText,
          },
        ],
      };
    },
  );

  server.tool(
    <span class="hljs-string">"get-forecast"</span>,
    <span class="hljs-string">"Get weather forecast for a location"</span>,
    {
      latitude: z.number().min(<span class="hljs-number">-90</span>).max(<span class="hljs-number">90</span>).describe(<span class="hljs-string">"Latitude of the location"</span>),
      longitude: z.number().min(<span class="hljs-number">-180</span>).max(<span class="hljs-number">180</span>).describe(<span class="hljs-string">"Longitude of the location"</span>),
    },
    <span class="hljs-keyword">async</span> ({ latitude, longitude }) =&gt; {
      <span class="hljs-comment">// Get grid point data</span>
      <span class="hljs-keyword">const</span> pointsUrl = <span class="hljs-string">`<span class="hljs-subst">${NWS_API_BASE}</span>/points/<span class="hljs-subst">${latitude.toFixed(<span class="hljs-number">4</span>)}</span>,<span class="hljs-subst">${longitude.toFixed(<span class="hljs-number">4</span>)}</span>`</span>;
      <span class="hljs-keyword">const</span> pointsData = <span class="hljs-keyword">await</span> makeNWSRequest&lt;PointsResponse&gt;(pointsUrl);

      <span class="hljs-keyword">if</span> (!pointsData) {
        <span class="hljs-keyword">return</span> {
          content: [
            {
              <span class="hljs-keyword">type</span>: <span class="hljs-string">"text"</span>,
              text: <span class="hljs-string">`Failed to retrieve grid point data for coordinates: <span class="hljs-subst">${latitude}</span>, <span class="hljs-subst">${longitude}</span>. This location may not be supported by the NWS API (only US locations are supported).`</span>,
            },
          ],
        };
      }

      <span class="hljs-keyword">const</span> forecastUrl = pointsData.properties?.forecast;
      <span class="hljs-keyword">if</span> (!forecastUrl) {
        <span class="hljs-keyword">return</span> {
          content: [
            {
              <span class="hljs-keyword">type</span>: <span class="hljs-string">"text"</span>,
              text: <span class="hljs-string">"Failed to get forecast URL from grid point data"</span>,
            },
          ],
        };
      }

      <span class="hljs-comment">// Get forecast data</span>
      <span class="hljs-keyword">const</span> forecastData = <span class="hljs-keyword">await</span> makeNWSRequest&lt;ForecastResponse&gt;(forecastUrl);
      <span class="hljs-keyword">if</span> (!forecastData) {
        <span class="hljs-keyword">return</span> {
          content: [
            {
              <span class="hljs-keyword">type</span>: <span class="hljs-string">"text"</span>,
              text: <span class="hljs-string">"Failed to retrieve forecast data"</span>,
            },
          ],
        };
      }

      <span class="hljs-keyword">const</span> periods = forecastData.properties?.periods || [];
      <span class="hljs-keyword">if</span> (periods.length === <span class="hljs-number">0</span>) {
        <span class="hljs-keyword">return</span> {
          content: [
            {
              <span class="hljs-keyword">type</span>: <span class="hljs-string">"text"</span>,
              text: <span class="hljs-string">"No forecast periods available"</span>,
            },
          ],
        };
      }

      <span class="hljs-comment">// Format forecast periods</span>
      <span class="hljs-keyword">const</span> formattedForecast = periods.map(<span class="hljs-function">(<span class="hljs-params">period: ForecastPeriod</span>) =&gt;</span>
        [
          <span class="hljs-string">`<span class="hljs-subst">${period.name || <span class="hljs-string">"Unknown"</span>}</span>:`</span>,
          <span class="hljs-string">`Temperature: <span class="hljs-subst">${period.temperature || <span class="hljs-string">"Unknown"</span>}</span>°<span class="hljs-subst">${period.temperatureUnit || <span class="hljs-string">"F"</span>}</span>`</span>,
          <span class="hljs-string">`Wind: <span class="hljs-subst">${period.windSpeed || <span class="hljs-string">"Unknown"</span>}</span> <span class="hljs-subst">${period.windDirection || <span class="hljs-string">""</span>}</span>`</span>,
          <span class="hljs-string">`<span class="hljs-subst">${period.shortForecast || <span class="hljs-string">"No forecast available"</span>}</span>`</span>,
          <span class="hljs-string">"---"</span>,
        ].join(<span class="hljs-string">"\n"</span>),
      );

      <span class="hljs-keyword">const</span> forecastText = <span class="hljs-string">`Forecast for <span class="hljs-subst">${latitude}</span>, <span class="hljs-subst">${longitude}</span>:\n\n<span class="hljs-subst">${formattedForecast.join(<span class="hljs-string">"\n"</span>)}</span>`</span>;

      <span class="hljs-keyword">return</span> {
        content: [
          {
            <span class="hljs-keyword">type</span>: <span class="hljs-string">"text"</span>,
            text: forecastText,
          },
        ],
      };
    },
  );

  <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">main</span>(<span class="hljs-params"></span>) </span>{
    <span class="hljs-keyword">const</span> transport = <span class="hljs-keyword">new</span> StdioServerTransport();
    <span class="hljs-keyword">await</span> server.connect(transport);
    <span class="hljs-built_in">console</span>.error(<span class="hljs-string">"Weather MCP Server running on stdio"</span>);
  }

  main().catch(<span class="hljs-function">(<span class="hljs-params">error</span>) =&gt;</span> {
    <span class="hljs-built_in">console</span>.error(<span class="hljs-string">"Fatal error in main():"</span>, error);
    process.exit(<span class="hljs-number">1</span>);
  });
</code></pre>
<p>This code sets up an MCP server using the Model Context Protocol SDK in TypeScript. It provides tools (<code>get-alerts</code>, <code>get-forecast</code>) to fetch weather alerts and forecasts from the NWS API based on user-inputted locations.</p>
<h3 id="heading-running-the-mcp-server">Running the MCP Server</h3>
<p>Now run <code>npm run build</code> in the terminal. This should create a build folder and index.js file in it.</p>
<p>We can now add this MCP server to cursor. In cursor setting, under MCP section, add an MCP server. The command to be used is <code>node &lt;path to build/index.js&gt;</code> . Looks the screenshot below, this is how it looks after successfully adding the MCP</p>
<ul>
<li><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1741981176725/b4855f7e-e8e1-404f-8a49-1b81303d3f3f.png" alt /></li>
</ul>
<p>Now we can ask Cursor to tell us the weather of any location is the US (because the MCP server uses the NWS API and only US locations are supported).</p>
<h2 id="heading-simplifying-ai-automation-with-pre-built-mcp-servers">Simplifying AI Automation with Pre-built MCP Servers</h2>
<p>Several platforms provide pre-built MCP servers, making it easy to integrate automation into your tools. Two notable ones are:</p>
<ul>
<li><p><a target="_blank" href="http://Smithery.ai">Smithery.ai</a></p>
</li>
<li><p><a target="_blank" href="http://Glama.ai">Glama.ai</a></p>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1742032108141/15916a19-04bc-4f99-9237-fdbf19ca4ba8.png" alt class="image--center mx-auto" /></p>
<p>Let’s take <strong>Smithery’s GitHub MCP server</strong> as an example. Instead of manually interacting with GitHub, you can:<br />✅ Add the MCP server to <strong>Cursor</strong> (or any AI-powered tool)<br />✅ Run the provided command with your <strong>GitHub Personal Access Token</strong><br />✅ Start issuing commands like <strong>creating repositories, pushing commits, or managing pull requests</strong>—just by asking the AI!</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>MCP unlocks a new level of <strong>automation</strong> by allowing LLMs to interact with <strong>databases, APIs, and other external services</strong> in real time. Whether it's managing Supabase migrations or fetching live weather data, MCP makes it possible—without manual intervention!</p>
<p>Want to explore more? Check out <a target="_blank" href="https://modelcontextprotocol.io"><strong>modelcontextprotocol.io</strong></a> for in-depth documentation. Thanks for reading and Happy coding!</p>
]]></content:encoded></item><item><title><![CDATA[Debugging OG Image Issues for Social Media]]></title><description><![CDATA[When sharing links on social platforms, you want your preview images to look crisp and load correctly. But I recently ran into issues where my OG image wasn’t showing properly on Twitter & WhatsApp while working fine on LinkedIn. Here’s what I learne...]]></description><link>https://blog.sathwikreddygv.com/debugging-og-image-issues-for-social-media</link><guid isPermaLink="true">https://blog.sathwikreddygv.com/debugging-og-image-issues-for-social-media</guid><category><![CDATA[twitter-card]]></category><category><![CDATA[openGraph]]></category><category><![CDATA[social media]]></category><category><![CDATA[Link Sharing]]></category><dc:creator><![CDATA[sathwikreddy GV]]></dc:creator><pubDate>Tue, 04 Mar 2025 05:03:40 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/m_HRfLhgABo/upload/51bc0ee7c67305548cb5336e300f441c.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>When sharing links on social platforms, you want your preview images to look crisp and load correctly. But I recently ran into issues where my <strong>OG image wasn’t showing properly on Twitter &amp; WhatsApp</strong> while working fine on LinkedIn. Here’s what I learned and how I fixed it.</p>
<hr />
<h2 id="heading-meta-tag-order-matters">Meta Tag Order Matters</h2>
<p>One key issue was that my <strong>Twitter meta tags were placed after the Open Graph (OG) tags</strong> in the index.html file. Twitter prefers its own meta tags to be defined <strong>before</strong> OG tags, or it might ignore them.</p>
<p><strong>Fix</strong> → Always define Twitter meta tags <strong>first</strong> in your <code>&lt;head&gt;</code> section.</p>
<hr />
<h2 id="heading-image-size-limits-amp-best-practices">Image Size Limits &amp; Best Practices</h2>
<p>Different platforms handle OG images differently:</p>
<ul>
<li><p><strong>WhatsApp:</strong> Requires images to be <strong>under 300KB</strong>, or they may not load.</p>
</li>
<li><p><strong>Twitter &amp; LinkedIn:</strong> Accept larger images, but Twitter prefers <strong>1200x630px</strong> for optimal display.</p>
</li>
</ul>
<p><strong>Fix</strong> → If your image isn’t appearing, try <strong>compressing it</strong> below 300KB</p>
<hr />
<h2 id="heading-cache-issues-amp-how-to-force-a-refresh">Cache Issues &amp; How to Force a Refresh</h2>
<p>Even after fixing the meta tags, changes <strong>weren’t reflecting</strong> on Twitter. That’s because social media platforms <strong>cache OG images</strong> aggressively.</p>
<p><strong>Fix</strong> → Add a <code>?v=1</code> (or any versioning parameter) to your OG image URL to force a refresh. Example:</p>
<pre><code class="lang-plaintext">htmlCopyEdit&lt;meta property="og:image" content="https://www.makemymvp.today/opengraph-image.png?v=1"&gt;
</code></pre>
<p>This tricks platforms into reloading the latest version of the image.</p>
<hr />
<h2 id="heading-final-debugging-checklist">Final Debugging Checklist</h2>
<p>If your OG image isn’t displaying correctly, check:<br />✅ <strong>Meta tag order</strong> (Twitter tags first)<br />✅ <strong>Image size &amp; format</strong> (compressed for WhatsApp)<br />✅ <strong>Cache refresh</strong> (<code>?v=1</code> trick)</p>
<p>After applying these fixes, my OG image now loads perfectly across Twitter, LinkedIn, and WhatsApp for <a target="_blank" href="https://makemymvp.today"><strong>makemymvp.today</strong></a>! 🚀</p>
<p>I hope this helps if you ever run into similar issues! Thanks for reading and Happing coding!</p>
]]></content:encoded></item><item><title><![CDATA[How does SSH actually work]]></title><description><![CDATA[What is SSH
Secure Shell (SSH) is a protocol used to safely connect and send commands to remote machines from your local machine. A common use is to connect to your deployment server hosted in the cloud and control it from your local machine. An SSH ...]]></description><link>https://blog.sathwikreddygv.com/how-does-ssh-actually-work</link><guid isPermaLink="true">https://blog.sathwikreddygv.com/how-does-ssh-actually-work</guid><category><![CDATA[ssh]]></category><category><![CDATA[https]]></category><category><![CDATA[command line]]></category><category><![CDATA[secure shell]]></category><category><![CDATA[encryption]]></category><dc:creator><![CDATA[sathwikreddy GV]]></dc:creator><pubDate>Fri, 29 Nov 2024 10:33:07 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/BcjdbyKWquw/upload/ace2ca79e41d62916e7eae56ef0368c9.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-what-is-ssh">What is SSH</h2>
<p>Secure Shell (SSH) is a protocol used to safely connect and send commands to remote machines from your local machine. A common use is to connect to your deployment server hosted in the cloud and control it from your local machine. An SSH command usually looks like this:</p>
<pre><code class="lang-bash">ssh user@&lt;IP_Address&gt;
</code></pre>
<p>Your server will have the SSH daemon (sshd) running on port 22, allowing you to connect to it. You can also connect using other ports with the -p flag like this: <code>ssh -p &lt;port_number&gt; user@&lt;IP_Address&gt;</code>. Make sure sshd is set to listen on that port number on your server. You can find the file <code>/etc/ssh/sshd_config</code> on your server to set the port numbers the daemon listens to.</p>
<p>With that explained, how does SSH actually work? Let’s explore it further.</p>
<h2 id="heading-how-does-ssh-work-internally">How does SSH work internally</h2>
<p>SSH is a protocol built on top of TCP/IP, similar to HTTPS. It encrypts traffic end-to-end using public key cryptography. When you run the command <code>ssh user@&lt;IP_Address&gt;</code>, the SSH client connects to the SSH server over port 22 using a basic TCP handshake. Then, the client and server exchange SSH protocol versions and decide which cryptographic algorithms to use, such as Diffie-Hellman or ECDH for key exchange and AES for encryption. During the key exchange, they establish a shared secret without directly transmitting it. Server authentication occurs on the client side, where the server sends its public key. The client checks this key against the <code>~/.ssh/known_hosts</code> file. If the key is not in the file, the user is prompted like the following to accept it.</p>
<pre><code class="lang-bash">The authenticity of host <span class="hljs-string">'178.111.18.112'</span> can<span class="hljs-string">'t be established.
ECDSA key fingerprint is SHA256:dsjdba4sa7iudybduiduo ad098bsmvs+ijO8Y.
This host key is known by the following other names/addresses:
    ~/.ssh/known_hosts:3: abc.xyz.com
Are you sure you want to continue connecting (yes/no/[fingerprint])?</span>
</code></pre>
<p>You may have seen this prompt on your terminal if you've ever tried to SSH into a server.</p>
<p>Once the shared session key is established, all further communication is encrypted using a symmetric encryption algorithm like AES, which was agreed upon earlier. The server may also prompt for a password to authenticate the user.<br />That's it! An SSH connection is established between the client and server, allowing the client to execute commands on the remote server.</p>
<h2 id="heading-how-is-it-different-from-https">How is it different from HTTPS</h2>
<p>HTTPS is also a protocol built on top of TCP/IP that uses encryption. So, what's the difference? Unlike SSH, HTTPS uses SSL/TLS certificates for authentication. It's important to note that HTTPS also encrypts traffic like SSH. However, HTTPS is designed to be stateless, while SSH maintains a state (such as the current working directory). It's easier for a client to mistakenly trust a fake SSL/TLS certificate from a web server than to trust a public key from an SSH server, because the client is immediately alerted about unknown hosts. HTTPS is designed to handle web traffic and is understood by browsers, whereas SSH is a command-line-based protocol. These are some differences between SSH and HTTPS.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>There you have it! Now you understand how SSH works internally and how it differs from HTTPS. You can learn more about SSH by visiting this <a target="_blank" href="https://www.cloudflare.com/en-gb/learning/access-management/what-is-ssh/">link</a>. Thanks for reading, and happy coding!</p>
]]></content:encoded></item><item><title><![CDATA[Sharding vs Partitioning: What’s the Difference?]]></title><description><![CDATA[Introduction
In a recent interview, I was asked about the difference between Sharding and Partitioning in the context of databases, and I couldn't answer properly. In this blog, we will learn what are they and the difference between them.
Why “Shard ...]]></description><link>https://blog.sathwikreddygv.com/sharding-vs-partitioning-whats-the-difference</link><guid isPermaLink="true">https://blog.sathwikreddygv.com/sharding-vs-partitioning-whats-the-difference</guid><category><![CDATA[sharding]]></category><category><![CDATA[Database Partitioning]]></category><category><![CDATA[Databases]]></category><category><![CDATA[backend]]></category><category><![CDATA[database scaling]]></category><dc:creator><![CDATA[sathwikreddy GV]]></dc:creator><pubDate>Wed, 25 Sep 2024 13:33:41 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1727142415648/7a3a8761-2d92-470d-aa98-b4632af9dc19.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-introduction">Introduction</h2>
<p>In a recent interview, I was asked about the difference between Sharding and Partitioning in the context of databases, and I couldn't answer properly. In this blog, we will learn what are they and the difference between them.</p>
<h2 id="heading-why-shard-or-partition-a-database">Why “Shard or Partition” a Database?</h2>
<p>The terms Sharding and Partitioning come into play when you have to scale your database. When there is a lot of traffic coming to access your database, you will need to either Shard or Partition your database to make your queries faster and serve requests faster. Both of them are database partitioning techniques. Now let’s look at them individually.</p>
<h2 id="heading-partitioning">Partitioning</h2>
<p>Database partitioning involves dividing the data in an application's database into distinct segments, or partitions. Partioning can be of different types like Horizontal partitioning, Vertical partitioning, Range partioning etc..</p>
<p>Horizontal partitioning is when you divide a table by rows. Vertical partitioning is when you divide columns into different tables. You can choose the type of partitioning based on your query patterns. For example, you might choose vertical partitioning if your queries only access specific columns in a table with many columns. By splitting the table into two different tables, you reduce the amount of data read from the disk, which can significantly speed up queries. You can learn about all types of partitioning <a target="_blank" href="https://www.geeksforgeeks.org/data-partitioning-techniques/">here</a>.</p>
<h2 id="heading-sharding">Sharding</h2>
<p>Database sharding is the process of distributing a large database across multiple machines to improve performance and scalability. Each shard is a partition, but sharding specifically focuses on distributing these partitions across different physical nodes to handle large-scale data more efficiently.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1727233699222/67cad667-6d81-4060-a3d1-ae97db76ce62.png" alt class="image--center mx-auto" /></p>
<p>It is important to note that the data is stored across multiple database servers which are called shards.</p>
<p>For example, if you have user data in two different countries, you can shard the data based on the location and locate the servers in the respective countries. This makes the queries faster for both the countries.</p>
<h2 id="heading-comparison">Comparison</h2>
<p>Sharding can be considered a subset of partitioning (specifically, horizontal partitioning), while partitioning is a broader concept that refers to dividing data into smaller chunks. Sharding always involves storing data across multiple database servers, whereas partitioning does not necessarily mean that.</p>
<p>Now you know the differences between sharding and partitioning. Thanks for reading, and happy coding!</p>
]]></content:encoded></item><item><title><![CDATA[WTF is multipart/form-data in HTTP]]></title><description><![CDATA[Introduction
I've always wondered how files are sent to the server in an HTTP request. Whenever I need to create a UI for file upload on the frontend and send it to the backend using an API call, I set the content-type to multipart/form-data in my HT...]]></description><link>https://blog.sathwikreddygv.com/wtf-is-multipartform-data-in-http</link><guid isPermaLink="true">https://blog.sathwikreddygv.com/wtf-is-multipartform-data-in-http</guid><category><![CDATA[multipart/form-data]]></category><category><![CDATA[multipart upload]]></category><category><![CDATA[formdata]]></category><category><![CDATA[http]]></category><category><![CDATA[http-headers]]></category><dc:creator><![CDATA[sathwikreddy GV]]></dc:creator><pubDate>Sat, 24 Aug 2024 10:30:23 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1724120734130/0aaf8d2b-98c2-4dac-8213-ab3e3ae3701e.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-introduction">Introduction</h2>
<p>I've always wondered how files are sent to the server in an HTTP request. Whenever I need to create a UI for file upload on the frontend and send it to the backend using an API call, I set the content-type to <code>multipart/form-data</code> in my HTTP headers. But what is <code>multipart/form-data</code> and how does it actually send the data to the server? Let's dive deep into it...</p>
<h2 id="heading-handling-form-data">Handling form data</h2>
<p>What is form data? Form data is a format for sending data from a web form. Let's say you have a form in your application that a user needs to fill out and submit, which essentially makes a POST request. When you make a POST request, you need to encode the data that forms the body of the request in some way. HTML forms provide three methods of encoding.</p>
<ul>
<li><p><code>application/x-www-form-urlencoded</code> (the default)</p>
</li>
<li><p><code>multipart/form-data</code></p>
</li>
<li><p><code>text/plain</code></p>
</li>
</ul>
<p><code>text/plain</code> is rarely used because it doesn't encode special characters and has issues with new lines (read more about it <a target="_blank" href="https://stackoverflow.com/questions/7628249/method-post-enctype-text-plain-are-not-compatible">here</a>). <code>application/x-www-form-urlencoded</code> is the default encoding when you use the <code>&lt;form/&gt;</code> tag in your HTML code unless you specify the enctype as <code>multipart/form-data</code>. It is recommended to use <code>multipart/form-data</code> when the form data includes complex data like files, binary data, or non-ASCII characters. Otherwise, <code>application/x-www-form-urlencoded</code> is fast and efficient for small and simple data but not suitable for file uploads.</p>
<p>Don't worry about having a &lt;form/&gt; tag in your code whenever you have to upload files in the frontend. You can always use <code>let fd = new FormData()</code> and append whatever data you wanna send.</p>
<h2 id="heading-structure-of-multipartform-data">Structure of multipart/form-data</h2>
<p>Let's examine the structure of multipart/form-data by setting up a simple loop that listens for incoming connections on port 8002 using <code>netcat</code> (<code>nc</code>).</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1724205665297/eef87072-b606-464d-90fa-273857ac012f.png" alt class="image--center mx-auto" /></p>
<p>The command will continuously listen for connections on port <code>8002</code>. When a connection is made, it doesn't send any data (<code>printf ''</code> sends an empty string), and the loop restarts, ready to accept a new connection. Now, let's make a connection and send a file to the server at port 8002 using the command <code>curl -F secret=@tick.gif http://localhost:8002</code>, where tick.gif is a file in my folder. This command sends the file in an HTTP request. The <code>-F</code> flag tells <code>curl</code> to send the data as a <code>multipart/form-data</code> request. Let's look at the data printed by our while loop.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1724205997041/1d93674c-9ff6-4757-855f-a102354a59f7.png" alt class="image--center mx-auto" /></p>
<p>We can see that the host is <code>localhost:8002</code> and the Content-Type is <code>multipart/form-data</code>. After that, we see a boundary and the binary data of the file starts. Here's how the data is sent when there are two (name, value) pairs. For example, the form sends two things to the server: a name and an image.</p>
<pre><code class="lang-bash">--boundary
Content-Disposition: form-data; name=<span class="hljs-string">"name"</span>

John Doe
--boundary
Content-Disposition: form-data; name=<span class="hljs-string">"file"</span>; filename=<span class="hljs-string">"photo.jpg"</span>
Content-Type: image/jpeg

(binary data)
--boundary--
</code></pre>
<p>The form data is divided into multiple parts and separated by a boundary. Each part includes headers that describe the content, followed by the data (text, binary, etc.). Now you know how multipart/form-data actually sends the data.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p><code>multipart/form-data</code> encoding is a good choice when your form includes complex data like files. For simple and small data, it is not as efficient as <code>application/x-www-form-urlencoded</code> because of the overhead from boundary markers, which are needed for sending files and mixed data types.</p>
]]></content:encoded></item><item><title><![CDATA[Building my own Redis in Go - Part 3]]></title><description><![CDATA[In the previous blog (part-2), we discussed the implementation of important commands like SET, GET, INCR, RPUSH, and LRANGE. In this blog, we will discuss two very important commands, EXPIRE and TTL. We will also look into how to write back to the cl...]]></description><link>https://blog.sathwikreddygv.com/building-my-own-redis-in-go-part-3</link><guid isPermaLink="true">https://blog.sathwikreddygv.com/building-my-own-redis-in-go-part-3</guid><category><![CDATA[Redis]]></category><category><![CDATA[Databases]]></category><category><![CDATA[key-value-db]]></category><category><![CDATA[In-memory-database]]></category><category><![CDATA[Go Language]]></category><dc:creator><![CDATA[sathwikreddy GV]]></dc:creator><pubDate>Tue, 16 Jul 2024 12:36:38 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1720291238718/9b1f4395-6a4d-4d97-9a2b-bea863a4fce6.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In the previous blog (<a target="_blank" href="https://sathwikreddygv.blog/building-my-own-redis-in-go-part-2">part-2</a>), we discussed the implementation of important commands like <code>SET</code>, <code>GET</code>, <code>INCR</code>, <code>RPUSH</code>, and <code>LRANGE</code>. In this blog, we will discuss two very important commands, <code>EXPIRE</code> and <code>TTL</code>. We will also look into how to write back to the client in RESP.</p>
<h2 id="heading-expire">EXPIRE</h2>
<p>The EXPIRE command in Redis sets a timeout for a key. If you recall our Keyvalue store struct from the previous blog, it had an <code>Expirations map[string]time.Time</code> map. This is where we store the keys with timeouts. Let's look at the code.</p>
<pre><code class="lang-go"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">executeEXPIRE</span><span class="hljs-params">(args []<span class="hljs-keyword">string</span>, kv *KeyValueStore)</span> <span class="hljs-title">string</span></span> {
    <span class="hljs-keyword">if</span> <span class="hljs-built_in">len</span>(args) &lt;= <span class="hljs-number">1</span> {
        <span class="hljs-keyword">return</span> <span class="hljs-string">"(error) ERR wrong number of arguments for 'EXPIRE' command"</span>
    }
    key := args[<span class="hljs-number">0</span>]
    expiration, err := strconv.Atoi(args[<span class="hljs-number">1</span>])
    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
        <span class="hljs-keyword">return</span> <span class="hljs-string">"(error) ERR value is not an integer"</span>
    }
    <span class="hljs-keyword">var</span> key_exists <span class="hljs-keyword">bool</span>
    kv.mu.Lock()
    <span class="hljs-keyword">defer</span> kv.mu.Unlock()
    <span class="hljs-keyword">if</span> _, exists := kv.Strings[key]; exists {
        key_exists = <span class="hljs-literal">true</span>
    } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> _, exists := kv.Lists[key]; exists {
        key_exists = <span class="hljs-literal">true</span>
    } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> _, exists := kv.Hashes[key]; exists {
        key_exists = <span class="hljs-literal">true</span>
    }

    <span class="hljs-keyword">if</span> key_exists {
        kv.Expirations[key] = time.Now().Add(time.Duration(expiration) * time.Second)
        <span class="hljs-keyword">return</span> <span class="hljs-string">"(integer) 1"</span>
    }

    <span class="hljs-keyword">return</span> <span class="hljs-string">"(integer) 0"</span>
}
</code></pre>
<p>The function accepts more than 1 argument (the key and the expiration value). It first checks if the timeout is a number and then verifies if the key exists in any of the 3 maps in the key-value store (Strings, Lists, Hashmaps). It then stores the current timestamp plus the expiration value, indicating when the key should expire.</p>
<h2 id="heading-activepassive-expiration">Active/Passive Expiration</h2>
<p>Redis expires these keys both actively and passively.</p>
<p>A key undergoes passive expiration when a client tries to access it after it has timed out. So, whenever a command like GET, MGET, INCR, or DECR is executed, we need to check if the key has expired. Let's write a function for this.</p>
<pre><code class="lang-go"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">checkExpiredStrings</span><span class="hljs-params">(key <span class="hljs-keyword">string</span>, kv *KeyValueStore)</span> <span class="hljs-title">bool</span></span> {
    <span class="hljs-keyword">if</span> expirationTime, exists := kv.Expirations[key]; exists {
        <span class="hljs-keyword">if</span> time.Now().After(expirationTime) {
            <span class="hljs-built_in">delete</span>(kv.Expirations, key)
            <span class="hljs-built_in">delete</span>(kv.Strings, key)
            <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>
        }
    }
    <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>
}
</code></pre>
<p>This function is now called before executing the commands mentioned above. If this function returns true, it returns (nil).</p>
<pre><code class="lang-go"><span class="hljs-keyword">if</span> checkExpiredStrings(key, kv) {
        <span class="hljs-keyword">return</span> <span class="hljs-string">"(nil)"</span>
}
</code></pre>
<p>There is another function, <code>checkExpiredLists</code>, used when executing commands that deal with Lists, such as LPUSH, RPUSH, LRANGE, and LPOP.</p>
<p>Regarding active expiration, Redis periodically checks keys to see if they have expired and deletes them if they have. I have not personally implemented this. You can read about Redis's implementation in their <a target="_blank" href="https://redis.io/docs/latest/commands/expire/">docs</a>.</p>
<h2 id="heading-ttl">TTL</h2>
<p>TTL returns the remaining time to live of a key that has a timeout. The function that executes this command first checks if the key exists in any of the maps (Strings, Lists, Hashmaps) and then checks if there is an expiration. If it exists, it returns the time to live of that key. If key is actually expired, it deletes it from the key-value store. This is also one more way of passive expiration. Here's the snippet where it returns the remaining time if the key exists.</p>
<pre><code class="lang-go"><span class="hljs-keyword">if</span> expirationTime, exists := kv.Expirations[key]; exists {
    <span class="hljs-keyword">if</span> time.Now().Before(expirationTime) {
        ttl := time.Until(expirationTime).Seconds()
        <span class="hljs-keyword">return</span> fmt.Sprintf(<span class="hljs-string">"(integer) %d"</span>, <span class="hljs-keyword">int</span>(ttl))
    <span class="hljs-built_in">delete</span>(kv.Expirations, key)
    <span class="hljs-built_in">delete</span>(kv.Strings, key)
    <span class="hljs-built_in">delete</span>(kv.Lists, key)
    <span class="hljs-built_in">delete</span>(kv.Hashes, key)
    <span class="hljs-keyword">return</span> <span class="hljs-string">"(integer) -2"</span>
}
</code></pre>
<p>TTL returns <code>-2</code> if the key does not exist and <code>-1</code> if the key exists but has no associated expiration.</p>
<h2 id="heading-writing-resp">Writing RESP</h2>
<p>We have explored implementing some essential commands in Redis. Now let's see how we can write back to the client using RESP. To keep it simple, godisDB always sends a Bulk String to the client. As you may have noticed, all our commands were also returning strings as responses.</p>
<pre><code class="lang-go"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">WriteBulkString</span><span class="hljs-params">(s <span class="hljs-keyword">string</span>, conn io.ReadWriter)</span> <span class="hljs-title">error</span></span> {
    val := []<span class="hljs-keyword">byte</span>(s)
    conn.Write([]<span class="hljs-keyword">byte</span>(<span class="hljs-string">"$"</span>))
    conn.Write(strconv.AppendUint(<span class="hljs-literal">nil</span>, <span class="hljs-keyword">uint64</span>(<span class="hljs-built_in">len</span>(val)), <span class="hljs-number">10</span>))
    conn.Write([]<span class="hljs-keyword">byte</span>(<span class="hljs-string">"\r\n"</span>))
    conn.Write(val)
    _, err := conn.Write([]<span class="hljs-keyword">byte</span>(<span class="hljs-string">"\r\n"</span>))
    <span class="hljs-keyword">return</span> err
}
</code></pre>
<p>This function is quite simple. It starts by appending the <code>$</code> symbol, which signifies Bulk Strings in RESP. Then, it appends the length of the response followed by CRLF. Next, it adds the actual string followed by another CRLF. Any Redis client will be able to read this as a Bulk String.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>We have finished reading input in RESP, executing commands, and writing to the client in RESP. In the next blog, we will look at how to implement persistence. You can check out the code <a target="_blank" href="https://github.com/sathwikreddygv/redis-written-in-go">here</a>. Thanks for reading, and happy coding!</p>
]]></content:encoded></item><item><title><![CDATA[Building my own Redis in Go - Part 2]]></title><description><![CDATA[In part 1 of this blog series, we discussed how to create a TCP server in Go and parse the Redis Serialization Protocol (RESP). Please check out that blog here. In this blog, I'm going to discuss how the commands are executed after parsing RESP. We w...]]></description><link>https://blog.sathwikreddygv.com/building-my-own-redis-in-go-part-2</link><guid isPermaLink="true">https://blog.sathwikreddygv.com/building-my-own-redis-in-go-part-2</guid><category><![CDATA[Redis]]></category><category><![CDATA[key-value]]></category><category><![CDATA[Go Language]]></category><category><![CDATA[Databases]]></category><category><![CDATA[In-memory-database]]></category><dc:creator><![CDATA[sathwikreddy GV]]></dc:creator><pubDate>Sat, 06 Jul 2024 18:28:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1720282378933/773e22c0-48c1-49ba-ab9f-a1483ee085f1.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In part 1 of this blog series, we discussed how to create a TCP server in Go and parse the Redis Serialization Protocol (RESP). Please check out that blog <a target="_blank" href="https://sathwikreddygv.blog/building-my-own-redis-in-go-part-1">here</a>. In this blog, I'm going to discuss how the commands are executed after parsing RESP. We will also go through the implementations of some important commands.</p>
<h2 id="heading-key-value-store">Key-Value Store</h2>
<p>We know that Redis is a key-value in-memory database. So, we need to set up a key-value struct to store the data in our database. Here's the structure for it:</p>
<pre><code class="lang-go"><span class="hljs-keyword">type</span> KeyValueStore <span class="hljs-keyword">struct</span> {
    Strings     <span class="hljs-keyword">map</span>[<span class="hljs-keyword">string</span>]<span class="hljs-keyword">string</span>
    Lists       <span class="hljs-keyword">map</span>[<span class="hljs-keyword">string</span>][]<span class="hljs-keyword">string</span>
    Hashes      <span class="hljs-keyword">map</span>[<span class="hljs-keyword">string</span>]<span class="hljs-keyword">map</span>[<span class="hljs-keyword">string</span>]<span class="hljs-keyword">string</span>
    Expirations <span class="hljs-keyword">map</span>[<span class="hljs-keyword">string</span>]time.Time
    mu          sync.RWMutex
}
</code></pre>
<p><code>KeyValueStore</code> is a struct with multiple hash maps where the key for each map is a string, but the type of value depends on the data structure we want to store in the database. For now, I've included Strings, Lists, and Hashes. <code>mu</code> is the mutex used to acquire a lock while reading/writing to this key-value store (Remember, godisDB is multithreaded unlike Redis). Ignore the <code>Expirations map[string]time.Time</code> for now; we will discuss this in the next blog when we explore the <code>EXPIRE</code> and <code>TTL</code> commands.</p>
<p>In the main function, before listening for connections, we will initialize our key-value store.</p>
<pre><code class="lang-go"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span> {
    listener, err := net.Listen(<span class="hljs-string">"tcp"</span>, <span class="hljs-string">":6369"</span>)
    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
        fmt.Println(<span class="hljs-string">"error listening"</span>)
    }
    kv := &amp;KeyValueStore{
        Strings:     <span class="hljs-built_in">make</span>(<span class="hljs-keyword">map</span>[<span class="hljs-keyword">string</span>]<span class="hljs-keyword">string</span>),
        Lists:       <span class="hljs-built_in">make</span>(<span class="hljs-keyword">map</span>[<span class="hljs-keyword">string</span>][]<span class="hljs-keyword">string</span>),
        Hashes:      <span class="hljs-built_in">make</span>(<span class="hljs-keyword">map</span>[<span class="hljs-keyword">string</span>]<span class="hljs-keyword">map</span>[<span class="hljs-keyword">string</span>]<span class="hljs-keyword">string</span>),
        Expirations: <span class="hljs-built_in">make</span>(<span class="hljs-keyword">map</span>[<span class="hljs-keyword">string</span>]time.Time),
    }
    ...
    <span class="hljs-keyword">go</span> handleConnection(c, kv)
</code></pre>
<p>As you can see, kv is passed as an argument to <code>handleConnection</code> function.</p>
<h2 id="heading-executing-commands">Executing Commands</h2>
<p>Now that we have a command and the arguments in a struct similar to <code>[{HELLO [world]}</code> , let's write a function to execute the commands</p>
<pre><code class="lang-go"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">executeCommand</span><span class="hljs-params">(cmd Command, conn io.ReadWriter, kv *KeyValueStore)</span> <span class="hljs-title">string</span></span> {
    <span class="hljs-keyword">switch</span> cmd.Name {
    <span class="hljs-keyword">case</span> <span class="hljs-string">"SET"</span>:
        <span class="hljs-keyword">return</span> executeSET(cmd.Args, kv)
    <span class="hljs-keyword">case</span> <span class="hljs-string">"GET"</span>:
        <span class="hljs-keyword">return</span> executeGET(cmd.Args, kv)
    <span class="hljs-keyword">case</span> <span class="hljs-string">"SETNX"</span>:
        <span class="hljs-keyword">return</span> executeSETNX(cmd.Args, kv)
    <span class="hljs-keyword">case</span> <span class="hljs-string">"MSET"</span>:
        <span class="hljs-keyword">return</span> executeMSET(cmd.Args, kv)
....continues
</code></pre>
<p>This function is a switch statement that handles commands case by case. I've only included 4 commands, but it is a really long function that supports commands like <code>PING, SET, GET, SETNX, MSET, MGET, INCR, DECR, RPUSH, LPUSH, RPOP, LPOP, LRANGE, DEL, EXPIRE, TTL</code>. Each command is handled separately in its own function. Let's look at some important commands and their functions in this blog.</p>
<h3 id="heading-set">SET</h3>
<p>SET is usually the first command you try when you start using Redis. This command sets the string value of a key. Implementing a function to execute this command is straightforward. Here's the function:</p>
<pre><code class="lang-go"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">executeSET</span><span class="hljs-params">(args []<span class="hljs-keyword">string</span>, kv *KeyValueStore)</span> <span class="hljs-title">string</span></span> {
    <span class="hljs-keyword">if</span> <span class="hljs-built_in">len</span>(args) &lt;= <span class="hljs-number">1</span> {
        <span class="hljs-keyword">return</span> <span class="hljs-string">"(error) ERR wrong number of arguments for 'SET' command"</span>
    } <span class="hljs-keyword">else</span> {
        key := args[<span class="hljs-number">0</span>]
        value := args[<span class="hljs-number">1</span>]
        kv.mu.Lock()
        <span class="hljs-keyword">defer</span> kv.mu.Unlock()
        kv.Strings[key] = value
    }
    <span class="hljs-keyword">return</span> <span class="hljs-string">"OK"</span>
}
</code></pre>
<p>SET command accepts two arguments: the key and the value. We acquire a lock on the kv store and set the key-value in kv.Strings because the SET command sets the string value of a key. Let's move on to the GET command.</p>
<h3 id="heading-get">GET</h3>
<p>The GET command returns the string value of a key. We simply need to search for the key in kv.Strings and return the value if the key exists. Here's the function:</p>
<pre><code class="lang-go"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">executeGET</span><span class="hljs-params">(args []<span class="hljs-keyword">string</span>, kv *KeyValueStore)</span> <span class="hljs-title">string</span></span> {
    <span class="hljs-keyword">if</span> <span class="hljs-built_in">len</span>(args) &lt; <span class="hljs-number">1</span> {
        <span class="hljs-keyword">return</span> <span class="hljs-string">"(error) ERR wrong number of arguments for 'GET' command"</span>
    }
    key := args[<span class="hljs-number">0</span>]
    kv.mu.RLock()
    <span class="hljs-keyword">defer</span> kv.mu.RUnlock()
    <span class="hljs-keyword">if</span> checkExpiredStrings(key, kv) {
        <span class="hljs-keyword">return</span> <span class="hljs-string">"(nil)"</span>
    }
    <span class="hljs-keyword">if</span> value, exists := kv.Strings[key]; exists {
        <span class="hljs-keyword">return</span> value
    }
    <span class="hljs-keyword">return</span> <span class="hljs-string">"Error, key doesn't exist"</span>
}
</code></pre>
<p>The GET command accepts only one argument. You can see that at the end of the function, the value is returned if the key exists. <code>kv.mu.RLock()</code> acquires a read lock, allowing multiple goroutines to read (but not write) at the same time. This is different from <code>kv.mu.Lock()</code>, where only one goroutine can read or write after acquiring the lock. The function <code>checkExpiredStrings</code> is called to check if the key has an expiration set. We will explore this more in the next blog.</p>
<p>Commands like MSET, MGET, and SETNX are various versions of the SET and GET commands. You can look at their implementations in the <a target="_blank" href="https://github.com/sathwikreddygv/redis-written-in-go">source code</a>.</p>
<p>Let's move on to the INCR command.</p>
<h3 id="heading-incr">INCR</h3>
<p>INCR increments the number stored at <code>key</code> by one. This is a string operation because Redis does not have a dedicated integer type. The string stored at the key is treated as a base-10 <strong>64-bit signed integer</strong> to perform the operation. If the key does not exist, it is set to <code>0</code> before the operation. An error is returned if the key contains a value of the wrong type or a string that cannot be represented as an integer. Here's the function:</p>
<pre><code class="lang-go"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">executeINCR</span><span class="hljs-params">(args []<span class="hljs-keyword">string</span>, kv *KeyValueStore)</span> <span class="hljs-title">string</span></span> {
    <span class="hljs-keyword">if</span> <span class="hljs-built_in">len</span>(args) &gt; <span class="hljs-number">1</span> {
        <span class="hljs-keyword">return</span> <span class="hljs-string">"(error) ERR wrong number of arguments for 'INCR' command"</span>
    }
    key := args[<span class="hljs-number">0</span>]
    kv.mu.Lock()
    <span class="hljs-keyword">defer</span> kv.mu.Unlock()

    <span class="hljs-keyword">if</span> _, exists := kv.Lists[key]; exists {
        <span class="hljs-keyword">return</span> <span class="hljs-string">"(error) WRONGTYPE Operation against a key holding the wrong kind of value"</span>
    }

    <span class="hljs-keyword">if</span> _, exists := kv.Hashes[key]; exists {
        <span class="hljs-keyword">return</span> <span class="hljs-string">"(error) WRONGTYPE Operation against a key holding the wrong kind of value"</span>
    }

    <span class="hljs-keyword">if</span> _, exists := kv.Strings[key]; !exists || checkExpiredStrings(key, kv) {
        kv.Strings[key] = <span class="hljs-string">"1"</span>
        <span class="hljs-keyword">return</span> <span class="hljs-string">"(integer) 1"</span>
    } <span class="hljs-keyword">else</span> {
        num, err := strconv.Atoi(kv.Strings[key])
        <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
            <span class="hljs-keyword">return</span> <span class="hljs-string">"(error) ERR value is not an integer"</span>
        }
        num++
        kv.Strings[key] = strconv.Itoa(num)
        <span class="hljs-keyword">return</span> fmt.Sprintf(<span class="hljs-string">"(integer) %d"</span>, num)
    }
}
</code></pre>
<p>Here, we first check if the key exists in Lists or Hashmaps and then move to Strings. In <code>kv.Strings</code>, if the key doesn't exist, we assign the key a value of 1 and return 1. Otherwise, we increase the existing value (if it can be converted to an integer).</p>
<p><code>DECR</code> is a similar command to <code>INCR</code> but for decrement. Now, let's look at storing arrays in our database. Some important commands to store and retrieve arrays are <code>RPUSH</code>, <code>LPUSH</code>, <code>RPOP</code>, <code>LPOP</code>, and <code>LRANGE</code>.</p>
<h3 id="heading-rpush">RPUSH</h3>
<p><code>RPUSH</code> inserts all the specified values at the end of the list stored at <code>key</code>. If the key doesn't exist, it creates one. Here's the function for <code>RPUSH</code>:</p>
<pre><code class="lang-go"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">executeRPUSH</span><span class="hljs-params">(args []<span class="hljs-keyword">string</span>, kv *KeyValueStore)</span> <span class="hljs-title">string</span></span> {
    <span class="hljs-keyword">if</span> <span class="hljs-built_in">len</span>(args) &lt;= <span class="hljs-number">1</span> {
        <span class="hljs-keyword">return</span> <span class="hljs-string">"(error) ERR wrong number of arguments for 'RPUSH' command"</span>
    } <span class="hljs-keyword">else</span> {
        kv.mu.Lock()
        <span class="hljs-keyword">defer</span> kv.mu.Unlock()
        key := args[<span class="hljs-number">0</span>]
        <span class="hljs-keyword">if</span> _, exists := kv.Lists[key]; !exists || checkExpiredLists(key, kv) {
            kv.Lists[key] = <span class="hljs-built_in">make</span>([]<span class="hljs-keyword">string</span>, <span class="hljs-number">0</span>)
        }

        <span class="hljs-keyword">for</span> i := <span class="hljs-number">1</span>; i &lt; <span class="hljs-built_in">len</span>(args); i++ {
            kv.Lists[key] = <span class="hljs-built_in">append</span>(kv.Lists[key], args[i])
        }
    }
    <span class="hljs-keyword">return</span> <span class="hljs-string">"OK"</span>
}
</code></pre>
<p>RPUSH accepts more than one argument and appends all the provided values to the list and returns "OK".</p>
<p>You can check out other commands like LPUSH, RPOP, and LPOP, which are used to store and retrieve values from arrays.</p>
<h3 id="heading-lrange">LRANGE</h3>
<p>LRANGE returns the specified elements of the list stored at <code>key</code>. Let's look at the function</p>
<pre><code class="lang-go"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">executeLRANGE</span><span class="hljs-params">(args []<span class="hljs-keyword">string</span>, kv *KeyValueStore)</span> <span class="hljs-title">string</span></span> {
    key := args[<span class="hljs-number">0</span>]
    kv.mu.Lock()
    <span class="hljs-keyword">defer</span> kv.mu.Unlock()
    <span class="hljs-keyword">if</span> _, exists := kv.Lists[key]; !exists || checkExpiredLists(key, kv) {
        <span class="hljs-keyword">return</span> <span class="hljs-string">"Error, key does not exist"</span>
    }

    startIndex, err := strconv.Atoi(args[<span class="hljs-number">1</span>])
    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
        <span class="hljs-keyword">return</span> <span class="hljs-string">"Error, Start Index must be an integer"</span>
    }
    endIndex, err := strconv.Atoi(args[<span class="hljs-number">2</span>])
    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
        <span class="hljs-keyword">return</span> <span class="hljs-string">"Error, End Index must be an integer"</span>
    }

    <span class="hljs-keyword">if</span> startIndex &lt; <span class="hljs-number">0</span> {
        startIndex = <span class="hljs-built_in">len</span>(kv.Lists[key]) + startIndex
    }
    <span class="hljs-keyword">if</span> endIndex &lt; <span class="hljs-number">0</span> {
        endIndex = <span class="hljs-built_in">len</span>(kv.Lists[key]) + endIndex
    }
    <span class="hljs-keyword">if</span> startIndex &gt; endIndex || startIndex &gt;= <span class="hljs-built_in">len</span>(kv.Lists[key]) {
        <span class="hljs-keyword">return</span> <span class="hljs-string">"empty"</span>
    }
    <span class="hljs-keyword">if</span> endIndex &gt;= <span class="hljs-built_in">len</span>(kv.Lists[key]) {
        endIndex = <span class="hljs-built_in">len</span>(kv.Lists[key]) - <span class="hljs-number">1</span>
    }

    str := strings.Join(kv.Lists[key][startIndex:endIndex+<span class="hljs-number">1</span>], <span class="hljs-string">" "</span>)
    <span class="hljs-keyword">return</span> str
}
</code></pre>
<p>LRANGE accepts the key, startIndex and endIndex as arguments.There are many safety checks in this function to handle indices going out of range. Finally, the desired elements are joined with spaces and returned as a string.</p>
<p>We've only discussed the implementation of some essential commands. Please check out all the implementations in the <a target="_blank" href="https://github.com/sathwikreddygv/redis-written-in-go">source code</a>.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>In the next blog (part-3), we will look at two important commands, <code>EXPIRE</code> and <code>TTL</code>, and the <code>checkExpiredLists</code> function, which we skipped in this blog. We will also discuss how to write back to the client in RESP. Thanks for reading. Happy coding!</p>
]]></content:encoded></item><item><title><![CDATA[Building my own Redis in Go - Part 1]]></title><description><![CDATA[Introduction
I've always wanted to understand how Redis works internally and how its features are built. So, I decided to create my own version of Redis, but in Go (Redis is actually built in C). The name "godisDB" came naturally when merging Go and ...]]></description><link>https://blog.sathwikreddygv.com/building-my-own-redis-in-go-part-1</link><guid isPermaLink="true">https://blog.sathwikreddygv.com/building-my-own-redis-in-go-part-1</guid><category><![CDATA[godisDB]]></category><category><![CDATA[Redis]]></category><category><![CDATA[Go Language]]></category><category><![CDATA[Databases]]></category><category><![CDATA[Key value stores]]></category><dc:creator><![CDATA[sathwikreddy GV]]></dc:creator><pubDate>Sun, 23 Jun 2024 18:15:01 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1719136954976/f4534af2-7214-471d-961d-b630b03f05d6.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-introduction">Introduction</h2>
<p>I've always wanted to understand how Redis works internally and how its features are built. So, I decided to create my own version of Redis, but in Go (Redis is actually built in C). The name "godisDB" came naturally when merging Go and Redis. You can find the source code <a target="_blank" href="https://github.com/sathwikreddygv/redis-written-in-go">here</a>. I'm breaking these blogs into multiple parts since there are many features, and I can keep publishing blogs as I build them one at a time. This blog (Part 1) will cover the following things:</p>
<ol>
<li><p>Creating a basic TCP server in Go</p>
</li>
<li><p>Parsing Redis Serialization Protocol (RESP)</p>
</li>
</ol>
<p>We need to create a TCP server because Redis is essentially a TCP server running on a port and accepting requests from clients. Redis uses the "Redis Serialization Protocol (RESP)" to communicate with its clients, and clients should use the same protocol to talk to Redis. If we use the same protocol for our database, all existing Redis clients can communicate with godisDB without any issues.</p>
<h2 id="heading-creating-a-basic-tcp-server-in-go">Creating a basic TCP server in Go</h2>
<p>Creating a TCP server in Go is fairly simple because there is a package called "net" that can do it for us in a few lines of code. Like all TCP servers, ours should listen for connections on a specific port. Since Redis runs on port 6379, let's use port 6369. Here's the code using the "net" package:</p>
<pre><code class="lang-go"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span> {
    listener, err := net.Listen(<span class="hljs-string">"tcp"</span>, <span class="hljs-string">":6369"</span>)
    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
        fmt.Println(<span class="hljs-string">"error listening"</span>)
    }
    <span class="hljs-keyword">for</span> {
        c, err := listener.Accept()
        <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
            fmt.Println(<span class="hljs-string">"error accepting connection"</span>)
        }
        fmt.Println(<span class="hljs-string">"client connected"</span>)
        <span class="hljs-keyword">go</span> handleConnection(c)
    }
}
</code></pre>
<p><code>net.Listen</code> allows the server to listen on the port, and <code>listener.Accept</code> is used to accept connections. This is called in a for loop so we can accept multiple clients. Then, I call the function <code>handleConnection</code> in a goroutine. A goroutine runs independently of the calling function. This is where godisDB differs from Redis because Redis is single-threaded and uses I/O Multiplexing and an <a target="_blank" href="https://sathwikreddygv.blog/wtf-is-an-event-loop-in-javascript">event loop</a> like Node.js to handle multiple clients. GodisDB handles each client in a goroutine, which runs independently. Now, let's look at how the <code>handleConnection</code> function works.</p>
<pre><code class="lang-go"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">handleConnection</span><span class="hljs-params">(conn net.Conn)</span></span> {
    <span class="hljs-keyword">defer</span> conn.Close()
    fmt.Printf(<span class="hljs-string">"Client connected: %s\n"</span>, conn.RemoteAddr().String())
    <span class="hljs-keyword">for</span> {
        <span class="hljs-keyword">var</span> buf []<span class="hljs-keyword">byte</span> = <span class="hljs-built_in">make</span>([]<span class="hljs-keyword">byte</span>, <span class="hljs-number">50</span>*<span class="hljs-number">1024</span>)
        n, err := conn.Read(buf[:])
        <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
            conn.Close()
            fmt.Print(<span class="hljs-string">"client disconnected with "</span>, err.Error())
            <span class="hljs-keyword">break</span>
        }
        commands, err := ParseRESP(conn, buf[:n])
        fmt.Print(commands)
        executeCommands(conn, commands, kv)
    }
}
</code></pre>
<p>This function reads input from the client in a connection, uses the <code>ParseRESP</code> function to parse the input, and then executes the commands. Let's look at what Redis Serialization Protocol (RESP) is and how it is parsed in the following sections.</p>
<h2 id="heading-redis-serialization-protocol-resp">Redis Serialization Protocol (RESP)</h2>
<p>Redis Serialization Protocol (RESP) is a protocol used by Redis and its clients to communicate. It is simple to implement, fast to parse, and human-readable. In an RESP-serialized payload, the first byte identifies the type, and the content follows. <code>\r\n</code> (CRLF) is the terminator in RESP. Here's a picture from the Redis documentation showing the types and their identifying bytes..</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1719150240918/7940a9e4-05fc-43c1-af6c-a3828197d967.png" alt class="image--center mx-auto" /></p>
<p>A simple string looks like <code>+OK\r\n</code>.</p>
<p>A bulk string looks like <code>$&lt;length&gt;\r\n&lt;data&gt;\r\n</code>. For example, <code>$5\r\nhello\r\n</code> (where <code>$</code> indicates a Bulk string and <code>5</code> is the length of the string "hello").</p>
<p>An array looks like <code>*&lt;number-of-elements&gt;\r\n&lt;element-1&gt;...&lt;element-n&gt;</code>. For example, <code>*2\r\n$5\r\nhello\r\n$5\r\nworld\r\n</code> (where <code>*</code> indicates an Array, <code>2</code> is the number of elements, followed by 2 bulk strings as elements).</p>
<p>Understanding Bulk strings and Arrays is essential for now. You can read more about RESP in the Redis docs <a target="_blank" href="https://redis.io/docs/latest/develop/reference/protocol-spec/">here.</a> Now, let's build our parser for RESP.</p>
<h2 id="heading-parsing-resp">Parsing RESP</h2>
<p>Remember that in the <code>handleConnection</code> function, we stored the client's input in a byte array (<code>inputBuf</code>). We copy it into a <code>bytes.Buffer</code> because the functions we use to parse this input will update the pointer internally. This way, we can keep reading the buffer without losing track of how much has already been read.</p>
<pre><code class="lang-go"><span class="hljs-keyword">var</span> buf *bytes.Buffer = bytes.NewBuffer([]<span class="hljs-keyword">byte</span>)
buf.Write(inputBuf)
</code></pre>
<p>We can start by reading the first byte to identify the type, then write separate functions to read each type. Here's the code for this function named <code>parseSingle</code>.</p>
<pre><code class="lang-go"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-params">(rp *RESPParser)</span> <span class="hljs-title">parseSingle</span><span class="hljs-params">()</span> <span class="hljs-params">(<span class="hljs-keyword">interface</span>{}, error)</span></span> {
    b, err := rp.buf.ReadByte()
    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
        <span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span>, err
    }
    <span class="hljs-keyword">switch</span> b {
    <span class="hljs-keyword">case</span> <span class="hljs-string">'+'</span>:
        <span class="hljs-keyword">return</span> readSimpleString(rp.c, rp.buf)
    <span class="hljs-keyword">case</span> <span class="hljs-string">'-'</span>:
        <span class="hljs-keyword">return</span> readError(rp.c, rp.buf)
    <span class="hljs-keyword">case</span> <span class="hljs-string">':'</span>:
        <span class="hljs-keyword">return</span> readInt64(rp.c, rp.buf)
    <span class="hljs-keyword">case</span> <span class="hljs-string">'$'</span>:
        <span class="hljs-keyword">return</span> readBulkString(rp.c, rp.buf)
    <span class="hljs-keyword">case</span> <span class="hljs-string">'*'</span>:
        <span class="hljs-keyword">return</span> readArray(rp.c, rp.buf, rp)
    }
    <span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span>, errors.New(<span class="hljs-string">"Invalid input"</span>)
}
</code></pre>
<p>As mentioned earlier, the <code>ReadByte</code> function reads and returns the next byte from the buffer, moving the internal pointer forward by one byte. Now, let's focus on the <code>readArray</code> function because Redis clients send requests to the Redis server as an array of strings.</p>
<pre><code class="lang-go"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">readArray</span><span class="hljs-params">(buf *bytes.Buffer, rp *RESPParser)</span> <span class="hljs-params">(<span class="hljs-keyword">interface</span>{}, error)</span></span> {
    count, err := readLength(buf)
    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
        <span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span>, err
    }
    <span class="hljs-keyword">var</span> elems []<span class="hljs-keyword">interface</span>{} = <span class="hljs-built_in">make</span>([]<span class="hljs-keyword">interface</span>{}, count)
    <span class="hljs-keyword">for</span> i := <span class="hljs-keyword">range</span> elems {
        elem, err := rp.parseSingle()
        <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
            <span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span>, err
        }
        elems[i] = elem
    }
    <span class="hljs-keyword">return</span> elems, <span class="hljs-literal">nil</span>
}
</code></pre>
<p>In this function, we first read the length of the array (The <code>readLength</code> function handles the first CRLF by moving the pointer to the start of the array) and then iterate over this length to read the contents inside the array. In the for loop, we call the <code>parseSingle</code> function again, but with an updated pointer since some bytes have already been read.</p>
<p>Now, if the input is something like <code>*2\r\n$5\r\nhello\r\n$5\r\nworld\r\n</code>, the <code>parseSingle</code> function first detects the type and calls the <code>readArray</code> function. Then, the <code>readArray</code> function calls the <code>parseSingle</code> function twice to read the two bulk strings. Let's look at the readBulkString function:</p>
<pre><code class="lang-go"><span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">readBulkString</span><span class="hljs-params">(buf *bytes.Buffer)</span> <span class="hljs-params">(<span class="hljs-keyword">string</span>, error)</span></span> {
    <span class="hljs-built_in">len</span>, err := readLength(buf)
    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
        <span class="hljs-keyword">return</span> <span class="hljs-string">""</span>, err
    }

    bulkStr := <span class="hljs-built_in">make</span>([]<span class="hljs-keyword">byte</span>, <span class="hljs-built_in">len</span>)
    _, err = buf.Read(bulkStr)
    <span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
        <span class="hljs-keyword">return</span> <span class="hljs-string">""</span>, err
    }

    <span class="hljs-comment">// moving buffer pointer by 2 for \r and \n</span>
    buf.ReadByte()
    buf.ReadByte()

    <span class="hljs-keyword">return</span> <span class="hljs-keyword">string</span>(bulkStr), <span class="hljs-literal">nil</span>
}
</code></pre>
<p>In this function, we first read the length of the bulk string and then read the buffer up to that length (Again, the <code>readLength</code> function handles the first CRLF by moving the pointer to the start of the BulkString). Finally, we move the buffer pointer by 2 at the end to account for the final CRLF.</p>
<p>So, for the above example, the <code>parseSingle</code> function will return an array ["hello", "world"]. In the array of strings sent by the client, the first element is the command, and the following elements are the arguments. So here, "hello" is the command, and "world" is an argument. Let's define a struct to manage this.</p>
<pre><code class="lang-go"><span class="hljs-keyword">type</span> Command <span class="hljs-keyword">struct</span> {
    Name <span class="hljs-keyword">string</span>
    Args []<span class="hljs-keyword">string</span>
}
</code></pre>
<p>The following code will help us pack the result from <code>parseSingle</code> function into this struct</p>
<pre><code class="lang-go">value, err := rp.parseSingle()
<span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
    <span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span>, err
}

<span class="hljs-keyword">var</span> cmds = <span class="hljs-built_in">make</span>([]Command, <span class="hljs-number">0</span>)
tokens, err := toArrayString(value.([]<span class="hljs-keyword">interface</span>{}))
<span class="hljs-keyword">if</span> err != <span class="hljs-literal">nil</span> {
    <span class="hljs-keyword">return</span> <span class="hljs-literal">nil</span>, err
}
cmd := strings.ToUpper(tokens[<span class="hljs-number">0</span>])
cmds = <span class="hljs-built_in">append</span>(cmds, Command{
    Name: cmd,
    Args: tokens[<span class="hljs-number">1</span>:],
})
</code></pre>
<p>This code takes the first argument and stores it as the Name, while the other elements are stored as Args. The final result for our example will be <code>[{HELLO [world]}</code>.</p>
<p>That's how we parse RESP-serialized inputs and store them as Redis commands to be executed.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>In Part 2 of this blog series, I will discuss how the commands are executed and sent to the client. I was inspired by Dhravya's <a target="_blank" href="https://github.com/Dhravya/radish">Radish</a> and Arpit Bhayani's <a target="_blank" href="https://github.com/DiceDB/dice">DiceDB</a> before building my own Redis. You can check out their implementations if you are interested. Happy coding!</p>
]]></content:encoded></item><item><title><![CDATA[WTF is Git rebase]]></title><description><![CDATA[I've never used git rebase at work, but I see many people talking about it, and there's the classic "merge vs rebase" debate. So, here I am, learning about rebase, comparing it with merge, and writing a blog for myself.
What does rebase do
Rebase is ...]]></description><link>https://blog.sathwikreddygv.com/wtf-is-git-rebase</link><guid isPermaLink="true">https://blog.sathwikreddygv.com/wtf-is-git-rebase</guid><category><![CDATA[Git]]></category><category><![CDATA[GitHub]]></category><category><![CDATA[git rebase]]></category><category><![CDATA[git merge]]></category><category><![CDATA[merge-vs-rebase]]></category><dc:creator><![CDATA[sathwikreddy GV]]></dc:creator><pubDate>Sat, 25 May 2024 11:31:21 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/KPAQpJYzH0Y/upload/19904e9fb090cca41923338306141eee.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I've never used git rebase at work, but I see many people talking about it, and there's the classic "merge vs rebase" debate. So, here I am, learning about rebase, comparing it with merge, and writing a blog for myself.</p>
<h2 id="heading-what-does-rebase-do">What does rebase do</h2>
<p>Rebase is a Git utility used when a team is working on a repository and wants to integrate changes from one branch to another. Git merge is another tool for this purpose, and we will explore the differences between them in the next section. First, let's dive into what rebase does.</p>
<p>Let's start with having a main branch and creating a feature branch for developing a new feature. Let's say we made 2 commits in this new feature branch. Meanwhile, our team has made 1 new commit in the main branch. Now we are in a situation where the main branch and the feature branch have diverged. We want to get the changes from the main branch into the feature branch keeping the history clean and making it look like we cut the feature branch from the latest main branch. Rebase can help us do exactly that. When we rebase our feature branch onto the main branch, the base of our feature branch will be updated, making it appear as if we created this feature branch from the latest commit in the main branch. Confused? Look at the image below to understand this better.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1716634124835/74893250-b27d-4dcc-b33d-ebc83ee846e0.png" alt class="image--center mx-auto" /></p>
<p>Let's create a new git repo and see how this works using git logs.</p>
<pre><code class="lang-bash">mkdir rebase_testing
<span class="hljs-built_in">cd</span> rebase_testing
git init
</code></pre>
<p>Now I'll make an initial commit in main, then cut a feature branch. Make 2 commits in the feature branch. Note: all are empty commits.</p>
<pre><code class="lang-bash">git commit --allow-empty -m <span class="hljs-string">"main: commit 1"</span>
git checkout -b feature_branch
git commit --allow-empty -m <span class="hljs-string">"feature: commit 1"</span>
git commit --allow-empty -m <span class="hljs-string">"feature: commit 2"</span>
</code></pre>
<p>Now let's make a new commit in the main branch</p>
<pre><code class="lang-bash">git checkout main
git commit --allow-empty -m <span class="hljs-string">"main: commit 2"</span>
</code></pre>
<p>This is how git logs look in this state in both the branches.</p>
<p>Main:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1716634786722/303bf2c7-25a7-4149-a650-af7950fc64ab.png" alt class="image--center mx-auto" /></p>
<p>Feature branch:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1716634934986/5050f91a-833e-4c8e-bee5-fd09c3deda8c.png" alt class="image--center mx-auto" /></p>
<p>Now let's rebase feature branch onto main</p>
<pre><code class="lang-bash">git checkout feature_branch
git rebase main
</code></pre>
<p>Now let's look at the commit history of the feature_branch</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1716635004122/31c8e1b1-e0dd-4a8c-a71d-5c99d793cd4b.png" alt class="image--center mx-auto" /></p>
<p>As you can see, the base commit of the feature branch has changed from <code>main: commit 1</code> to <code>main: commit 2</code>, just like in the image. And that's how rebase works. Now we can merge the feature branch into the main branch by running <code>git checkout main</code> and <code>git merge feature_branch</code>.</p>
<h2 id="heading-how-is-it-different-from-git-merge">How is it different from git merge</h2>
<p>Git merge is also a Git utility used to integrate changes from one branch to another. However, unlike rebase, merge does not rewrite commits. Instead, it creates a new merge commit. Look at the image below for a better understanding.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1716635408033/83128347-45f3-4ad5-aeb7-f78d4902588f.png" alt class="image--center mx-auto" /></p>
<p>A new merge commit is created only when the two branches have diverged, as in the example above. However, if the main branch doesn't have any new commits, Git can directly move the main branch tip to the feature branch tip. This is called a fast-forward merge.</p>
<p>Now it is easy to see that, after rebasing the feature branch onto the main branch, the feature branch is no longer diverged from the main branch. So, merging the feature branch into the main branch will result in a fast-forward merge without a new merge commit.</p>
<p>This is why some developers prefer "rebase and merge" instead of "just merge".</p>
<h2 id="heading-golden-rule-of-rebasing">Golden rule of rebasing</h2>
<p>The golden rule of rebasing is to never use it on public branches like main. Since rebase can rewrite the commits of a branch, your main branch will then be different from everyone else's main branch, which is not desirable.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>If you want a clean commit history without any merge commits, you can choose to rebase and then merge. But if you want to keep all the merge history and avoid the risk of rewriting commit history, go for the classic merge.</p>
<p>The go-to docs when I need to learn something about Git are the <a target="_blank" href="https://www.atlassian.com/git/glossary#commands">Atlassian docs</a>. This blog is also inspired by them. Thanks for reading and happy coding !</p>
]]></content:encoded></item><item><title><![CDATA[Understanding Scroll Driven Animations in CSS]]></title><description><![CDATA[Intro
Scroll-driven animations are "animations that are triggered as the user scrolls through a webpage," according to ChatGPT. I always wanted to build a personal website with cool scroll-based animations. I used to be amazed by some websites (like ...]]></description><link>https://blog.sathwikreddygv.com/understanding-scroll-driven-animations-in-css</link><guid isPermaLink="true">https://blog.sathwikreddygv.com/understanding-scroll-driven-animations-in-css</guid><category><![CDATA[CSS]]></category><category><![CDATA[CSS Animation]]></category><category><![CDATA[scroll animation]]></category><category><![CDATA[CSS3]]></category><category><![CDATA[Frontend Development]]></category><dc:creator><![CDATA[sathwikreddy GV]]></dc:creator><pubDate>Thu, 23 May 2024 17:23:37 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/Hatkch_piQM/upload/d985cd483a9c04deb622ad0814f7ab62.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-intro">Intro</h2>
<p>Scroll-driven animations are "animations that are triggered as the user scrolls through a webpage," according to ChatGPT. I always wanted to build a personal website with cool scroll-based animations. I used to be amazed by some websites (<a target="_blank" href="https://albinotonnina.com/">like this</a>) with impressive scroll-based animations. But when I started building my personal website, I procrastinated a lot. Then I thought I should write a blog for myself on scroll-driven animations in CSS to get a better idea of how to build my website. So here we go!</p>
<p>We have a default time-based timeline in CSS animations. Below is an example of a typical time-based timeline:</p>
<pre><code class="lang-css"><span class="hljs-selector-id">#scroll-icon</span> {
    <span class="hljs-attribute">animation</span>: scroll-icon <span class="hljs-number">1.8s</span> ease infinite;
}
<span class="hljs-keyword">@keyframes</span> scroll-icon {
    0%{
        <span class="hljs-attribute">opacity</span>: <span class="hljs-number">0</span>;
        <span class="hljs-attribute">transform</span>: <span class="hljs-built_in">translate</span>(-<span class="hljs-number">0px</span>,-<span class="hljs-number">10px</span>);
    }
    50%{
        <span class="hljs-attribute">opacity</span>: <span class="hljs-number">1</span>;
    }
    100%{
        <span class="hljs-attribute">opacity</span>: <span class="hljs-number">0</span>;
        <span class="hljs-attribute">transform</span>: <span class="hljs-built_in">translate</span>(<span class="hljs-number">0px</span>,<span class="hljs-number">50px</span>);
    }
}
</code></pre>
<p>But Scroll based timelines can be of 2 types.</p>
<h2 id="heading-types-of-scroll-based-timelines">Types of Scroll based timelines</h2>
<h3 id="heading-scroll-progress-timeline">Scroll progress timeline</h3>
<p>One can progress this timeline by scrolling an element. The progression starts at 0% and ends at 100%, and the element can be animated within this range.</p>
<p>Let's look at an example to understand this better.</p>
<pre><code class="lang-css"><span class="hljs-selector-id">#sticky-parallax-header</span> {
    <span class="hljs-attribute">position</span>: fixed;
    <span class="hljs-attribute">top</span>: <span class="hljs-number">0</span>;
    <span class="hljs-attribute">animation</span>: sticky-parallax-header-move-and-size linear forwards;
    <span class="hljs-attribute">animation-timeline</span>: <span class="hljs-built_in">scroll</span>();
    <span class="hljs-attribute">animation-range</span>: <span class="hljs-number">0vh</span> <span class="hljs-number">90vh</span>;
}

<span class="hljs-keyword">@keyframes</span> sticky-parallax-header-move-<span class="hljs-keyword">and</span>-size {
    <span class="hljs-selector-tag">from</span> {
        <span class="hljs-attribute">top</span>: <span class="hljs-number">93vh</span>;
        <span class="hljs-attribute">width</span>: <span class="hljs-number">100vw</span>;
        <span class="hljs-attribute">font-size</span>: <span class="hljs-number">20px</span>;
    }
    <span class="hljs-selector-tag">to</span> {
        <span class="hljs-attribute">top</span>: <span class="hljs-number">4</span>;
        <span class="hljs-attribute">width</span>: <span class="hljs-number">40vw</span>;
        <span class="hljs-attribute">font-size</span>: <span class="hljs-number">14px</span>;
    }
}
</code></pre>
<p>In this above example, there is a line <code>animation-timeline: scroll()</code> which says the animation timeline is scroll based. So when my element is at the bottom of the page, the animation starts as the user scroll the page. The keyframes tell us that the element goes from <code>width: 100vw</code> width to <code>width: 40vw</code>, and similarly top and font-size are also changed. Let's look at how this animation turns out now!</p>
<p>Initial state of the header(at the bottom):</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1716433917990/f6c89d02-35ff-40ab-b753-1697ffac54d0.png" alt class="image--center mx-auto" /></p>
<p>State when the page is half scrolled:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1716433948473/95057621-c082-4a62-ab59-683583c19a49.png" alt class="image--center mx-auto" /></p>
<p>Final state:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1716434135906/3ec76322-86c3-430f-be8c-4c89370cb3ef.png" alt class="image--center mx-auto" /></p>
<p>You can see that the header moves to the top as you scroll. The width and font size also decrease as specified in the CSS code above. The header stops animating at the top and takes on the final width and font size mentioned in the code.</p>
<p>That's how scroll-progress timeline works!</p>
<h3 id="heading-view-progress-timeline">View progress timeline</h3>
<p>You can progress this timeline based on the visibility of an element. The progression is 0% when the element first becomes visible and 100% when the element reaches the edge and is no longer visible.Let's look at an example</p>
<pre><code class="lang-css"><span class="hljs-selector-class">.revealing-image</span> {
    <span class="hljs-comment">/* Create View Timeline */</span>
    <span class="hljs-attribute">view-timeline-name</span>: --revealing-image;
    <span class="hljs-attribute">view-timeline-axis</span>: block;

    <span class="hljs-comment">/* Attach animation, linked to the  View Timeline */</span>
    <span class="hljs-attribute">animation</span>: linear reveal both;
    <span class="hljs-attribute">animation-timeline</span>: --revealing-image;

    <span class="hljs-comment">/* Tweak range when effect should run*/</span>
    <span class="hljs-attribute">animation-range</span>: entry <span class="hljs-number">25%</span> cover <span class="hljs-number">50%</span>;
}

<span class="hljs-keyword">@keyframes</span> reveal {
    <span class="hljs-selector-tag">from</span> {
        <span class="hljs-attribute">opacity</span>: <span class="hljs-number">0</span>;
        <span class="hljs-attribute">clip-path</span>: <span class="hljs-built_in">inset</span>(<span class="hljs-number">45%</span> <span class="hljs-number">20%</span> <span class="hljs-number">45%</span> <span class="hljs-number">20%</span>);
    }
    <span class="hljs-selector-tag">to</span> {
        <span class="hljs-attribute">opacity</span>: <span class="hljs-number">1</span>;
        <span class="hljs-attribute">clip-path</span>: <span class="hljs-built_in">inset</span>(<span class="hljs-number">0%</span> <span class="hljs-number">0%</span> <span class="hljs-number">0%</span> <span class="hljs-number">0%</span>);
    }
}
</code></pre>
<p>Here, we first create a view-timeline and then assign it using <code>animation-timeline: --revealing-image;</code>. The <code>view-timeline-axis: block;</code> defines that the animation follows the vertical scroll. The line <code>animation-range: entry 25% cover 50%;</code> is crucial as it specifies when the animation starts and ends. As mentioned earlier, the progression in this timeline is based on the visibility of an element. "Entry 25%" means the animation starts when the element is 25% visible, and "cover 50%" means the animation stops when 50% of the scrolling (for that element in the viewport) is done. Let's see how this turns out!</p>
<p>Image 1 (Opacity is less than 1 and the image is clipped):</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1716484042990/e6e7f388-078a-4e77-91ef-b1442c7d4124.png" alt class="image--center mx-auto" /></p>
<p>Image 2 (Opacity is 1 and the image is fully visible):</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1716484054000/dc4ae93d-61c4-4e0b-add1-4e8885e6706a.png" alt class="image--center mx-auto" /></p>
<p>It is clear in the devtools that the element has been scrolled more than 50%, so the animation has finished. That’s how the view progress timeline works.</p>
<h2 id="heading-scroll-driven-animations-devtools-extension">Scroll-driven animations Devtools Extension</h2>
<p>All the images above had a cool debugger on the right side. This is the DevTools extension for scroll-driven animation. It's very useful when you are learning scroll-driven animations. Get it <a target="_blank" href="https://chromewebstore.google.com/detail/scroll-driven-animations/ojihehfngalmpghicjgbfdmloiifhoce">here</a>!</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Scroll-driven animations can make your website look much more beautiful. It is really helpful to understand them. I learned them from <a target="_blank" href="https://scroll-driven-animations.style/">https://scroll-driven-animations.style/</a>, and this blog is also heavily inspired by that site. Please check out more scroll-driven animations there. Happy coding!</p>
]]></content:encoded></item><item><title><![CDATA[What are /etc, /var, /bin, /opt in Linux]]></title><description><![CDATA[Being a frontend developer in my first year of work, these Linux folders always used to confuse me. What are they, and why are they named like that? I recall setting up Nginx on my MacBook for some development tasks. I would always ask the DevOps guy...]]></description><link>https://blog.sathwikreddygv.com/what-are-etc-var-bin-opt-in-linux</link><guid isPermaLink="true">https://blog.sathwikreddygv.com/what-are-etc-var-bin-opt-in-linux</guid><category><![CDATA[Linux]]></category><category><![CDATA[file system hierarchy]]></category><category><![CDATA[linux for beginners]]></category><category><![CDATA[unix]]></category><category><![CDATA[unixfilesystem]]></category><dc:creator><![CDATA[sathwikreddy GV]]></dc:creator><pubDate>Thu, 09 May 2024 15:45:26 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/xbEVM6oJ1Fs/upload/b16864c5c11e10a92c99f6fe8ee32353.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Being a frontend developer in my first year of work, these Linux folders always used to confuse me. What are they, and why are they named like that? I recall setting up Nginx on my MacBook for some development tasks. I would always ask the DevOps guy in my office where to find the Nginx config file. He would simply say, "Just go to <code>/opt/homebrew/etc/nginx</code>, and you'll find it." I used to wonder how they remembered all that information.</p>
<p>Recently, I realized I needed to learn about this and write a blog post. So, let's dive in and explore "some" of the Linux File System.</p>
<p>So, <code>/etc</code>, <code>/var,/bin</code>, and <code>/opt</code> folders are part of the larger File System Hierarchy in Linux. There are specific requirements and guidelines for where files and directories should be placed in Linux according to <a target="_blank" href="https://refspecs.linuxfoundation.org/FHS_3.0/fhs/index.html">FHS (Formerly, FSSTND)</a>. FHS stands for Filesystem Hierarchy Standard, while FSSTND stands for Filesystem Standard. In this blog, I will focus on explaining these four folders mentioned above.</p>
<p>Let's start with <code>/bin</code>. "bin" stands for "binary," and this is where the essential executable binary files (i.e., compiled machine code) are stored for various system commands and utilities. So, common commands like cp, mv, rm, cat, grep, etc., are stored here.</p>
<p>Coming to <code>/var</code>, "var" stands for "variable," and it contains data files that are expected to change during the normal operation of the system. For instance, in /var/log, you can find all the logs of your apps.</p>
<p><code>/etc</code> is where all the configuration files for your applications are stored. "etc" actually stands for "etcetera" but can also be seen as "editable-text-configurations." The origin of this name dates back to a time when people had folders like /bin for specific purposes but lacked folders for files such as configuration files, data files, or socket files. Although now it is mainly for configuration files, the name has remained unchanged.</p>
<p><code>/opt</code> is used to store all third-party software packages in your system that are not part of the core operating system. This is where my story of finding the nginx config begins. Knowing that I installed nginx using a homebrew formula, and homebrew is third-party software on my computer, I can navigate to <code>/opt/homebrew</code> first. I also know that configuration files are in the /etc directory. So, I can search for either <code>/opt/homebrew/etc</code> or <code>/opt/homebrew/nginx</code>. <code>/opt/homebrew/etc/</code> is the correct path, and then I navigate to <code>/opt/homebrew/etc/nginx</code> to locate where the nginx.conf file is stored.</p>
<p>Now I know where to look for the <code>nginx.conf</code> file, so I don't have to feel frustrated or bother the devops guy every time I need to edit it! One fun fact I got to know while researching about this topic is that the full form of Linux is "<strong>Lovable Intellect Not Using XP".</strong> Thanks for reading and Happy coding!</p>
]]></content:encoded></item><item><title><![CDATA[WTF is an event loop in Javascript]]></title><description><![CDATA[You probably already know that JavaScript is a single-threaded language as you read this blog. However, JavaScript can carry out asynchronous actions that may appear to be occurring in parallel. JavaScript accomplishes this with the assistance of som...]]></description><link>https://blog.sathwikreddygv.com/wtf-is-an-event-loop-in-javascript</link><guid isPermaLink="true">https://blog.sathwikreddygv.com/wtf-is-an-event-loop-in-javascript</guid><category><![CDATA[JavaScript]]></category><category><![CDATA[Event Loop]]></category><dc:creator><![CDATA[sathwikreddy GV]]></dc:creator><pubDate>Sun, 05 May 2024 09:29:33 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/1qlMnKfql5c/upload/ef7c5e435d101b31cbe9d246f5a70db5.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>You probably already know that JavaScript is a single-threaded language as you read this blog. However, JavaScript can carry out asynchronous actions that may appear to be occurring in parallel. JavaScript accomplishes this with the assistance of something known as an <strong>Event Loop</strong>. Let's dive into how an Event Loop operates in this blog.</p>
<p>There are 4 main things to be seen when a Javascript engine is running.</p>
<ul>
<li><p><strong>The CallStack</strong></p>
</li>
<li><p><strong>Webapis/Libuv</strong></p>
</li>
<li><p><strong>Event Queue</strong></p>
</li>
<li><p><strong>Event Loop</strong></p>
</li>
</ul>
<p><strong>CallStack</strong> is literally a stack(LIFO) which tracks what part of the program is running. When a function is called, it gets pushed to the Callstack and when the function returns, it gets popped from the Callstack.</p>
<p><strong>WebAPIs</strong> are built-in browser APIs that enables asynchronous operations in JavaScript(in browser) whereas <strong>Libuv</strong> is a cross-platform library that provides support for asynchronous operations in Node.js. When there is an asynchronous call (like an API call or a DB Query) in our program, it gets handed over to WebAPi/Libuv. While WebAPi/Libuv are handling the queries, Javascript is not blocked and can continue executing the program further.</p>
<p><strong>Event Queue</strong> is a queue(FIFO) where callbacks for these asynchronous calls are pushed once they are processed. So when WebAPi/Libuv are done handling the asynchronous calls, the corresponding callbacks are pushed into the Event Queue.</p>
<p><strong>Event Loop</strong> is the one which connects the <em>Event Queue</em> with the <em>Callstack</em>. Its sole purpose is to check if the Callstack is empty and push the first item from the Event Queue to the Callstack. So when the Callstack is empty, the callback for an asynchronous event that was called earlier gets executed.</p>
<p>This is how Javascript handles async calls being a single-threaded language. Now you know WTF is an event loop in Javascript !</p>
<p>I was heavily inspired by the blog post(<a target="_blank" href="https://dev.to/nodedoctors/an-animated-guide-to-nodejs-event-loop-3g62">https://dev.to/nodedoctors/an-animated-guide-to-nodejs-event-loop-3g62</a>) about how Nodejs event loop works while writing this blog. Thanks for giving it a read!</p>
]]></content:encoded></item></channel></rss>