<?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>Forem: erndob</title>
    <description>The latest articles on Forem by erndob (@erndob).</description>
    <link>https://forem.com/erndob</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F309513%2F99db8198-9e94-4ebb-91a7-c9de205dde3b.png</url>
      <title>Forem: erndob</title>
      <link>https://forem.com/erndob</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/erndob"/>
    <language>en</language>
    <item>
      <title>P2P For Web Devs, Part 1.1: dht-rpc</title>
      <dc:creator>erndob</dc:creator>
      <pubDate>Fri, 20 Mar 2026 05:30:56 +0000</pubDate>
      <link>https://forem.com/erndob/p2p-for-web-devs-part-11-dht-rpc-38b2</link>
      <guid>https://forem.com/erndob/p2p-for-web-devs-part-11-dht-rpc-38b2</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Previous articles: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/erndob/p2p-for-web-devs-prologue-why-should-i-care-2gfm"&gt;P2P For Web Devs, Prologue: Why should I care?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/erndob/p2p-for-web-devs-part-1-networking-1961"&gt;P2P For Web Devs, Part 1: Networking&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://github.com/holepunchto/dht-rpc" rel="noopener noreferrer"&gt;dht-rpc&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;dht-rpc is a Kademlia based DHT implementation.&lt;/p&gt;

&lt;p&gt;Quick refresher:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Distributed Hash Table (DHT) stores data across different nodes in some kind of shared key space.&lt;/li&gt;
&lt;li&gt;Kademlia defines how we store data and find the exact node that holds the hash key you are looking for. It's one of the ways to implement a DHT.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let's build our own DHT and see what happens. I might mention some things that might make no sense yet, but as you read this article we will go back to those concepts and it will click.&lt;/p&gt;

&lt;p&gt;We have absolutely nothing now. Let's create the very first node.&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="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;DHT&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dht-rpc&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;node&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;DHT&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This single &lt;code&gt;new DHT()&lt;/code&gt; statement does A LOT.&lt;/p&gt;

&lt;p&gt;It's an instance of &lt;code&gt;EventEmitter&lt;/code&gt; that during constructor execution will:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create a &lt;a href="https://www.npmjs.com/package/kademlia-routing-table" rel="noopener noreferrer"&gt;kademlia routing table&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Open &lt;a href="https://www.npmjs.com/package/udx-native" rel="noopener noreferrer"&gt;UDP sockets&lt;/a&gt; and start listening.&lt;/li&gt;
&lt;li&gt;Starts watching network interfaces, health, and ticking to react to changes and maintain the routing table.&lt;/li&gt;
&lt;li&gt;Tracks all the nodes it's seen and keeps track of their liveliness.&lt;/li&gt;
&lt;li&gt;Probes the network to find closest nodes to itself.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now, at this stage this is a bit useless. It does a lot and at the same time nothing, because we are lacking some key parts.&lt;/p&gt;

&lt;p&gt;E.g. nodes identify themselves through a key that is a hash of your public ip and port number. &lt;code&gt;dht-rpc&lt;/code&gt; does that for you, it automatically asks another node to ping you, this way getting public IP and confirming that the node is accessible.&lt;/p&gt;

&lt;p&gt;But you cannot do that because there's no other nodes at the moment.&lt;/p&gt;

&lt;p&gt;Also, nodes start as ephemeral by default. Meaning even in the case of it being publicly accessible, other nodes won't add it to their routing table until the node is deemed stable enough. An ephemeral node functions more as a client, it still interacts with the DHT and can query other nodes for information, but it is not discoverable by other nodes.&lt;/p&gt;

&lt;p&gt;So our &lt;code&gt;new DHT()&lt;/code&gt; is essentially a client that did not connect to anything. It is technically running, but not really functioning.&lt;/p&gt;

&lt;p&gt;Let's fix it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bootstrap
&lt;/h2&gt;

