<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Abhinov007</title>
    <description>The latest articles on DEV Community by Abhinov007 (@abhinov007).</description>
    <link>https://dev.to/abhinov007</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1013812%2F31faced1-4cb4-475c-837b-6451b38feee9.png</url>
      <title>DEV Community: Abhinov007</title>
      <link>https://dev.to/abhinov007</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/abhinov007"/>
    <language>en</language>
    <item>
      <title>Building the In-Memory Store: Strings, Lists, Hashes and Expiry</title>
      <dc:creator>Abhinov007</dc:creator>
      <pubDate>Fri, 19 Jun 2026 17:20:10 +0000</pubDate>
      <link>https://dev.to/abhinov007/building-the-in-memory-store-strings-lists-hashes-and-expiry-301g</link>
      <guid>https://dev.to/abhinov007/building-the-in-memory-store-strings-lists-hashes-and-expiry-301g</guid>
      <description>&lt;p&gt;In the previous post, I wrote about RESP — the protocol layer that lets a Redis server understand commands coming over TCP.&lt;/p&gt;

&lt;p&gt;But parsing a command is only the first step.&lt;/p&gt;

&lt;p&gt;Once the server receives something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;SET name Alice
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and the RESP parser converts it into:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SET&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Alice&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;the next question is:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where does this data actually live?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That is what this post is about.&lt;/p&gt;

&lt;p&gt;In this part of my Redis clone, I built the in-memory storage layer that supports:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Strings&lt;/li&gt;
&lt;li&gt;Lists&lt;/li&gt;
&lt;li&gt;Hashes&lt;/li&gt;
&lt;li&gt;Key expiry&lt;/li&gt;
&lt;li&gt;Type validation&lt;/li&gt;
&lt;li&gt;Lazy expiry on access&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is the layer where the server starts feeling like an actual database.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why the Storage Layer Matters
&lt;/h2&gt;

&lt;p&gt;At first, a Redis clone sounds like it could just be:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;store&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;or:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;store&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Map&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And for the most basic version, that is true.&lt;/p&gt;

&lt;p&gt;If all you want is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;SET name Alice
GET name
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;then a simple key-value map works.&lt;/p&gt;

&lt;p&gt;But Redis is not just a plain key-value object.&lt;/p&gt;

&lt;p&gt;Redis has data types.&lt;/p&gt;

&lt;p&gt;A key can store a string.&lt;/p&gt;

&lt;p&gt;Another key can store a list.&lt;/p&gt;

&lt;p&gt;Another key can store a hash.&lt;/p&gt;

&lt;p&gt;Some keys may expire after 10 seconds.&lt;/p&gt;

&lt;p&gt;Some commands should only work on specific data types.&lt;/p&gt;

&lt;p&gt;So the storage layer needs to track more than just:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;key -&amp;gt; value
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It needs to track:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;key -&amp;gt; value
key -&amp;gt; type
key -&amp;gt; expiry time
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is what turns a simple JavaScript object into a Redis-like in-memory database.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Basic Data Model
&lt;/h2&gt;

&lt;p&gt;The simplest mental model for the store is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;database:
  name      -&amp;gt; Alice
  queue     -&amp;gt; [task1, task2, task3]
  user:1    -&amp;gt; { name: Bob, age: 30 }

expiry:
  session   -&amp;gt; expires at timestamp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So internally, the system has two major responsibilities:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Store the actual data.&lt;/li&gt;
&lt;li&gt;Track when keys should expire.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I kept these concerns separate.&lt;/p&gt;

&lt;p&gt;The database module focuses on storing and retrieving values.&lt;/p&gt;

&lt;p&gt;The expiry module focuses on TTL metadata.&lt;/p&gt;

&lt;p&gt;This separation makes the design easier to reason about.&lt;/p&gt;




&lt;h2&gt;
  
  
  Strings
&lt;/h2&gt;

&lt;p&gt;Strings are the simplest Redis data type.&lt;/p&gt;

&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;SET name Alice
GET name
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The server stores:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;name -&amp;gt; Alice
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When the client runs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;GET name
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;the server checks:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Does the key exist?&lt;/li&gt;
&lt;li&gt;Has the key expired?&lt;/li&gt;
&lt;li&gt;Is the key storing a string?&lt;/li&gt;
&lt;li&gt;If yes, return the value.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The response is encoded back in RESP format.&lt;/p&gt;

&lt;p&gt;For example, if the value is &lt;code&gt;Alice&lt;/code&gt;, the server returns:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$5\r\nAlice\r\n
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the key does not exist, it returns a null bulk string:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$-1\r\n
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The string data type is simple, but it becomes important because many other commands depend on the same storage rules:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;type checking&lt;/li&gt;
&lt;li&gt;expiry checking&lt;/li&gt;
&lt;li&gt;persistence&lt;/li&gt;
&lt;li&gt;replication&lt;/li&gt;
&lt;li&gt;response encoding&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A simple &lt;code&gt;GET&lt;/code&gt; command still passes through multiple layers of the system.&lt;/p&gt;




&lt;h2&gt;
  
  
  SET with Expiry
&lt;/h2&gt;

&lt;p&gt;Redis allows setting keys with expiry:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;SET session abc EX 60
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This means:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Store session = abc
Expire it after 60 seconds
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In my Redis clone, the value is stored in the main database, while the expiry timestamp is stored separately.&lt;/p&gt;

&lt;p&gt;So conceptually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;database:
  session -&amp;gt; abc

expiry:
  session -&amp;gt; currentTime + 60 seconds
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This separation makes expiry easier to manage.&lt;/p&gt;

&lt;p&gt;The value does not need to know about its own expiry.&lt;/p&gt;

&lt;p&gt;The expiry system handles that concern.&lt;/p&gt;




&lt;h2&gt;
  
  
  Lazy Expiry
&lt;/h2&gt;

&lt;p&gt;There are two common ways to expire keys:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Active expiry
Lazy expiry
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Active expiry means a background process keeps scanning for expired keys and deletes them.&lt;/p&gt;

&lt;p&gt;Lazy expiry means the server checks whether a key has expired only when someone tries to access it.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;SET token abc EX 10
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After 10 seconds, the key is expired.&lt;/p&gt;

&lt;p&gt;But instead of deleting it immediately in the background, the server can wait until a client runs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;GET token
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At that moment, the server checks:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Does token have an expiry timestamp?
Is the current time greater than the expiry timestamp?
If yes, delete token and return null.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So the flow becomes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;GET token
   ↓
check expiry
   ↓
expired?
   ↓
delete key + expiry metadata
   ↓
return null
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This approach keeps the system simpler.&lt;/p&gt;

&lt;p&gt;It also taught me an important database design idea:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Sometimes cleanup does not have to happen immediately. It just has to happen before the data is observed.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Why Expiry Affects Everything
&lt;/h2&gt;

&lt;p&gt;Expiry sounds like a small feature, but it touches many parts of the database.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;h3&gt;
  
  
  GET
&lt;/h3&gt;

