<?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>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>