&lt;p&gt;We need to have some kind of initial node that can be used by others to join the network. &lt;code&gt;dht-rpc&lt;/code&gt; offers a super simple solution for it. E.g. if I want to work in localhost on port 10001 I can do 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;bootstrap&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;DHT&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bootstrapper&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10001&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;127.0.0.1&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;It works exactly like any other DHT node, if you look at the code, it's a basic &lt;code&gt;new DHT()&lt;/code&gt; where we identify that this node:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;is not ephemeral&lt;/li&gt;
&lt;li&gt;is not behind a firewall as others need to be able to access it&lt;/li&gt;
&lt;li&gt;we provide the port number and ip address so that the node can identify itself correctly with a &lt;code&gt;hash(ip+port)&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;we say that port is static&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So this bootstrap node is now live, listening for requests on the port we specified and identifies itself with a consistent id.&lt;/p&gt;

&lt;p&gt;At this point in time its routing table is empty as there's no other nodes.&lt;/p&gt;

&lt;h2&gt;
  
  
  First node
&lt;/h2&gt;

&lt;p&gt;Let's create a &lt;code&gt;node1&lt;/code&gt; that uses this &lt;code&gt;bootstrap&lt;/code&gt;.&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;node1&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;DHT&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;bootstrap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;localhost:10001&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;node1&lt;/code&gt; initializes its state, the routing table. Currently everything is empty. At this moment your own ID is random, instead of &lt;code&gt;hash(ip+port)&lt;/code&gt;, as you don't know your own ip and you don't have assigned port.&lt;/li&gt;
&lt;li&gt;Initializes networking, opens sockets on OS-assigned ports (as we did not explicitly specify them), starts network watchers and ticks to periodically ping known nodes.&lt;/li&gt;
&lt;li&gt;Send a UDP message for &lt;code&gt;FIND_NODE&lt;/code&gt; to the &lt;code&gt;bootstrap&lt;/code&gt; node, asking to find your own id(the randomly generated one).&lt;/li&gt;
&lt;li&gt;Once you receive a response, put that response sender into your routing table.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The previously initiated ticks now will start periodically pinging &lt;code&gt;bootstrap&lt;/code&gt; because our routing table now has the &lt;code&gt;bootstrap&lt;/code&gt; node in it. We need to keep pinging to track their liveliness as nodes can go down any time.&lt;/p&gt;

&lt;p&gt;If you look at the &lt;code&gt;bootstrap&lt;/code&gt; node - its routing table is still empty. Even though it is communicating with &lt;code&gt;node1&lt;/code&gt;, &lt;code&gt;bootstrap&lt;/code&gt; node does not add it into the routing table which makes &lt;code&gt;node1&lt;/code&gt; not discoverable by other nodes. That's because &lt;code&gt;node1&lt;/code&gt; starts as ephemeral.&lt;/p&gt;

&lt;h2&gt;
  
  
  Upgrading from Ephemeral
&lt;/h2&gt;

&lt;p&gt;After around 20 minutes of running, &lt;code&gt;node1&lt;/code&gt; will try to upgrade itself to a non-ephemeral node. It will do it by:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Send "PING_NAT" messages to up to 5 known nodes. In our current state we know only about the single &lt;code&gt;bootstrap&lt;/code&gt; node. In our message we indicate our server socket port number.&lt;/li&gt;
&lt;li&gt;If we receive pings on the server socket, that means we are not behind a firewall. And if our IP is stable through all of the pings that have been happening, we upgrade from ephemeral into a proper node.&lt;/li&gt;
&lt;li&gt;We change our ID from the randomly generated one into the &lt;code&gt;hash(ip+port)&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Because our id changed, we re-bootstrap to get the nodes closest to our new id, by doing the &lt;code&gt;FIND_NODE&lt;/code&gt; request again. Resulting in the latest routing table.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After this, if you check in routing table of the &lt;code&gt;bootstrap&lt;/code&gt; - our &lt;code&gt;node1&lt;/code&gt; is there! Meaning now &lt;code&gt;bootstrap&lt;/code&gt; will also track &lt;code&gt;node1&lt;/code&gt; and keep pinging it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Third node
&lt;/h2&gt;

&lt;p&gt;Let's add a new node, let's not wait for 20 minutes, and just indicate that this node is not ephemeral.&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;node2&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;DHT&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;bootstrap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;localhost:10001&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;ephemeral&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you run this, you will see that &lt;code&gt;node2&lt;/code&gt; immediately learns about &lt;code&gt;bootstrap&lt;/code&gt; and &lt;code&gt;node1&lt;/code&gt;, and they also immediately become aware of &lt;code&gt;node2&lt;/code&gt;. Even though our &lt;code&gt;node2&lt;/code&gt; was never explicitly configured to track &lt;code&gt;node1&lt;/code&gt;, the data propagated through out entire network where all 3 nodes know about each other.&lt;/p&gt;