&lt;p&gt;Before returning a key, the server must check whether it is expired.&lt;/p&gt;

&lt;h3&gt;
  
  
  DEL
&lt;/h3&gt;

&lt;p&gt;When a key is deleted, its expiry metadata should also be removed.&lt;/p&gt;

&lt;h3&gt;
  
  
  FLUSHALL
&lt;/h3&gt;

&lt;p&gt;When the database is cleared, the expiry store should also be cleared.&lt;/p&gt;

&lt;h3&gt;
  
  
  Persistence
&lt;/h3&gt;

&lt;p&gt;If a key has expiry metadata, that information needs to be preserved or handled correctly during save/load.&lt;/p&gt;

&lt;h3&gt;
  
  
  Replication
&lt;/h3&gt;

&lt;p&gt;If the master writes a key with expiry, the replica needs to receive the same write behavior.&lt;/p&gt;

&lt;h3&gt;
  
  
  Sandbox
&lt;/h3&gt;

&lt;p&gt;If a key has TTL, the UI should show the countdown clearly.&lt;/p&gt;

&lt;p&gt;So expiry is not just an extra field.&lt;/p&gt;

&lt;p&gt;It becomes a cross-cutting concern.&lt;/p&gt;




&lt;h2&gt;
  
  
  Lists
&lt;/h2&gt;

&lt;p&gt;The next data type I implemented was Lists.&lt;/p&gt;

&lt;p&gt;Redis lists are useful for queues, stacks, timelines, and task buffers.&lt;/p&gt;

&lt;p&gt;Supported commands include:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;LPUSH queue a b c
RPUSH queue d e
LPOP queue
RPOP queue
LLEN queue
LRANGE queue 0 &lt;span class="nt"&gt;-1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A list is stored internally as an ordered array-like structure.&lt;/p&gt;

&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;LPUSH queue task1
LPUSH queue task2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The list becomes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;queue -&amp;gt; [task2, task1]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Because &lt;code&gt;LPUSH&lt;/code&gt; inserts on the left.&lt;/p&gt;

&lt;p&gt;If we run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;RPUSH queue task3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;the list becomes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;queue -&amp;gt; [task2, task1, task3]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This was useful because it forced me to think about command semantics.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;LPUSH&lt;/code&gt; and &lt;code&gt;RPUSH&lt;/code&gt; sound similar, but they mutate different ends of the list.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;LPOP&lt;/code&gt; and &lt;code&gt;RPOP&lt;/code&gt; also remove from different ends.&lt;/p&gt;




&lt;h2&gt;
  
  
  LRANGE and Index Handling
&lt;/h2&gt;

&lt;p&gt;One of the more interesting list commands is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;LRANGE queue 0 &lt;span class="nt"&gt;-1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This means:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Return all elements from index 0 to the last element.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Redis supports negative indexes.&lt;/p&gt;

&lt;p&gt;So:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;LRANGE queue &lt;span class="nt"&gt;-2&lt;/span&gt; &lt;span class="nt"&gt;-1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;means:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Return the last two elements.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That means the clone needs to normalize indexes.&lt;/p&gt;

&lt;p&gt;The server has to convert:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;start = -2
stop = -1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;into real array indexes based on the list length.&lt;/p&gt;

&lt;p&gt;This small feature makes the implementation more realistic.&lt;/p&gt;

&lt;p&gt;It is not just pushing and popping.&lt;/p&gt;

&lt;p&gt;It is matching Redis-like behavior.&lt;/p&gt;




&lt;h2&gt;
  
  
  Hashes
&lt;/h2&gt;

&lt;p&gt;Hashes are another important Redis data type.&lt;/p&gt;

&lt;p&gt;They let you store field-value pairs under a single key.&lt;/p&gt;

