DEV Community

Debojyoti Ganguly
Debojyoti Ganguly

Posted on

Building Peetopee: A Peer-to-Peer Messenger with Bun, libp2p, SQLite, and Modern Cryptography

Most messaging applications rely on centralized infrastructure. Messages travel through company-owned servers, identities are managed by centralized systems, and availability depends on cloud providers.

I wanted to understand what happens when you remove all of that.

Instead of reading more documentation about distributed systems, I decided to build one.

The result was Peetopee, a proof-of-concept peer-to-peer messenger built with Bun, TypeScript, libp2p, SQLite, and WebCrypto.

The goal wasn't to build a Signal competitor. The goal was to learn how peer discovery, cryptographic identity, key exchange, session management, and encrypted messaging actually work under the hood.

The Requirements

Before writing any code, I defined a few constraints:

  • No centralized server
  • Persistent peer identities
  • End-to-end encrypted messages
  • Local message history
  • Terminal-first interface
  • Direct peer-to-peer communication

That gave me a surprisingly complete messaging system architecture.

Why Bun?

I chose Bun mostly because I wanted to see how far its ecosystem had matured.

A few features made it particularly attractive:

  • Built-in TypeScript support
  • Fast startup times
  • Native SQLite support
  • WebCrypto APIs
  • Minimal tooling setup

Since this project involved networking, cryptography, and persistence, Bun covered most of the infrastructure I needed out of the box.

The Networking Layer

For networking, I used libp2p.

Instead of worrying about low-level socket management, libp2p provides abstractions for:

  • Peer discovery
  • Transport security
  • Stream multiplexing
  • Peer identification

Peetopee uses:

  • TCP
  • Noise
  • Yamux
  • mDNS
  • Identify

Communication happens over a custom protocol:

/p2p-chat/1.0.0

Messages are serialized as JSON and exchanged over libp2p streams.

Two Identities, Not One

One design decision I particularly liked was separating network identity from application identity.

Network Identity

libp2p already uses Ed25519 keys to generate Peer IDs.

These identities are persisted so nodes remain recognizable after restarts.

Application Identity

I created a second identity using WebCrypto.

This identity handles:

  • Handshake authentication
  • Session establishment
  • Message signing

Keeping them separate made the architecture much cleaner.

Building the Handshake

The cryptographic handshake combines several modern primitives:

  • Ed25519 signatures
  • P-256 ECDH
  • HKDF
  • AES-GCM

The process looks roughly like this:

  • Exchange identities
  • Verify signatures
  • Exchange ephemeral keys
  • Perform ECDH
  • Derive session keys with HKDF
  • Store the session Once complete, both peers possess the same symmetric encryption key without transmitting it directly.

Persistence

A chat application isn't very useful if it forgets everything after a restart.

Peetopee stores:

  • Identities
  • Prekeys
  • Sessions
  • Messages

in SQLite.

The storage layer is intentionally boring, and that's a compliment.

SQLite provides reliability without adding unnecessary complexity.

Challenges

The hardest part wasn't networking.

It wasn't cryptography.

It was ecosystem compatibility.

Bun has come a long way, but many packages still assume a Node.js runtime. Building around those assumptions required more effort than expected.

Another challenge was designing a handshake that was educational while remaining understandable. I intentionally avoided implementing something as complex as Signal's Double Ratchet because the goal was learning, not protocol innovation.

What I Learned

Three things stood out:

libp2p removes an enormous amount of complexity from distributed systems.
Identity management is often more important than message transport.
Good architecture matters even in small projects.

Separating crypto, networking, storage, and services early prevented the project from turning into a tangled mess later.

What's Next?

Potential future improvements include:

  • Double Ratchet support
  • NAT traversal
  • Group messaging
  • Multi-device synchronization
  • Better peer discovery
  • Rich terminal UI

For now, though, Peetopee accomplished its original goal: helping me understand how decentralized messaging systems actually work.

The full source code is available on GitHub if you'd like to explore the implementation yourself.

Top comments (0)