&lt;p&gt;Let's kill this &lt;code&gt;node2&lt;/code&gt;. After some time, you will see that &lt;code&gt;node2&lt;/code&gt; disappears from &lt;code&gt;bootstrap&lt;/code&gt; and &lt;code&gt;node1&lt;/code&gt;. So all those pings let's the nodes in the network to know when another node went down, to correctly remove it from tracked nodes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Many nodes
&lt;/h2&gt;

&lt;p&gt;Let's spin up a 100 nodes.&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;clients&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;length&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;DHT&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;bootstrap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;localhost:10001&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="na"&gt;ephemeral&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you look at the &lt;code&gt;bootstrap&lt;/code&gt; node, you will notice that its routing table does not contain all 100 nodes. And if you keep spinning up new nodes, the bootstrap tracked nodes will increase but it will increase slower and slower. I got up to 800 nodes on my machine with bootstrap tracking 123 nodes before my poor Macbook Air started overheating. In the last 100 nodes that were added, the bootstrap added only 2 new nodes into its routing table.&lt;/p&gt;

&lt;p&gt;Why?&lt;/p&gt;

&lt;p&gt;If every node had to keep track of every single node, ping each other, etc, this wouldn't be able to scale. Kademlia defines how we structure our routing table and how we can still find who we are looking for. A few times I mentioned "distance". That's not geographical distance, that's a mathematical distance between the hash keys, the &lt;code&gt;hash(ip+port)&lt;/code&gt; we mentioned. And the routing table groups seen nodes based on this distance, where at certain point you stop tracking newly added nodes in specific distance range. If a node gets a request for a hash, it's fine if it doesn't have that hash in its routing table, it just needs to return the closest nodes to the searched id that it does have. This way through a few hops you can get closer and closer to what you are looking for until you find it. This is also the reason why during bootstrapping you do the &lt;code&gt;FIND_NODE&lt;/code&gt; request on your own id - you are trying to fill your own routing table by finding nodes that are close to you.&lt;/p&gt;

&lt;p&gt;This is a very rough overview, if you want to go deeper there's a great article on it &lt;a href="https://medium.com/@ievstrygul/kademlia-the-p2p-system-behind-ethereum-and-bittorrent-networks-a3d8f539f114" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  RPC
&lt;/h2&gt;

&lt;p&gt;So we have our distributed hash table running, we have many nodes connecting to each other, identifying themselves with a hash and we are able to search for any hash.&lt;/p&gt;

&lt;p&gt;While that is cool, it's not very useful on its own. Good thing is, the library is called &lt;code&gt;dht-rpc&lt;/code&gt; and not just &lt;code&gt;dht&lt;/code&gt;. So it allows us to make the nodes do whatever we want through RPC.&lt;/p&gt;

&lt;p&gt;A "trick" to it, is that the nodes ids and the data or actions we execute, are all on the same key space so exactly same way of finding nodes close to yourself, can be used to find nodes closest to data. Two clients don't need to know anything about the current state of entire DHT, they can just query the DHT on same &lt;code&gt;hash(whatever)&lt;/code&gt; and those two clients will together interact with same node that is closest to this hash. As nodes enter and leave the entire DHT, what is the "closest" node changes over time.&lt;/p&gt;