&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;HSET user:1 name Bob age 30
HGET user:1 name
HGETALL user:1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Conceptually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;user:1 -&amp;gt; {
  name: Bob,
  age: 30
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is useful for storing object-like data.&lt;/p&gt;

&lt;p&gt;In the clone, hashes support commands like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;HSET user:1 name Bob age 30
HGET user:1 name
HDEL user:1 age
HGETALL user:1
HLEN user:1
HEXISTS user:1 name
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The interesting part here was handling multiple fields in one command.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;HSET user:1 name Bob age 30 city Delhi
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This command contains multiple field-value pairs.&lt;/p&gt;

&lt;p&gt;The command handler needs to validate that fields and values are paired correctly.&lt;/p&gt;

&lt;p&gt;If there is a missing value, the command should return an error.&lt;/p&gt;

&lt;p&gt;That kind of validation is what makes the command engine feel closer to a real Redis server.&lt;/p&gt;




&lt;h2&gt;
  
  
  Type Checking
&lt;/h2&gt;

&lt;p&gt;One of the most important parts of the storage layer is type checking.&lt;/p&gt;

&lt;p&gt;Imagine this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;SET name Alice
LPUSH name Bob
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This should not work.&lt;/p&gt;

&lt;p&gt;The key &lt;code&gt;name&lt;/code&gt; already stores a string.&lt;/p&gt;

&lt;p&gt;A list command should not be allowed on it.&lt;/p&gt;

&lt;p&gt;So the server must return a wrong type error instead of silently converting the value.&lt;/p&gt;

&lt;p&gt;This means each key needs an associated type.&lt;/p&gt;

&lt;p&gt;Conceptually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;name:
  type: string
  value: Alice

queue:
  type: list
  value: [task1, task2]

user:1:
  type: hash
  value: { name: Bob }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Before executing a command, the handler checks whether the key has the expected type.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;GET expects string
LPUSH expects list
HGET expects hash
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the key does not exist, some commands create it.&lt;/p&gt;

&lt;p&gt;If the key exists with the wrong type, the command returns an error.&lt;/p&gt;

&lt;p&gt;This was one of the key differences between a simple map and a Redis-like store.&lt;/p&gt;




&lt;h2&gt;
  
  
  Command Flow Example: SET
&lt;/h2&gt;

&lt;p&gt;Let’s walk through what happens when a client sends:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;SET name Alice EX 60
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The flow looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;RESP parser receives raw bytes
        ↓
Parser emits ["SET", "name", "Alice", "EX", "60"]
        ↓
Command router identifies SET
        ↓
SET handler validates arguments
        ↓
Database stores name = Alice
        ↓
Type is marked as string
        ↓
Expiry store records TTL
        ↓
AOF persistence records the write
        ↓
RDB snapshot is updated
        ↓
Replication layer can propagate the write
        ↓
Server returns +OK
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So one command touches:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;protocol parsing&lt;/li&gt;
&lt;li&gt;command validation&lt;/li&gt;
&lt;li&gt;storage&lt;/li&gt;
&lt;li&gt;expiry&lt;/li&gt;
&lt;li&gt;persistence&lt;/li&gt;
&lt;li&gt;replication&lt;/li&gt;
&lt;li&gt;response encoding&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is why even “simple” Redis commands are great for learning systems design.&lt;/p&gt;




&lt;h2&gt;
  
  
  Command Flow Example: LPUSH
&lt;/h2&gt;

&lt;p&gt;Now take:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;LPUSH queue task1 task2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The server does:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Parse command
        ↓
Check whether queue exists
        ↓
If it does not exist, create a new list
        ↓
If it exists, verify it is a list
        ↓
Push values to the left
        ↓
Persist the write
        ↓
Return the new list length
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If &lt;code&gt;queue&lt;/code&gt; does not exist, it is created.&lt;/p&gt;

&lt;p&gt;If &lt;code&gt;queue&lt;/code&gt; already stores a list, the command mutates it.&lt;/p&gt;

&lt;p&gt;If &lt;code&gt;queue&lt;/code&gt; stores a string or hash, the command returns a type error.&lt;/p&gt;

&lt;p&gt;This type behavior keeps the database predictable.&lt;/p&gt;




&lt;h2&gt;
  
  
  Command Flow Example: HSET
&lt;/h2&gt;

&lt;p&gt;For:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;HSET user:1 name Bob age 30
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;the server does:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Parse command
        ↓
Check whether user:1 exists
        ↓
If not, create a hash
        ↓
Validate field-value pairs
        ↓
Set name = Bob
        ↓
Set age = 30
        ↓
Return number of new fields added
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Hashes were interesting because they introduced nested storage.&lt;/p&gt;

&lt;p&gt;A string stores one value.&lt;/p&gt;

&lt;p&gt;A list stores an ordered sequence.&lt;/p&gt;

&lt;p&gt;A hash stores a map inside the main map.&lt;/p&gt;

&lt;p&gt;That means the storage layer becomes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;database map
   ↓
key
   ↓
hash map
   ↓
field -&amp;gt; value
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This helped me understand why Redis data types are powerful.&lt;/p&gt;

&lt;p&gt;They let you model different kinds of data without leaving the key-value model.&lt;/p&gt;




&lt;h2&gt;
  
  
  Storage and Persistence
&lt;/h2&gt;

&lt;p&gt;The storage layer does not live alone.&lt;/p&gt;

&lt;p&gt;Every successful write needs to be persisted.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;SET name Alice
LPUSH queue task1
HSET user:1 name Bob
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These commands should update memory, but they should also be recorded by the persistence system.&lt;/p&gt;

&lt;p&gt;That way, if the server restarts, the database can be rebuilt.&lt;/p&gt;

&lt;p&gt;This is where AOF and RDB connect to storage.&lt;/p&gt;

&lt;p&gt;The storage layer owns the current state.&lt;/p&gt;

&lt;p&gt;The persistence layer records or snapshots that state.&lt;/p&gt;

&lt;p&gt;The relationship looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Command handler
   ↓
Storage update
   ↓
AOF append
   ↓
RDB snapshot
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This made the architecture cleaner because storage does not need to know how commands arrived.&lt;/p&gt;

&lt;p&gt;It only needs to expose operations that command handlers can use.&lt;/p&gt;




&lt;h2&gt;
  
  
  Storage and Replication
&lt;/h2&gt;

&lt;p&gt;Replication also depends on successful writes.&lt;/p&gt;

&lt;p&gt;When a write happens on the master, it needs to be propagated to replicas.&lt;/p&gt;

&lt;p&gt;So after a command updates storage, the server can send the same write command to replica connections.&lt;/p&gt;

&lt;p&gt;The flow becomes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Client writes to master
        ↓
Master updates in-memory store
        ↓
Master persists write
        ↓
Master sends command to replicas
        ↓
Replicas apply the same write
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is where command design matters.&lt;/p&gt;

&lt;p&gt;If the command is represented clearly, it can be reused for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;local execution&lt;/li&gt;
&lt;li&gt;persistence replay&lt;/li&gt;
&lt;li&gt;replication propagation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That was an important architectural learning.&lt;/p&gt;




&lt;h2&gt;
  
  
  Storage and the React Sandbox
&lt;/h2&gt;

&lt;p&gt;The React sandbox made the storage layer much easier to understand visually.&lt;/p&gt;

&lt;p&gt;Instead of only seeing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;+OK
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;after running a command, the sandbox shows the internal changes.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;SET name Alice EX 60
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;updates:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Database tab
Expiry tab
AOF log
RDB snapshot
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For a list command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;LPUSH queue task1 task2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;the database view shows the list changing.&lt;/p&gt;

&lt;p&gt;For a hash command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;HSET user:1 name Bob age 30
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;the database view shows the hash fields.&lt;/p&gt;

&lt;p&gt;This made the project more explainable.&lt;/p&gt;

&lt;p&gt;It is one thing to say “the database changed.”&lt;/p&gt;

&lt;p&gt;It is much better to see how it changed.&lt;/p&gt;




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

&lt;h3&gt;
  
  
  1. A key-value store is not always simple
&lt;/h3&gt;

&lt;p&gt;At the beginning, I thought the storage layer would be the easiest part.&lt;/p&gt;

&lt;p&gt;But once data types, expiry, type checking, persistence, and replication are added, it becomes much more interesting.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Types matter
&lt;/h3&gt;

&lt;p&gt;Redis commands are simple because Redis is strict about what each key contains.&lt;/p&gt;

&lt;p&gt;A list command should not work on a string.&lt;/p&gt;

&lt;p&gt;A hash command should not work on a list.&lt;/p&gt;

&lt;p&gt;That strictness makes the system predictable.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Expiry is a system-wide concern
&lt;/h3&gt;

&lt;p&gt;TTL is not just attached to &lt;code&gt;SET&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;It affects reads, deletes, persistence, replication, and UI visualization.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Command semantics are important
&lt;/h3&gt;

&lt;p&gt;Commands like &lt;code&gt;LPUSH&lt;/code&gt;, &lt;code&gt;RPUSH&lt;/code&gt;, &lt;code&gt;LRANGE&lt;/code&gt;, &lt;code&gt;HSET&lt;/code&gt;, and &lt;code&gt;HGETALL&lt;/code&gt; all have small behavior details that need to be handled carefully.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Separating storage and expiry keeps the design clean
&lt;/h3&gt;

&lt;p&gt;The database stores values.&lt;/p&gt;

&lt;p&gt;The expiry module stores TTL metadata.&lt;/p&gt;

&lt;p&gt;This makes both parts easier to test and reason about.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. A database is a collection of layers
&lt;/h3&gt;

&lt;p&gt;The in-memory store is only one layer.&lt;/p&gt;

&lt;p&gt;It sits between protocol parsing, command execution, persistence, replication, and client responses.&lt;/p&gt;




&lt;h2&gt;
  
  
  Final Thought
&lt;/h2&gt;

&lt;p&gt;Before building this, I thought of Redis mainly as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;key -&amp;gt; value
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After building the storage layer, I started seeing it more like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;key -&amp;gt; typed value
key -&amp;gt; expiry metadata
commands -&amp;gt; validated mutations
writes -&amp;gt; persistence + replication
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That shift matters.&lt;/p&gt;

&lt;p&gt;A Redis-like database is not just a map.&lt;/p&gt;

&lt;p&gt;It is a carefully designed system where every command has rules, every key has type behavior, and every write can affect persistence, replication, and expiry.&lt;/p&gt;

&lt;p&gt;That is what made this part of the project so valuable.&lt;/p&gt;

&lt;p&gt;In the next post, I’ll go deeper into persistence: how AOF and RDB work, why both exist, and what I learned while implementing them.&lt;/p&gt;

&lt;p&gt;Repo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://github.com/Abhinov007/redis_clone
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Live sandbox:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https:https://redis-clone.vercel.app/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>backend</category>
      <category>database</category>
      <category>javascript</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Understanding RESP: The Protocol Behind Redis</title>
      <dc:creator>Abhinov007</dc:creator>
      <pubDate>Thu, 18 Jun 2026 08:53:24 +0000</pubDate>
      <link>https://dev.to/abhinov007/understanding-resp-the-protocol-behind-redis-50p4</link>
      <guid>https://dev.to/abhinov007/understanding-resp-the-protocol-behind-redis-50p4</guid>
      <description>&lt;p&gt;In the first post, I shared the overview of my Redis clone built from scratch in Node.js.&lt;/p&gt;

&lt;p&gt;In this post, I want to go one layer deeper into one of the most important parts of the project:&lt;/p&gt;

&lt;p&gt;RESP — the Redis Serialization Protocol.&lt;/p&gt;

&lt;p&gt;Before my Redis clone could support commands like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="n"&gt;Alice&lt;/span&gt;
&lt;span class="k"&gt;GET&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;
&lt;span class="n"&gt;LPUSH&lt;/span&gt; &lt;span class="n"&gt;queue&lt;/span&gt; &lt;span class="n"&gt;task1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;it first needed to understand what the client was actually sending over the network.&lt;br&gt;
That is where RESP comes in.&lt;/p&gt;
&lt;h2&gt;
  
  
  Why Redis Needs a Protocol
&lt;/h2&gt;

&lt;p&gt;When we use Redis from a CLI or client library, we usually think in commands:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;SET name Alice&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;But Redis does not internally receive a nice JavaScript array like:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;["SET", "name", "Alice"]&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;It receives raw bytes over a TCP socket.&lt;/p&gt;

&lt;p&gt;Those bytes need structure.&lt;/p&gt;

&lt;p&gt;The server must know:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;where the command starts&lt;/li&gt;
&lt;li&gt;where the command ends&lt;/li&gt;
&lt;li&gt;how many arguments exist&lt;/li&gt;
&lt;li&gt;how long each argument is&lt;/li&gt;
&lt;li&gt;whether the value is a string, integer, error, array, or bulk string&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That structure is provided by RESP.&lt;/p&gt;

&lt;p&gt;RESP is the language Redis clients and Redis servers use to talk to each other.&lt;/p&gt;
&lt;h2&gt;
  
  
  What RESP Looks Like
&lt;/h2&gt;

&lt;p&gt;A command like:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;GET name&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;is represented in RESP as:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;*2\r\n$3\r\nGET\r\n$4\r\nname\r\n&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;At first, this looks strange.&lt;/p&gt;

&lt;p&gt;But once broken down, it is simple.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;*2&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;means this is an array with 2 elements.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$3
GET
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;means the first bulk string has length 3, and its value is GET.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;$4&lt;br&gt;
name&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;means the second bulk string has length 4, and its value is name.&lt;/p&gt;

&lt;p&gt;So the server parses this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;*2\r\n$3\r\nGET\r\n$4\r\nname\r\n
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;into this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"GET"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the command engine can execute it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Example: SET Command in RESP
&lt;/h2&gt;

&lt;p&gt;A command like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;
SET name Alice

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;becomes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
*3\r\n$3\r\nSET\r\n$4\r\nname\r\n$5\r\nAlice\r\n

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Breakdown:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
*3

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are 3 parts in the command.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
$3

SET

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;First argument is &lt;code&gt;SET&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
$4

name

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Second argument is &lt;code&gt;name&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
$5

Alice

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Third argument is &lt;code&gt;Alice&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The parser converts this into:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SET&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Alice&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then the server can route it to the &lt;code&gt;SET&lt;/code&gt; command handler.&lt;/p&gt;




&lt;h2&gt;
  
  
  RESP Data Types
&lt;/h2&gt;

&lt;p&gt;RESP is not only used for commands.&lt;/p&gt;

&lt;p&gt;It is also used for server responses.&lt;/p&gt;

&lt;p&gt;Some common RESP types are:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
+ Simple Strings

- Errors

: Integers

$ Bulk Strings

* Arrays

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For example, when Redis replies with OK:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
+OK\r\n

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When a key does not exist:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
$-1\r\n

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When returning an integer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
:1\r\n

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When returning an error:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
-ERR unknown command\r\n

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When returning an array:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
*2\r\n$5\r\nhello\r\n$5\r\nworld\r\n

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is why a Redis clone needs both:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
RESP parser

RESP encoder

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The parser reads client commands.&lt;/p&gt;

&lt;p&gt;The encoder sends Redis-compatible responses back.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Not Just Split by Spaces?
&lt;/h2&gt;

&lt;p&gt;At the beginning, it is tempting to parse commands like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;parts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For very simple commands, this might work.&lt;/p&gt;

&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;
GET name

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But it breaks quickly.&lt;/p&gt;

&lt;p&gt;What about values with spaces?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;
SET message &lt;span class="s2"&gt;"hello world"&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What about binary-safe strings?&lt;/p&gt;

&lt;p&gt;What about multiple commands arriving together?&lt;/p&gt;

&lt;p&gt;What about half a command arriving in one TCP chunk and the rest arriving later?&lt;/p&gt;

&lt;p&gt;Redis needs a protocol that can handle all of this reliably.&lt;/p&gt;

&lt;p&gt;RESP solves this by including length information.&lt;/p&gt;

&lt;p&gt;Instead of guessing where a value ends, the server reads exactly the number of bytes specified.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
$11

hello world

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The server knows the value is exactly 11 bytes long.&lt;/p&gt;

&lt;p&gt;That makes RESP binary-safe and much more reliable than splitting strings by spaces.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Hard Part: TCP Is a Stream
&lt;/h2&gt;

&lt;p&gt;The biggest lesson while implementing RESP was this:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TCP is stream-based, not message-based.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That means the server does not always receive one complete command at a time.&lt;/p&gt;

&lt;p&gt;A client might send:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
*2\r\n$3\r\nGET\r\n$4\r\nname\r\n

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But the server may receive it in pieces.&lt;/p&gt;

&lt;p&gt;For example, first chunk:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
*2\r\n$3\r\nGE

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Second chunk:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
T\r\n$4\r\nname\r\n

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or the opposite can happen.&lt;/p&gt;

&lt;p&gt;Multiple commands may arrive in a single chunk:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
*1\r\n$4\r\nPING\r\n*2\r\n$3\r\nGET\r\n$4\r\nname\r\n

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So the parser cannot assume:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
one socket data event = one command

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That assumption is wrong.&lt;/p&gt;

&lt;p&gt;Instead, the parser needs to maintain an internal buffer.&lt;/p&gt;




&lt;h2&gt;
  
  
  How Incremental Parsing Works
&lt;/h2&gt;

&lt;p&gt;In my Redis clone, the RESP parser works incrementally.&lt;/p&gt;

&lt;p&gt;The idea is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
1. Receive a chunk from the socket

2. Add it to a buffer

3. Try to parse a complete command

4. If the command is incomplete, wait for more data

5. If one command is complete, return it

6. If more data remains, keep parsing

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The parser has to be patient.&lt;/p&gt;

&lt;p&gt;If the buffer does not contain enough data yet, it should not throw an error immediately.&lt;/p&gt;

&lt;p&gt;It should simply wait for the next chunk.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
*2\r\n$3\r\nGET\r\n$4\r\nna

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is not invalid.&lt;/p&gt;

&lt;p&gt;It is just incomplete.&lt;/p&gt;

&lt;p&gt;The parser should remember it and continue when the next chunk arrives:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
me\r\n

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then it can produce:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;GET&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This was one of the first moments where the project felt like real systems engineering.&lt;/p&gt;

&lt;p&gt;The problem was not just parsing strings.&lt;/p&gt;

&lt;p&gt;The problem was parsing an ongoing network stream safely.&lt;/p&gt;




&lt;h2&gt;
  
  
  Parser Flow
&lt;/h2&gt;

&lt;p&gt;At a high level, the parser does this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
Socket receives bytes

        ↓

Append bytes to parser buffer

        ↓

Check first RESP marker

        ↓

Parse array length

        ↓

Parse each bulk string

        ↓

If complete, emit command

        ↓

Remove parsed bytes from buffer

        ↓

Keep remaining bytes for next parse

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For a command like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
*3\r\n$3\r\nSET\r\n$4\r\nname\r\n$5\r\nAlice\r\n

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;the parser needs to read:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
array length = 3

bulk string length = 3

value = SET

bulk string length = 4

value = name

bulk string length = 5

value = Alice

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Only then can it emit:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SET&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Alice&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  How This Fits Into the Redis Clone
&lt;/h2&gt;

&lt;p&gt;Once the RESP parser produces a command array, the rest of the server can work with normal JavaScript data.&lt;/p&gt;

&lt;p&gt;The flow becomes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
Raw TCP bytes

    ↓

RESP parser

    ↓

Command array

    ↓

Command router

    ↓

Command handler

    ↓

Storage / persistence / replication

    ↓

RESP response

    ↓

Socket write

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So when the client sends:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;
SET name Alice

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;the internal flow is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
*3\r\n$3\r\nSET\r\n$4\r\nname\r\n$5\r\nAlice\r\n

        ↓

["SET", "name", "Alice"]

        ↓

SET handler

        ↓

store name = Alice

        ↓

append to AOF

        ↓

update snapshot

        ↓

propagate to replicas

        ↓

+OK\r\n

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is what made me appreciate Redis more.&lt;/p&gt;

&lt;p&gt;A simple command goes through many layers before the user sees &lt;code&gt;OK&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  RESP Responses
&lt;/h2&gt;

&lt;p&gt;Parsing client commands is only half of the protocol.&lt;/p&gt;

&lt;p&gt;The server also needs to send valid RESP responses.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;p&gt;For &lt;code&gt;PING&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
+PONG\r\n

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For successful &lt;code&gt;SET&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
+OK\r\n

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For &lt;code&gt;GET name&lt;/code&gt; when the value is Alice:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
$5\r\nAlice\r\n

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For a missing key:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
$-1\r\n

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For &lt;code&gt;DEL name&lt;/code&gt; returning one deleted key:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
:1\r\n

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For errors:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
-ERR wrong number of arguments\r\n

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This matters because Redis clients expect protocol-compatible responses.&lt;/p&gt;

&lt;p&gt;If the response format is wrong, the client cannot understand the server.&lt;/p&gt;

&lt;p&gt;So the clone needed RESP encoding helpers, not just command logic.&lt;/p&gt;




&lt;h2&gt;
  
  
  Things I Learned While Building the Parser
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Protocol design removes ambiguity
&lt;/h3&gt;

&lt;p&gt;Without RESP, parsing commands with spaces, quotes, or binary data becomes messy.&lt;/p&gt;

&lt;p&gt;With RESP, the server knows exactly how many bytes to read.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. TCP does not care about your command boundaries
&lt;/h3&gt;

&lt;p&gt;TCP gives you a stream of bytes.&lt;/p&gt;

&lt;p&gt;It does not promise that each command arrives separately.&lt;/p&gt;

&lt;p&gt;The application protocol has to define message boundaries.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. A parser needs state
&lt;/h3&gt;

&lt;p&gt;The parser must remember incomplete data between chunks.&lt;/p&gt;

&lt;p&gt;That buffer is what makes incremental parsing possible.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Good parsing keeps the rest of the system clean
&lt;/h3&gt;

&lt;p&gt;Once the parser returns:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SET&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Alice&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;the command handlers do not need to care about raw TCP bytes.&lt;/p&gt;

&lt;p&gt;That separation makes the server easier to build.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. RESP is simple but powerful
&lt;/h3&gt;

&lt;p&gt;RESP is readable enough to debug manually, but structured enough to support real clients and binary-safe values.&lt;/p&gt;

&lt;p&gt;That is a great balance.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why This Part Matters
&lt;/h2&gt;

&lt;p&gt;It is easy to think the database starts at the storage layer.&lt;/p&gt;

&lt;p&gt;But actually, the database starts at the protocol layer.&lt;/p&gt;

&lt;p&gt;Before Redis can store a key, it has to understand the command.&lt;/p&gt;

&lt;p&gt;Before it can return a value, it has to encode the response.&lt;/p&gt;

&lt;p&gt;Before it can support clients reliably, it has to handle partial messages, multiple messages, and malformed input.&lt;/p&gt;

&lt;p&gt;That is why implementing RESP was one of the most important parts of this project.&lt;/p&gt;

&lt;p&gt;It turned a simple TCP server into something Redis-compatible.&lt;/p&gt;




&lt;h2&gt;
  
  
  Final Thought
&lt;/h2&gt;

&lt;p&gt;Before this project, I saw Redis commands as text commands:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;
SET key value

GET key

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After implementing RESP, I started seeing them as structured messages flowing over TCP.&lt;/p&gt;

&lt;p&gt;That shift changed how I think about backend systems.&lt;/p&gt;

&lt;p&gt;A protocol is not just a format.&lt;/p&gt;

&lt;p&gt;It is the contract between two systems.&lt;/p&gt;

&lt;p&gt;And in Redis, RESP is that contract.&lt;/p&gt;

&lt;p&gt;In the next post, I’ll go deeper into the in-memory storage layer: how strings, lists, hashes, and expiry are represented internally.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/Abhinov007/redis_clone" rel="noopener noreferrer"&gt;Repo:https://github.com/Abhinov007/redis_clone&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://abhinov007.github.io/Redis_Clone/" rel="noopener noreferrer"&gt;Live sandbox:https://abhinov007.github.io/Redis_Clone/&lt;/a&gt;&lt;/p&gt;

</description>
      <category>redis</category>
      <category>node</category>
      <category>distributedsystems</category>
      <category>javascript</category>
    </item>
    <item>
      <title>I Built a Redis Clone from Scratch in Node.js — RESP, Persistence, Replication, Pub/Sub and Transactions</title>
      <dc:creator>Abhinov007</dc:creator>
      <pubDate>Wed, 17 Jun 2026 08:40:00 +0000</pubDate>
      <link>https://dev.to/abhinov007/i-built-a-redis-clone-from-scratch-in-nodejs-resp-persistence-replication-pubsub-and-46lk</link>
      <guid>https://dev.to/abhinov007/i-built-a-redis-clone-from-scratch-in-nodejs-resp-persistence-replication-pubsub-and-46lk</guid>
      <description>&lt;p&gt;Most of us use Redis like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SET name Alice
GET name
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It feels simple.&lt;/p&gt;

&lt;p&gt;You send a command.&lt;br&gt;
Redis stores something.&lt;br&gt;
You get the value back.&lt;/p&gt;

&lt;p&gt;But I wanted to understand what actually happens behind that simplicity.&lt;br&gt;
So I built a Redis-compatible server from scratch in Node.js.&lt;/p&gt;

&lt;p&gt;Not a wrapper around Redis.&lt;br&gt;
Not a client library.&lt;br&gt;
Not a toy object in memory.&lt;/p&gt;

&lt;p&gt;A real TCP server that can understand Redis-style commands, parse the RESP protocol, store data in memory, persist writes, support transactions, handle Pub/Sub, and replicate data from master to replica.&lt;/p&gt;

&lt;p&gt;The repo includes a Node.js Redis server, a CLI client, a RESP parser, storage modules, persistence modules, replication logic, Pub/Sub, tests, and a React sandbox for visualizing internals.&lt;br&gt;
&lt;a href="https://github.com/Abhinov007/redis_clone" rel="noopener noreferrer"&gt;Repo&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://redis-clone.vercel.app/" rel="noopener noreferrer"&gt;Live Sandbox&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why I Built This&lt;/strong&gt;&lt;br&gt;
I have used Redis in multiple projects for caching, queues, sessions, rate limiting, and real-time features.&lt;/p&gt;

&lt;p&gt;But I realized something:&lt;br&gt;
I knew how to use Redis, but I did not deeply understand how Redis works.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SET session:123 abc EX 60
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This one command looks tiny.&lt;br&gt;
But internally, a Redis-like server has to do a lot:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Accept a TCP connection&lt;/li&gt;
&lt;li&gt;Read raw bytes from the socket&lt;/li&gt;
&lt;li&gt;Parse the command format&lt;/li&gt;
&lt;li&gt;Validate the command&lt;/li&gt;
&lt;li&gt;Store the key-value pair&lt;/li&gt;
&lt;li&gt;Track expiry metadata&lt;/li&gt;
&lt;li&gt;Append the write to persistence&lt;/li&gt;
&lt;li&gt;Return a protocol-compatible response&lt;/li&gt;
&lt;li&gt;Potentially propagate the write to replicas&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is a lot of systems engineering hidden behind one line.&lt;br&gt;
So the goal of this project was simple:&lt;br&gt;
&lt;em&gt;Rebuild enough of Redis from scratch to understand the core ideas behind it.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;*&lt;em&gt;What I Built&lt;br&gt;
*&lt;/em&gt;&lt;br&gt;
The project is a Redis-compatible server built in Node.js. The README describes it as implementing the RESP protocol, dual-layer persistence with AOF and RDB, master/replica replication, Pub/Sub messaging, transactions, and an interactive React sandbox.&lt;/p&gt;

&lt;p&gt;The server supports:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Strings&lt;/li&gt;
&lt;li&gt;Lists&lt;/li&gt;
&lt;li&gt;Hashes&lt;/li&gt;
&lt;li&gt;Key expiry&lt;/li&gt;
&lt;li&gt;PING&lt;/li&gt;
&lt;li&gt;DEL&lt;/li&gt;
&lt;li&gt;FLUSHALL&lt;/li&gt;
&lt;li&gt;Transactions&lt;/li&gt;
&lt;li&gt;Pub/Sub&lt;/li&gt;
&lt;li&gt;Replication&lt;/li&gt;
&lt;li&gt;Persistence&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Some example commands:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SET name Alice EX 60
GET name

LPUSH queue task1 task2 task3
LRANGE queue 0 -1

HSET user:1 name Bob age 30
HGET user:1 name

MULTI
SET a 1
SET b 2
EXEC

SUBSCRIBE news
PUBLISH news "Hello World"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The README lists support for strings, lists, hashes, key expiry, PING, DEL, and FLUSHALL as part of the core command set.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;The Architecture&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;At a high level, the server looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;`TCP Client
   ↓
Node.js TCP Server
   ↓
RESP Parser
   ↓
Command Router
   ↓
Command Handler
   ↓
In-Memory Store
   ↓
Persistence / Replication / Response`

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When a client sends:&lt;br&gt;
&lt;code&gt;SET name Alice&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;the server does roughly this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Receive bytes over TCP&lt;/li&gt;
&lt;li&gt;Parse the bytes into a command&lt;/li&gt;
&lt;li&gt;Identify SET as the command&lt;/li&gt;
&lt;li&gt;Validate the arguments&lt;/li&gt;
&lt;li&gt;Store name = Alice in memory&lt;/li&gt;
&lt;li&gt;Append the write to AOF&lt;/li&gt;
&lt;li&gt;Update the RDB-style snapshot&lt;/li&gt;
&lt;li&gt;Propagate the command to replicas&lt;/li&gt;
&lt;li&gt;Send +OK back to the client&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That flow made me realize that a database is not just “storage”.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Networking&lt;/li&gt;
&lt;li&gt;Protocol parsing&lt;/li&gt;
&lt;li&gt;Command execution&lt;/li&gt;
&lt;li&gt;Memory management&lt;/li&gt;
&lt;li&gt;Persistence&lt;/li&gt;
&lt;li&gt;Replication&lt;/li&gt;
&lt;li&gt;Client state&lt;/li&gt;
&lt;li&gt;Failure handling&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Layer 1: TCP Server
&lt;/h2&gt;

&lt;p&gt;The first layer was the TCP server.&lt;br&gt;
Redis does not work like a normal REST API.&lt;/p&gt;

&lt;p&gt;There is no:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;POST /set
GET /get
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Instead, clients connect to a TCP port and send protocol-formatted commands.&lt;/p&gt;

&lt;p&gt;In this project, the server runs on port 6379 by default, similar to Redis.&lt;/p&gt;

&lt;p&gt;Example:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;node server.js&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;or:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;node server.js --port 6380&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The server listens for socket connections, receives chunks of data, parses them, executes commands, and writes responses back to the client.&lt;/p&gt;

&lt;p&gt;This was the first important lesson:&lt;/p&gt;

&lt;p&gt;A database server is also a network server.&lt;/p&gt;

&lt;h2&gt;
  
  
  Layer 2: RESP Protocol
&lt;/h2&gt;

&lt;p&gt;Redis clients communicate using RESP, the Redis Serialization Protocol.&lt;/p&gt;

&lt;p&gt;That means the server cannot just read plain strings and split them by spaces forever.&lt;/p&gt;

&lt;p&gt;It needs to understand structured protocol messages.&lt;/p&gt;

&lt;p&gt;For example, a Redis command can be represented like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;*2
$3
GET
$4
name
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That means:&lt;/p&gt;

&lt;p&gt;["GET", "name"]&lt;/p&gt;

&lt;p&gt;The parser has to understand arrays, bulk strings, simple strings, integers, and errors.&lt;/p&gt;

&lt;p&gt;The interesting part is TCP chunking.&lt;/p&gt;

&lt;p&gt;A command may arrive like this:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;*2\r\n$3\r\nGET\r\n&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;and then later:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;$4\r\nname\r\n&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Or multiple commands may arrive together in a single chunk.&lt;/p&gt;

&lt;p&gt;So the parser cannot assume that every socket event equals one full command.&lt;/p&gt;

&lt;p&gt;It needs to buffer incomplete data and continue parsing once more bytes arrive.&lt;/p&gt;

&lt;p&gt;That was one of the first places where this stopped feeling like a normal backend project and started feeling like a systems project.&lt;/p&gt;

&lt;h2&gt;
  
  
  Layer 3: In-Memory Store
&lt;/h2&gt;

&lt;p&gt;Once the command is parsed, the server needs somewhere to store data.&lt;/p&gt;

&lt;p&gt;At the core, Redis is an in-memory data store.&lt;/p&gt;

&lt;p&gt;So I built a storage layer around an in-memory Map.&lt;/p&gt;

&lt;p&gt;But the store cannot treat every value the same way.&lt;/p&gt;

&lt;p&gt;A string is different from a list.&lt;br&gt;
A list is different from a hash.&lt;br&gt;
A key with expiry is different from a normal key.&lt;/p&gt;

&lt;p&gt;So the storage layer had to track:&lt;/p&gt;

&lt;p&gt;key&lt;br&gt;
value&lt;br&gt;
type&lt;br&gt;
expiry metadata&lt;/p&gt;

&lt;p&gt;Supported data types include:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;String&lt;br&gt;
List&lt;br&gt;
Hash&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The README describes the server as supporting three data types: strings, lists, and hashes.&lt;/p&gt;

&lt;p&gt;Example string command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="n"&gt;Alice&lt;/span&gt;
&lt;span class="k"&gt;GET&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Example list command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="n"&gt;LPUSH&lt;/span&gt; &lt;span class="n"&gt;queue&lt;/span&gt; &lt;span class="n"&gt;task1&lt;/span&gt; &lt;span class="n"&gt;task2&lt;/span&gt;
&lt;span class="n"&gt;RPOP&lt;/span&gt; &lt;span class="n"&gt;queue&lt;/span&gt;
&lt;span class="n"&gt;LRANGE&lt;/span&gt; &lt;span class="n"&gt;queue&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Example hash command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="n"&gt;HSET&lt;/span&gt; &lt;span class="k"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="n"&gt;Bob&lt;/span&gt; &lt;span class="n"&gt;age&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;
&lt;span class="n"&gt;HGET&lt;/span&gt; &lt;span class="k"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;
&lt;span class="n"&gt;HGETALL&lt;/span&gt; &lt;span class="k"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This layer taught me why Redis has strict type behavior.&lt;/p&gt;

&lt;p&gt;For example, if a key is storing a string, you should not be able to run a list command on it.&lt;/p&gt;

&lt;p&gt;That kind of validation is what turns a basic key-value map into a real database-like system.&lt;/p&gt;

&lt;h2&gt;
  
  
  Layer 4: Expiry
&lt;/h2&gt;

&lt;p&gt;Redis is heavily used for temporary data:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sessions
OTP codes
cache entries
rate limits
temporary locks
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So expiry support was important.&lt;/p&gt;

&lt;p&gt;The clone supports commands like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="n"&gt;Alice&lt;/span&gt; &lt;span class="n"&gt;EX&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That means the key should automatically expire after 60 seconds.&lt;/p&gt;

&lt;p&gt;There are two ways to think about expiry:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Active expiry: background cleanup
Lazy expiry: delete only when accessed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this project, expiry metadata is tracked separately, and keys can be checked when accessed.&lt;/p&gt;

&lt;p&gt;So when a user runs:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;GET name&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;the server checks:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Does this key exist?
Does it have expiry metadata?
Has the expiry time passed?
If yes, delete and return null.
If no, return the value.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This sounds small, but expiry affects many parts of the system:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;GET should respect expiry
DEL should remove expiry metadata
Persistence should preserve expiry information
Replication should handle expiring keys correctly
The sandbox should show TTL countdowns
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Layer 5: Persistence
&lt;/h2&gt;

&lt;p&gt;An in-memory store is fast, but if the process dies, memory disappears.&lt;/p&gt;

&lt;p&gt;So I added persistence.&lt;/p&gt;

&lt;p&gt;The project implements two persistence styles:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;AOF
RDB
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;AOF means Append Only File.&lt;/p&gt;

&lt;p&gt;Every write command gets appended to a log file.&lt;/p&gt;

&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="n"&gt;Alice&lt;/span&gt;
&lt;span class="n"&gt;LPUSH&lt;/span&gt; &lt;span class="n"&gt;queue&lt;/span&gt; &lt;span class="n"&gt;task1&lt;/span&gt;
&lt;span class="n"&gt;HSET&lt;/span&gt; &lt;span class="k"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="n"&gt;Bob&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On restart, the server can replay the commands and rebuild the database.&lt;/p&gt;

&lt;p&gt;RDB is snapshot-style persistence.&lt;/p&gt;

&lt;p&gt;Instead of replaying every command, the server stores a full snapshot of the current database.&lt;/p&gt;

&lt;p&gt;The README describes this as dual-layer persistence: AOF writes every command to database.aof, while RDB writes a JSON snapshot to dump.rdb.&lt;/p&gt;

&lt;p&gt;This helped me understand the tradeoff:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;AOF gives a detailed write history.
RDB gives a compact snapshot.
AOF can be more durable.
RDB can be faster to load.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In real Redis, these persistence modes have many more details.&lt;/p&gt;

&lt;p&gt;But even implementing a simplified version made the core design much clearer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Layer 6: Transactions
&lt;/h2&gt;

&lt;p&gt;Redis supports transactions using:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="n"&gt;MULTI&lt;/span&gt;
&lt;span class="k"&gt;EXEC&lt;/span&gt;
&lt;span class="n"&gt;DISCARD&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So I implemented that too.&lt;/p&gt;

&lt;p&gt;The idea is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;MULTI starts a transaction block.&lt;/li&gt;
&lt;li&gt;Commands are queued instead of executed immediately.&lt;/li&gt;
&lt;li&gt;EXEC runs the queued commands.&lt;/li&gt;
&lt;li&gt;DISCARD cancels them.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="n"&gt;MULTI&lt;/span&gt;
&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="n"&gt;Alice&lt;/span&gt;
&lt;span class="k"&gt;GET&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;
&lt;span class="k"&gt;EXEC&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important thing here is that transaction state is per client.&lt;/p&gt;

&lt;p&gt;One client may be inside a transaction.&lt;br&gt;
Another client may not be.&lt;/p&gt;

&lt;p&gt;That means the server needs to maintain client-specific state, not just global database state.&lt;/p&gt;

&lt;p&gt;The README lists MULTI, EXEC, and DISCARD as supported transaction commands.&lt;/p&gt;

&lt;p&gt;This changed how I thought about the server.&lt;/p&gt;

&lt;p&gt;Earlier, I saw it as:&lt;/p&gt;

&lt;p&gt;command comes in → execute command&lt;/p&gt;

&lt;p&gt;After transactions, it became:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;command comes in
    ↓
is this client inside MULTI?
    ↓
if yes, queue command
if no, execute command
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is a much more realistic server model.&lt;/p&gt;

&lt;h2&gt;
  
  
  Layer 7: Pub/Sub
&lt;/h2&gt;

&lt;p&gt;The next feature was Pub/Sub.&lt;/p&gt;

&lt;p&gt;Redis Pub/Sub lets clients subscribe to channels and receive messages when someone publishes to that channel.&lt;/p&gt;

&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="n"&gt;SUBSCRIBE&lt;/span&gt; &lt;span class="n"&gt;news&lt;/span&gt;

&lt;span class="k"&gt;Then&lt;/span&gt; &lt;span class="n"&gt;another&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="n"&gt;can&lt;/span&gt; &lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;

&lt;span class="n"&gt;PUBLISH&lt;/span&gt; &lt;span class="n"&gt;news&lt;/span&gt; &lt;span class="nv"&gt;"Hello World"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Internally, this means the server needs a mapping like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;news -&amp;gt; [socket1, socket2, socket3]
alerts -&amp;gt; [socket4, socket5]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When a message is published, the server finds all subscribers and pushes the message to their sockets.&lt;/p&gt;

&lt;p&gt;The README lists SUBSCRIBE, UNSUBSCRIBE, and PUBLISH, with channel-to-subscriber socket mapping and cleanup on disconnect.&lt;/p&gt;

&lt;p&gt;This was one of the most fun parts of the project because it moved the server beyond request-response.&lt;/p&gt;

&lt;p&gt;Normal commands are like:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;client asks → server replies&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Pub/Sub is different:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;client subscribes
server remembers the socket
later, server pushes messages to it
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That makes the server feel alive.&lt;/p&gt;

&lt;h2&gt;
  
  
  Layer 8: Replication
&lt;/h2&gt;

&lt;p&gt;Replication was the hardest and most interesting part.&lt;/p&gt;

&lt;p&gt;The clone supports master-replica replication.&lt;/p&gt;

&lt;p&gt;You can run one server as master:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;node server.js &lt;span class="nt"&gt;--port&lt;/span&gt; 6379
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And another as replica:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;node server.js &lt;span class="nt"&gt;--port&lt;/span&gt; 6380 &lt;span class="nt"&gt;--replicaof&lt;/span&gt; localhost 6379
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Writes on the master are propagated to the replica. The README also describes full sync with an RDB snapshot and partial resync on reconnect.&lt;/p&gt;

&lt;p&gt;At a high level:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Replica connects to master&lt;/li&gt;
&lt;li&gt;Replica performs handshake&lt;/li&gt;
&lt;li&gt;Master sends snapshot&lt;/li&gt;
&lt;li&gt;Master streams future write commands&lt;/li&gt;
&lt;li&gt;Replica applies writes&lt;/li&gt;
&lt;li&gt;Replica stays read-only for normal clients&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The project includes:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;master replication ID&lt;/li&gt;
&lt;li&gt;1 MB circular backlog&lt;/li&gt;
&lt;li&gt;full resync&lt;/li&gt;
&lt;li&gt;partial resync&lt;/li&gt;
&lt;li&gt;replica handshake state machine&lt;/li&gt;
&lt;li&gt;auto-reconnect&lt;/li&gt;
&lt;li&gt;REPLCONF ACK heartbeats&lt;/li&gt;
&lt;li&gt;read-only replica mode&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Those replication details are listed in the README’s replication section.&lt;/p&gt;

&lt;p&gt;This was where I realized that replication is not just “copy data to another server”.&lt;/p&gt;

&lt;p&gt;It involves:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;connection state&lt;/li&gt;
&lt;li&gt;offset tracking&lt;/li&gt;
&lt;li&gt;backlogs&lt;/li&gt;
&lt;li&gt;snapshots&lt;/li&gt;
&lt;li&gt;handshakes&lt;/li&gt;
&lt;li&gt;heartbeats&lt;/li&gt;
&lt;li&gt;reconnect behavior&lt;/li&gt;
&lt;li&gt;read-only enforcement&lt;/li&gt;
&lt;li&gt;command propagation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Replication turned this from a key-value store into a distributed systems project.&lt;/p&gt;

&lt;h2&gt;
  
  
  Layer 9: React Sandbox
&lt;/h2&gt;

&lt;p&gt;After building the server, I wanted a way to make the internals easier to understand.&lt;/p&gt;

&lt;p&gt;So I built a React + Vite sandbox that simulates the server in the browser.&lt;/p&gt;

&lt;p&gt;The sandbox lets you type Redis commands and watch internal state update live.&lt;/p&gt;

&lt;p&gt;It shows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Database&lt;/li&gt;
&lt;li&gt;Expiry store&lt;/li&gt;
&lt;li&gt;AOF log&lt;/li&gt;
&lt;li&gt;RDB snapshot&lt;/li&gt;
&lt;li&gt;Pub/Sub channels&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The README says the sandbox simulates the server entirely in the browser and includes tabs for database, expiry, AOF log, RDB snapshot, and Pub/Sub.&lt;/p&gt;

&lt;p&gt;This is useful because most backend internals are invisible.&lt;/p&gt;

&lt;p&gt;You run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="n"&gt;Alice&lt;/span&gt; &lt;span class="n"&gt;EX&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and normally you only see:&lt;/p&gt;

&lt;p&gt;OK&lt;/p&gt;

&lt;p&gt;But in the sandbox, you can see:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;The key appears in the database
The TTL countdown starts
The AOF log updates
The RDB snapshot changes
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That made the project not just functional, but explainable.&lt;/p&gt;

</description>
      <category>redis</category>
      <category>backend</category>
      <category>distributedsystems</category>
      <category>node</category>
    </item>
  </channel>
</rss>
