DEV Community

Abhinov007
Abhinov007

Posted on

Understanding RESP: The Protocol Behind Redis

In the first post, I shared the overview of my Redis clone built from scratch in Node.js.

In this post, I want to go one layer deeper into one of the most important parts of the project:

RESP — the Redis Serialization Protocol.

Before my Redis clone could support commands like:

SET name Alice
GET name
LPUSH queue task1
Enter fullscreen mode Exit fullscreen mode

it first needed to understand what the client was actually sending over the network.
That is where RESP comes in.

Why Redis Needs a Protocol

When we use Redis from a CLI or client library, we usually think in commands:

SET name Alice

But Redis does not internally receive a nice JavaScript array like:

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

It receives raw bytes over a TCP socket.

Those bytes need structure.

The server must know:

  • where the command starts
  • where the command ends
  • how many arguments exist
  • how long each argument is
  • whether the value is a string, integer, error, array, or bulk string

That structure is provided by RESP.

RESP is the language Redis clients and Redis servers use to talk to each other.

What RESP Looks Like

A command like:

GET name

is represented in RESP as:

*2\r\n$3\r\nGET\r\n$4\r\nname\r\n

At first, this looks strange.

But once broken down, it is simple.

*2

means this is an array with 2 elements.

$3
GET
Enter fullscreen mode Exit fullscreen mode

means the first bulk string has length 3, and its value is GET.

$4
name

means the second bulk string has length 4, and its value is name.

So the server parses this:

*2\r\n$3\r\nGET\r\n$4\r\nname\r\n
Enter fullscreen mode Exit fullscreen mode

into this:

["GET", "name"]
Enter fullscreen mode Exit fullscreen mode

Now the command engine can execute it.

Example: SET Command in RESP

A command like:


SET name Alice

Enter fullscreen mode Exit fullscreen mode

becomes:


*3\r\n$3\r\nSET\r\n$4\r\nname\r\n$5\r\nAlice\r\n

Enter fullscreen mode Exit fullscreen mode

Breakdown:


*3

Enter fullscreen mode Exit fullscreen mode

There are 3 parts in the command.


$3

SET

Enter fullscreen mode Exit fullscreen mode

First argument is SET.


$4

name

Enter fullscreen mode Exit fullscreen mode

Second argument is name.


$5

Alice

Enter fullscreen mode Exit fullscreen mode

Third argument is Alice.

The parser converts this into:


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

Enter fullscreen mode Exit fullscreen mode

Then the server can route it to the SET command handler.


RESP Data Types

RESP is not only used for commands.

It is also used for server responses.

Some common RESP types are:


+ Simple Strings

- Errors

: Integers

$ Bulk Strings

* Arrays

Enter fullscreen mode Exit fullscreen mode

For example, when Redis replies with OK:


+OK\r\n

Enter fullscreen mode Exit fullscreen mode

When a key does not exist:


$-1\r\n

Enter fullscreen mode Exit fullscreen mode

When returning an integer:


:1\r\n

Enter fullscreen mode Exit fullscreen mode

When returning an error:


-ERR unknown command\r\n

Enter fullscreen mode Exit fullscreen mode

When returning an array:


*2\r\n$5\r\nhello\r\n$5\r\nworld\r\n

Enter fullscreen mode Exit fullscreen mode

This is why a Redis clone needs both:


RESP parser

RESP encoder

Enter fullscreen mode Exit fullscreen mode

The parser reads client commands.

The encoder sends Redis-compatible responses back.


Why Not Just Split by Spaces?

At the beginning, it is tempting to parse commands like this:


const parts = input.trim().split(" ");

Enter fullscreen mode Exit fullscreen mode

For very simple commands, this might work.

Example:


GET name

Enter fullscreen mode Exit fullscreen mode

But it breaks quickly.

What about values with spaces?


SET message "hello world"

Enter fullscreen mode Exit fullscreen mode

What about binary-safe strings?

What about multiple commands arriving together?

What about half a command arriving in one TCP chunk and the rest arriving later?

Redis needs a protocol that can handle all of this reliably.

RESP solves this by including length information.

Instead of guessing where a value ends, the server reads exactly the number of bytes specified.

For example:


$11

hello world

Enter fullscreen mode Exit fullscreen mode

The server knows the value is exactly 11 bytes long.

That makes RESP binary-safe and much more reliable than splitting strings by spaces.


The Hard Part: TCP Is a Stream

The biggest lesson while implementing RESP was this:

TCP is stream-based, not message-based.

That means the server does not always receive one complete command at a time.

A client might send:


*2\r\n$3\r\nGET\r\n$4\r\nname\r\n

Enter fullscreen mode Exit fullscreen mode

But the server may receive it in pieces.

For example, first chunk:


*2\r\n$3\r\nGE

Enter fullscreen mode Exit fullscreen mode

Second chunk:


T\r\n$4\r\nname\r\n

Enter fullscreen mode Exit fullscreen mode

Or the opposite can happen.

Multiple commands may arrive in a single chunk:


*1\r\n$4\r\nPING\r\n*2\r\n$3\r\nGET\r\n$4\r\nname\r\n

Enter fullscreen mode Exit fullscreen mode

So the parser cannot assume:


one socket data event = one command

Enter fullscreen mode Exit fullscreen mode

That assumption is wrong.

Instead, the parser needs to maintain an internal buffer.


How Incremental Parsing Works

In my Redis clone, the RESP parser works incrementally.

The idea is:


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

Enter fullscreen mode Exit fullscreen mode

The parser has to be patient.

If the buffer does not contain enough data yet, it should not throw an error immediately.

It should simply wait for the next chunk.

For example:


*2\r\n$3\r\nGET\r\n$4\r\nna

Enter fullscreen mode Exit fullscreen mode

This is not invalid.

It is just incomplete.

The parser should remember it and continue when the next chunk arrives:


me\r\n

Enter fullscreen mode Exit fullscreen mode

Then it can produce:


["GET", "name"]

Enter fullscreen mode Exit fullscreen mode

This was one of the first moments where the project felt like real systems engineering.

The problem was not just parsing strings.

The problem was parsing an ongoing network stream safely.


Parser Flow

At a high level, the parser does this:


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

Enter fullscreen mode Exit fullscreen mode

For a command like:


*3\r\n$3\r\nSET\r\n$4\r\nname\r\n$5\r\nAlice\r\n

Enter fullscreen mode Exit fullscreen mode

the parser needs to read:


array length = 3

bulk string length = 3

value = SET

bulk string length = 4

value = name

bulk string length = 5

value = Alice

Enter fullscreen mode Exit fullscreen mode

Only then can it emit:


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

Enter fullscreen mode Exit fullscreen mode

How This Fits Into the Redis Clone

Once the RESP parser produces a command array, the rest of the server can work with normal JavaScript data.

The flow becomes:


Raw TCP bytes

    ↓

RESP parser

    ↓

Command array

    ↓

Command router

    ↓

Command handler

    ↓

Storage / persistence / replication

    ↓

RESP response

    ↓

Socket write

Enter fullscreen mode Exit fullscreen mode

So when the client sends:


SET name Alice

Enter fullscreen mode Exit fullscreen mode

the internal flow is:


*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

Enter fullscreen mode Exit fullscreen mode

This is what made me appreciate Redis more.

A simple command goes through many layers before the user sees OK.


RESP Responses

Parsing client commands is only half of the protocol.

The server also needs to send valid RESP responses.

For example:

For PING:


+PONG\r\n

Enter fullscreen mode Exit fullscreen mode

For successful SET:


+OK\r\n

Enter fullscreen mode Exit fullscreen mode

For GET name when the value is Alice:


$5\r\nAlice\r\n

Enter fullscreen mode Exit fullscreen mode

For a missing key:


$-1\r\n

Enter fullscreen mode Exit fullscreen mode

For DEL name returning one deleted key:


:1\r\n

Enter fullscreen mode Exit fullscreen mode

For errors:


-ERR wrong number of arguments\r\n

Enter fullscreen mode Exit fullscreen mode

This matters because Redis clients expect protocol-compatible responses.

If the response format is wrong, the client cannot understand the server.

So the clone needed RESP encoding helpers, not just command logic.


Things I Learned While Building the Parser

1. Protocol design removes ambiguity

Without RESP, parsing commands with spaces, quotes, or binary data becomes messy.

With RESP, the server knows exactly how many bytes to read.

2. TCP does not care about your command boundaries

TCP gives you a stream of bytes.

It does not promise that each command arrives separately.

The application protocol has to define message boundaries.

3. A parser needs state

The parser must remember incomplete data between chunks.

That buffer is what makes incremental parsing possible.

4. Good parsing keeps the rest of the system clean

Once the parser returns:


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

Enter fullscreen mode Exit fullscreen mode

the command handlers do not need to care about raw TCP bytes.

That separation makes the server easier to build.

5. RESP is simple but powerful

RESP is readable enough to debug manually, but structured enough to support real clients and binary-safe values.

That is a great balance.


Why This Part Matters

It is easy to think the database starts at the storage layer.

But actually, the database starts at the protocol layer.

Before Redis can store a key, it has to understand the command.

Before it can return a value, it has to encode the response.

Before it can support clients reliably, it has to handle partial messages, multiple messages, and malformed input.

That is why implementing RESP was one of the most important parts of this project.

It turned a simple TCP server into something Redis-compatible.


Final Thought

Before this project, I saw Redis commands as text commands:


SET key value

GET key

Enter fullscreen mode Exit fullscreen mode

After implementing RESP, I started seeing them as structured messages flowing over TCP.

That shift changed how I think about backend systems.

A protocol is not just a format.

It is the contract between two systems.

And in Redis, RESP is that contract.

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

Repo:https://github.com/Abhinov007/redis_clone

Live sandbox:https://abhinov007.github.io/Redis_Clone/

Top comments (0)