&lt;p&gt;Let's modify the nodes script, to handle custom requests:&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;nodes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;length&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;node&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;DHT&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;bootstrap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;localhost:10001&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="na"&gt;ephemeral&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;

  &lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;request&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;myId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;hex&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;substring&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;myId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; did something`&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;node&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And now the client:&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;client&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;DHT&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;bootstrap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;localhost:10001&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fullyBootstrapped&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;alloc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;data&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;()))&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;finished&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We remove the &lt;code&gt;ephemeral: false&lt;/code&gt; as this is a client to query the DHT, not be part of it. The &lt;code&gt;target&lt;/code&gt; is the key we are looking for, so we care about the node closest to the &lt;code&gt;target&lt;/code&gt;. &lt;code&gt;command&lt;/code&gt; is just custom number that we can access from the nodes, that's how you implement branching logic for different actions.&lt;/p&gt;

&lt;p&gt;Now if we run this, we get:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;...
46fc3 did something
1d4c6 did something
12ecc did something
0f40c did something
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We want to find the node closest to the target, but instead we got data from every single node that got traversed. If you run this client a few times, you will get different outputs, because even though you look for the same target, the traversal changes as each run generates a unique starting routing table. And it depends on the network, which node will respond first. While that is not what we need exactly, it's still good to know that we are able to do this, you can inject code in the entire DHT traversal logic, and you can capture responses from that traversal on the client side.&lt;/p&gt;

&lt;h2&gt;
  
  
  Read from the closest node
&lt;/h2&gt;

&lt;p&gt;To actually know which response is from the closest node, we can add this line:&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="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;closest reply&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;closestReplies&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, if you run this many times, you will get different results from the query as the traversal is different, but the last line will always be the same. It's always the same node that is closest to your target. If you randomize your target, like &lt;code&gt;target: crypto.randomBytes(32)&lt;/code&gt;, you will start seeing different closest reply between runs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Writing to closest node
&lt;/h2&gt;

&lt;p&gt;Reading replies when there's nothing written is not very useful. Or if you need to execute any other action just from the closest node and not the entire traversal.&lt;/p&gt;

&lt;p&gt;Let's clean up the entire solution and implement the proper logic.&lt;/p&gt;

&lt;p&gt;New DHT nodes:&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;nodes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;length&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;node&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;DHT&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;bootstrap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;localhost:10001&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="na"&gt;ephemeral&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;state&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;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;request&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;target&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;hex&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;switch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;command&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="c1"&gt;// Read&lt;/span&gt;
        &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;
      &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="c1"&gt;// Write&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;previousValue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;previousValue&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// Our own defined error code&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;node&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We store the state in memory, as a simple &lt;code&gt;Map&lt;/code&gt; where the key is the &lt;code&gt;target&lt;/code&gt;. So you can imagine a single node might be closest to multiple &lt;code&gt;target&lt;/code&gt;s and store all of them.&lt;br&gt;
We start using that &lt;code&gt;command&lt;/code&gt; property, so clients can indicate what they want to do and requests can handle it. Write command also responds with the previous value.&lt;br&gt;
And if it is an unknown command, we return our custom error code instead.&lt;/p&gt;

&lt;p&gt;Now, let's implement our client:&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;client&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;DHT&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;bootstrap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;localhost:10001&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fullyBootstrapped&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;target&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;crypto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createHash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sha256&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;data key&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;digest&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;q&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;q&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;finished&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;write me!&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="nx"&gt;q&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;closestNodes&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;hex&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; responded with &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;First, we create &lt;code&gt;target&lt;/code&gt; key as a &lt;code&gt;sha256&lt;/code&gt; hash. Before I did just a manual buffer of 32 bytes, which does work, but we want to have uniformly distributed keys. You want to make sure you are not accidentally hitting collisions and that the keys are distributed well between nodes and not accumulating among just a few of them.&lt;/p&gt;

&lt;p&gt;Then we do exactly same &lt;code&gt;client.query&lt;/code&gt; like before. This time I do not care about what it is responding, I just want to get the closest node to the &lt;code&gt;target&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;After the query, now I can do &lt;code&gt;client.request&lt;/code&gt;. The request body is almost same as it was with query, except our &lt;code&gt;command&lt;/code&gt; changed to &lt;code&gt;1&lt;/code&gt; and we added a &lt;code&gt;value&lt;/code&gt;. But the most important thing is that &lt;code&gt;request&lt;/code&gt; allows us to pass a specific &lt;code&gt;node&lt;/code&gt;, so we can use the &lt;code&gt;q.closestNodes[0]&lt;/code&gt; from the read query.&lt;/p&gt;

&lt;p&gt;Unlike a &lt;code&gt;query&lt;/code&gt; that traverses the DHT, &lt;code&gt;request&lt;/code&gt; will make just a single call to the specified node. It hits the same callback we configured in our nodes logic.&lt;/p&gt;

&lt;p&gt;If you run the client a few times, you will get the same &lt;code&gt;id&lt;/code&gt; each time, as it is same node that is closest to target. And you will get &lt;code&gt;undefined&lt;/code&gt; as value first time, and &lt;code&gt;write me!&lt;/code&gt; the second time.&lt;/p&gt;

&lt;p&gt;You can make exactly same write request but with a query, that is completely valid, but you will end up with many nodes doing that same write as the DHT is traversed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reliability
&lt;/h2&gt;

&lt;p&gt;We have strong building blocks already, but there's one issue. What happens if the node that holds data disconnects? Or, even worse, nothing bad happens, instead a new node joins and it happens to be closer to the &lt;code&gt;target&lt;/code&gt;. In that case subsequent &lt;code&gt;closestReplies[0]&lt;/code&gt; requests start showing &lt;code&gt;undefined&lt;/code&gt; even when you had data. Your expectations break even if none of the nodes broke.&lt;/p&gt;

&lt;p&gt;Good thing, DHT allows us to &lt;code&gt;commit&lt;/code&gt; when we do a query.&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;q&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;write me!&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;commit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The query looks almost identical to before, except we added &lt;code&gt;{ commit: true }&lt;/code&gt;, and the &lt;code&gt;value&lt;/code&gt; that before was on &lt;code&gt;client.request&lt;/code&gt; now is in the &lt;code&gt;client.query&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;When you indicate that a query should be committed, once the initial pass through travels the DHT triggering all your traversal requests and populating the &lt;code&gt;closestNodes&lt;/code&gt;/&lt;code&gt;closestReplies&lt;/code&gt;, it automatically executes same request again but only to the 20 &lt;code&gt;closestReplies&lt;/code&gt; and this time it passes the token.&lt;/p&gt;

&lt;p&gt;The token is a simple hash of client IP and a secret that got returned by the node during response of DHT traversal. If you make a new request to same node including the token you got from previous request, it gets validated that the hash is correct for your IP address. This gives you a super basic and lightweight auth if you want to track and control who can write or access the data(keep in mind IPs are not static and clients can share them, so it's not appropriate for anything serious). Or you can use it as a simple proof of commit, meaning your node can know that it was recognized as one of the "nearest" to the target.&lt;/p&gt;

&lt;p&gt;Here's how our new node request handler looks:&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="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;request&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;switch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;command&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="c1"&gt;// Read or Write&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;target&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;hex&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;previousValue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// this is a commit, we write&lt;/span&gt;
        &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Compared to before, we combined our separate commands for read and write into a single command. Instead, we know it is a write if a token exists. Token does not need any validation because DHT itself already validated it.&lt;/p&gt;

&lt;p&gt;With this, if you run our query, 20 nodes closest to the &lt;code&gt;target&lt;/code&gt; will store the value into their state. Now, even if the closest node dies, you still have your data in other nearby nodes.&lt;/p&gt;

&lt;p&gt;You now also do not need a separate &lt;code&gt;client.request&lt;/code&gt; for the write, everything is done in this one &lt;code&gt;query&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Now, we can also adjust how we read data, instead of looking at &lt;code&gt;q.closestReplies[0]&lt;/code&gt; like we did before, we need to find any node that returns data instead.&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="k"&gt;for&lt;/span&gt; &lt;span class="k"&gt;await &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;}))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;hex&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt; returned &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;break&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that in this &lt;code&gt;query&lt;/code&gt;, we do not pass &lt;code&gt;commit: true&lt;/code&gt; and the &lt;code&gt;value&lt;/code&gt;. We simply read the responses as they come in, once any of the nodes returns it - we break early and are done. This way even if a new closest node does not have the value, it does not matter.&lt;/p&gt;

&lt;p&gt;If you run the current code a few times, you will notice that between runs you get different node responding with the value but still in the same subset of nodes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;We did a lot! We got a very powerful building block that allows us to connect arbitrary amount of machines without a special coordinator. And we got a powerful RPC logic where we can make these nodes do whatever we want.&lt;/p&gt;

&lt;p&gt;While the examples shown here do work, it is not production code. It has problems like not necessarily getting the latest data, not dealing with malicious nodes, not having encryption, etc. The goal was to show core patterns that give you ideas on what you can do, not how to do production ready DHT.&lt;/p&gt;

&lt;p&gt;We will see how some of these problems are solved in the other modules that build on top of &lt;code&gt;dht-rpc&lt;/code&gt;.&lt;/p&gt;

</description>
      <category>node</category>
      <category>p2p</category>
      <category>javascript</category>
      <category>networking</category>
    </item>
    <item>
      <title>P2P For Web Devs, Part 1: Networking</title>
      <dc:creator>erndob</dc:creator>
      <pubDate>Thu, 12 Mar 2026 03:23:14 +0000</pubDate>
      <link>https://forem.com/erndob/p2p-for-web-devs-part-1-networking-1961</link>
      <guid>https://forem.com/erndob/p2p-for-web-devs-part-1-networking-1961</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Previous article in this series: &lt;a href="https://dev.to/erndob/p2p-for-web-devs-prologue-why-should-i-care-2gfm"&gt;P2P For Web Devs, Prologue: Why should I care?&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://github.com/holepunchto/hyperswarm" rel="noopener noreferrer"&gt;Hyperswarm&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;When you start with Pear runtime, one of the first things you encounter is Hyperswarm. It describes itself as:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A high-level API for finding and connecting to peers who are interested in a "topic."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Let's dissect this seemingly simple description.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Peers&lt;/strong&gt; are processes that communicate through a network, with a key distinction being that they do both - make requests and receive requests. You will often see a term "node" too, these are used &lt;em&gt;almost&lt;/em&gt; interchangeably in P2P context, as all peers are nodes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Swarm&lt;/strong&gt; in Hyperswarm is a group of peers that are all connected to each other. E.g. a group call with 4 people in it, is a swarm of 4 peers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Finding&lt;/strong&gt; means we are solving the equivalent of DNS lookup, we need to get IP addresses of the peers to connect to.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Connecting&lt;/strong&gt; is the step after "finding". When you get an IP address of a server, usually you connect with HTTP request on a predefined port number. Here it is more complex, as who we are connecting to is not conveniently listening on port 443.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Topic&lt;/strong&gt; is a way for peers to find each other. E.g. in a phone call of 4 people, a topic would be a call id. Someone else could join this call without knowing the other 4 people, they just need the call id.&lt;/p&gt;

&lt;p&gt;A key point to understand is that peers are not equal to an IP address. Your phone is a peer. But a phone is a mobile device that is constantly changing IPs as you move. So a topic would match you to a peer, but translating peer to an IP address is a separate issue.&lt;/p&gt;

&lt;p&gt;Now that's a lot of information packed into that short sentence describing Hyperswarm.&lt;/p&gt;

&lt;p&gt;Let's reduce scope, and go deeper.&lt;/p&gt;

&lt;p&gt;Hyperswarm is built on top of Hyperdht.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://github.com/holepunchto/hyperdht" rel="noopener noreferrer"&gt;HyperDHT&lt;/a&gt;
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;The Hyperswarm DHT uses a series of holepunching techniques to make sure connectivity works on most networks, and is mainly used to facilitate finding and connecting to peers using end to end encrypted Noise streams.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;We got a layer deeper, we got new terms, and the description is twice as long now.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;DHT&lt;/strong&gt; - Distributed Hash Table. That's a hash table, that instead of living in memory of a single machine, is actually across many different machines. The different machines can hold a range of data from the table, and all of them together form the full table. E.g. Cassandra DB is an implementation of DHT.&lt;br&gt;
In our context, DHT is what we can use to implement the "finding" part, that's where we can have information about existing peers, their IP addresses, topics. It's the metadata layer that is accessed by the entire network, without a central server. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;holepunching&lt;/strong&gt; - that's the connection part between peers. It solves the previously mentioned issue of "not conveniently listening on port 443". &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;end to end encrypted Noise streams&lt;/strong&gt; - that's the equivalent of SSL on the web. The &lt;a href="https://noiseprotocol.org/" rel="noopener noreferrer"&gt;noise protocol&lt;/a&gt; is a lot more lightweight compared to SSL and does not require a certificate authority. Which makes it the ideal choice for embedded systems and P2P systems that do not want to depend on some central certification issuer.&lt;/p&gt;

&lt;p&gt;So in this module we got some terms not only about what is being done, but also on how it is being done.&lt;/p&gt;

&lt;p&gt;Another line in the description of hyperdht is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Built on top of dht-rpc.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://github.com/holepunchto/dht-rpc" rel="noopener noreferrer"&gt;dht-rpc&lt;/a&gt;
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;Make RPC calls over a Kademlia based DHT.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Remote IP / firewall detection&lt;/li&gt;
&lt;li&gt;Easily add any command to your DHT&lt;/li&gt;
&lt;li&gt;Streaming queries and updates&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;I think we are deep enough! This is a core module that focuses on the DHT itself. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://en.wikipedia.org/wiki/Kademlia" rel="noopener noreferrer"&gt;Kademlia&lt;/a&gt; based DHT&lt;/strong&gt;, DHT's can be implemented in different ways. E.g. the more controlled and trusted nodes will usually implement DHT with a hash ring. In the case of P2P network where peers are connecting and disconnecting all the time, are untrusted and the scale can easily reach millions of peers, Kademlia is the standard. These different approaches define how the node with the data you need is actually found. &lt;/p&gt;

&lt;p&gt;I mentioned that we use DHT here for the metadata, for resolving the peer to IP maps. But that's just the use case in the hyperswarm module. This dht-rpc module is just a generic implementation, giving you the ability to command and query your DHT for whatever custom data you need.&lt;/p&gt;

&lt;h2&gt;
  
  
  Next
&lt;/h2&gt;

&lt;p&gt;Now that we have the vocabulary and understand the problem scope, we can start looking at how these problems are actually solved.&lt;/p&gt;

&lt;p&gt;In the next articles I will go through implementation details of each of those modules, building up from dht-rpc to Hyperswarm.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Next article: &lt;a href="https://dev.to/erndob/p2p-for-web-devs-part-11-dht-rpc-38b2"&gt;P2P For Web Devs, Part 1.1: dht-rpc&lt;/a&gt; &lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>p2p</category>
      <category>node</category>
      <category>distributedsystems</category>
      <category>javascript</category>
    </item>
    <item>
      <title>P2P For Web Devs, Prologue: Why should I care?</title>
      <dc:creator>erndob</dc:creator>
      <pubDate>Wed, 11 Mar 2026 07:44:34 +0000</pubDate>
      <link>https://forem.com/erndob/p2p-for-web-devs-prologue-why-should-i-care-2gfm</link>
      <guid>https://forem.com/erndob/p2p-for-web-devs-prologue-why-should-i-care-2gfm</guid>
      <description>&lt;h2&gt;
  
  
  What is it?
&lt;/h2&gt;

&lt;p&gt;Peer-to-peer (P2P) is when devices communicate with each other directly, instead of through a server.&lt;/p&gt;

&lt;h2&gt;
  
  
  Have I used it before?
&lt;/h2&gt;

&lt;p&gt;P2P is heavily used in a lot of highly distributed systems when you want to avoid having a single point of failure with a centralized orchestrator. You likely used some kind of cloud service that at one of the layers is built with P2P.&lt;/p&gt;

&lt;p&gt;You have likely used many P2P based services as a consumer, maybe without realizing it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://learn.microsoft.com/en-us/windows/deployment/do/waas-delivery-optimization" rel="noopener noreferrer"&gt;Windows Updates&lt;/a&gt; are distributed through "Delivery Optimization", where they combine regular HTTP servers, cache layers, and P2P distribution. That P2P distribution is absolutely crucial, as Windows machines are downloading the update, they start sharing the parts they downloaded with other machines. In case of machines on the same network, the update can spread through the network without even touching public internet. You can imagine absolutely massive savings in bandwidth that Microsoft gets from this. This also results in increased reliability and faster download speeds for consumers.&lt;/li&gt;
&lt;li&gt;A lot of real time communication apps use P2P for video/voice, often with a mixed architecture.&lt;/li&gt;
&lt;li&gt;Things like remote desktop applications will often use P2P to connect you directly.&lt;/li&gt;
&lt;li&gt;If you ever used a torrent client to download files - that's P2P.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  So what? That does not apply to the majority of applications.
&lt;/h2&gt;

&lt;p&gt;You likely noticed a pattern there - it is either static files like updates, or real time communication where you do not care about state.&lt;/p&gt;

&lt;p&gt;But what if we took a step back, and tried to build consumer applications with regular data, that is P2P based?&lt;/p&gt;

&lt;p&gt;Of course we would have a lot of things to solve. But let's ignore that for now. Let's think about what a P2P application would give us:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Cost&lt;/strong&gt;. With state and processing on device, and communication P2P, suddenly the entire cost model is different.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Latency&lt;/strong&gt;. Less intermediary hops, less latency.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reliability&lt;/strong&gt;. No server, means no downtime when a cloud provider goes down. Additionally P2P applications are resilient to internet outages for home/office networks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Security&lt;/strong&gt;. Data on device instead of a massive server with everyones data in one place removes a huge area of potential data leaks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Privacy&lt;/strong&gt;. This is especially vital now, as AI enables surveillance at an unprecedented scale.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Environment&lt;/strong&gt;. Data centers have a global impact and localized impact to communities that live close to them. P2P uses devices consumers already own.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  If it is so great, why is it not more widely used?
&lt;/h2&gt;

&lt;p&gt;It is hard. There are a lot of problems to solve that have been solved on regular stacks already. Certain aspects require compromises.&lt;/p&gt;

&lt;p&gt;There's a lack of incentives.&lt;br&gt;
Modern web development is highly run by FAANG. They are responsible for majority of open source tools that became industry default(React), they control the runtime(Chrome), they control languages(C#), they control the servers(AWS), they control how we store our code(GitHub) and now they also control the AI agents that increasingly do the coding. They have zero incentive to develop tooling for applications that remove the need to pay them and give control to the consumer. They will use these technologies internally for reliability, or in their products to save on bandwidth, but they have zero incentives to develop a full platform for P2P applications. WebRTC by Google is the closest thing we have, but its focus is on real time data streaming. It's developed mostly to strengthen Web as a platform, enabling audio/video/screen sharing in-browser, so Google can compete vs native apps. Not to enable general P2P applications.&lt;/p&gt;

&lt;h2&gt;
  
  
  What about crypto currency and blockchains?
&lt;/h2&gt;

&lt;p&gt;I must mention crypto as that is the most successful P2P experiment we have. It offers a global, trusted database that anyone can interact with and build upon. &lt;/p&gt;

&lt;p&gt;While it is successful, these apps navigate complexity that regular apps do not have. Like, consensus mechanisms so everyone agrees on what the trusted state is, complex monetary incentives, paid transactions, paid storage, etc. &lt;/p&gt;

&lt;p&gt;If you take a step back from what is possible with blockchains now, say "we don't need half of this", and build something that is for consumers and not finance, you suddenly get a powerful technical stack that can be adapted for regular applications.&lt;/p&gt;

&lt;h2&gt;
  
  
  It is useful and can be adapted, but no one wants to solve those hard problems on their own.
&lt;/h2&gt;

&lt;p&gt;That's where &lt;a href="https://pears.com/" rel="noopener noreferrer"&gt;Pear&lt;/a&gt; comes in. It's a P2P stack that you can use to build consumer applications. If you look at the docs, the index page links to 106 javascript modules taking care of different things. &lt;/p&gt;

&lt;p&gt;It feels magical, a few cli commands, a few lines of javascript, and you have working demos of P2P applications.&lt;/p&gt;

&lt;p&gt;Work feeling like magic because it is easy is great, work feeling like magic because you don't understand what is happening is a time bomb.&lt;/p&gt;

&lt;p&gt;The goal of this series is for myself to learn the Pear stack and demystify it from the web developer perspective.&lt;/p&gt;

&lt;p&gt;Instead of approaching it from the end - the &lt;code&gt;pear init&lt;/code&gt;, we will focus on the core building blocks of pear runtime. Develop understanding and intuition for how it actually runs, so we know what we can build and how to troubleshoot.&lt;/p&gt;

&lt;p&gt;In this series I will focus on the &lt;a href="https://docs.pears.com/index.html#p2p-modules" rel="noopener noreferrer"&gt;P2P Modules&lt;/a&gt;, these are core modules that cover networking, persistence, synchronization and storage.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Next article: &lt;a href="https://dev.to/erndob/p2p-for-web-devs-part-1-networking-1961"&gt;P2P For Web Devs, Part 1: Networking&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>p2p</category>
      <category>node</category>
      <category>networking</category>
      <category>javascript</category>
    </item>
  </channel>
</rss>
