DEV Community

Jaison D Souza
Jaison D Souza

Posted on

Ditch JSON, Go Binary: A Simple Message Protocol

Binary Message Protocol — that's what I am calling this method. Sounds cool right? Well maybe not.

Fancy name, but it’s really just a simple method of sending data by message framing.


Problem

In real-time applications, speed and efficiency are critical. Sending large JSON payloads over WebSockets can add unnecessary overhead, making communication slower and more resource-intensive.

At the same time, the server needs a clear way to identify what kind of message it's receiving. For example, whether it's a request to join a session, an update, or some other action.

When I was building a collaboration platform with TypeScript and Golang, I ran into this exact problem: JSON was too heavy for real-time communication, and I needed a faster, lighter protocol that still allowed the server to understand the type of each message.


JSON Examples

Joining:

{
  "type": "join",
  "payload": { "token": "...", "doc_id": "..." }
}
Enter fullscreen mode Exit fullscreen mode


`

Updating a document:

json
{
"type": "update",
"payload": { "doc_id": "abc123", "delta": [1, 0, 2, 3] }
}
// The delta could be very large

Looks fine — but that's a lot of extra characters being sent on every message.


Solution: Binary Message Protocol

A simple method where:

  • First byte = message type
  • Remaining bytes = payload

That’s it.

So instead of sending the verbose JSON above, we just send something like:


[2, 21, 1212, 1, 21, 2, 4, 243, 544, 4, 5, 67, 7]

Here:

  • 2 = message type (join)
  • The rest = encoded payload

Message Type Mapping

Here’s an example mapping:

Byte Type Description
1 update Document update (deltas)
2 join Join a document session
3 leave Leave a document session
4 ping Keep-alive / heartbeat
5 auth Authentication / validation

Encoding & Decoding

This isn’t just theory — here’s how you could implement it.

Encoding in Go

go
func (c *Client) HandleNotification(msgType uint, message string) {
msgBytes := []byte(message)
payload := make([]byte, 1+len(msgBytes))
payload[0] = byte(msgType)
copy(payload[1:], msgBytes)
c.Send <- payload
}

Decoding in TypeScript

ts
function parseMessage(message: Uint8Array): {
type: number;
payload: Uint8Array;
} {
const type = message[0];
const payload = message.slice(1);
return { type, payload };
}


Sending as Binary over WebSocket

In Go, you just tell the WebSocket library that you're writing binary messages:

go
func (c *Client) WritePump() {
for msg := range c.Send {
// websocket.BinaryMessage ensures data is sent as binary
if err := c.Conn.WriteMessage(websocket.BinaryMessage, msg); err != nil {
log.Println("write error:", err)
return
}
}
}


Why Not JSON? Why Not Protobuf?

  • JSON → Easy to use but heavy.
  • Protobuf → Lightweight but complex.
  • Binary Message Protocol → Minimal, custom, and just enough for real-time collaboration.

Closing Thoughts

This might not be advanced, and many senior engineers may find it straightforward, but I just wanted to share what I learned. Hopefully, you found it useful too.


Project

🔗 Kairo on GitHub

`


Top comments (0)