<?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: LeangDev</title>
    <description>The latest articles on DEV Community by LeangDev (@houy_sengleang).</description>
    <link>https://dev.to/houy_sengleang</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%2F1942159%2Fe95dddbe-aa92-4b94-a90c-191f0a1ed67c.jpeg</url>
      <title>DEV Community: LeangDev</title>
      <link>https://dev.to/houy_sengleang</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/houy_sengleang"/>
    <language>en</language>
    <item>
      <title>How to debug WebSockets without changing your code</title>
      <dc:creator>LeangDev</dc:creator>
      <pubDate>Wed, 04 Nov 2026 16:10:46 +0000</pubDate>
      <link>https://dev.to/houy_sengleang/how-to-debug-websockets-without-changing-your-code-5114</link>
      <guid>https://dev.to/houy_sengleang/how-to-debug-websockets-without-changing-your-code-5114</guid>
      <description>&lt;p&gt;If you've worked with WebSockets, you know the pain: something's wrong, and you can't &lt;em&gt;see&lt;/em&gt; what's going over the wire.&lt;/p&gt;

&lt;p&gt;For browser apps, Chrome DevTools has you covered (Network tab → WS → Messages). But the moment your client is a &lt;strong&gt;Node service, a mobile app, a bot, or an IoT device&lt;/strong&gt;, DevTools can't help. So most of us 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="nx"&gt;ws&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;message&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;data&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="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;IN&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;data&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="c1"&gt;// 🙃&lt;/span&gt;
  &lt;span class="nf"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&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;It works until you have five connections, binary frames, and a terminal full of JSON.&lt;/p&gt;

&lt;h2&gt;
  
  
  The proxy approach
&lt;/h2&gt;

&lt;p&gt;A cleaner trick: put a &lt;strong&gt;proxy&lt;/strong&gt; between your client and server. The client connects to the proxy, the proxy connects to the real server, and every frame passes through something that can record and show it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;your app  ──►  proxy (records everything)  ──►  your WebSocket server
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your code doesn't change. Only the URL does.&lt;/p&gt;

&lt;p&gt;You &lt;em&gt;can&lt;/em&gt; do this with general HTTP tools like mitmproxy, but for &lt;code&gt;wss://&lt;/code&gt; you have to set up a CA certificate and proxy config, and the UI isn't built for message streams. I wanted something I could start in one command, so I built &lt;strong&gt;&lt;a href="https://github.com/hoysengleang/wslog" rel="noopener noreferrer"&gt;wslog&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it in 10 seconds
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx wslog-proxy &lt;span class="nt"&gt;--demo&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open &lt;a href="http://localhost:4000" rel="noopener noreferrer"&gt;http://localhost:4000&lt;/a&gt;. The &lt;code&gt;--demo&lt;/code&gt; flag starts a fake chat server with simulated users, so you'll see traffic right away.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0unlaohd3co0ltrrqfdx.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0unlaohd3co0ltrrqfdx.gif" alt=" " width="560" height="315"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Using it with your own app
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx wslog-proxy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Open &lt;a href="http://localhost:4000" rel="noopener noreferrer"&gt;http://localhost:4000&lt;/a&gt; and paste the URL your app connects to, e.g. &lt;code&gt;wss://api.example.com/socket&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;wslog gives you a proxy URL like &lt;code&gt;ws://127.0.0.1:4000/p/3f9a1c2b&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Use that URL in your app:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// before&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ws&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;WebSocket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;wss://api.example.com/socket&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// after (only while debugging)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ws&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;WebSocket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ws://127.0.0.1:4000/p/3f9a1c2b&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;Path and query are passed through, so &lt;code&gt;.../p/3f9a1c2b/chat?room=1&lt;/code&gt; reaches &lt;code&gt;wss://api.example.com/socket/chat?room=1&lt;/code&gt;. Cookies, &lt;code&gt;Authorization&lt;/code&gt; headers and the negotiated subprotocol are forwarded too, so auth keeps working.&lt;/p&gt;

&lt;p&gt;Tip: load the URL from an env var (&lt;code&gt;WS_URL&lt;/code&gt;) and you can switch between direct and proxied without touching code.&lt;/p&gt;

&lt;p&gt;The proxy URL is derived from the target and remembered across restarts, so you only set it up once.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you get
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Live table&lt;/strong&gt; of every frame: time, direction, size, connection, preview. It stays fast with tens of thousands of rows&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Detail pane&lt;/strong&gt; with a collapsible JSON tree, raw view, and hex/base64 for binary frames&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Connections sidebar&lt;/strong&gt; with status, close codes, and a message-rate sparkline&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Search and filters&lt;/strong&gt; by direction, connection, or target, plus export to JSON&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multiple servers at once&lt;/strong&gt;, each with its own proxy URL&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Don't leak your tokens
&lt;/h2&gt;

&lt;p&gt;WebSocket traffic is full of secrets. wslog can mask fields &lt;strong&gt;before anything is stored&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx wslog-proxy &lt;span class="nt"&gt;--mask&lt;/span&gt; token,password
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Masked values never reach disk, the dashboard, or exports, even inside nested JSON or JSON encoded in a string. Handshake headers like cookies and &lt;code&gt;Authorization&lt;/code&gt; are forwarded but never stored. And it only listens on &lt;code&gt;127.0.0.1&lt;/code&gt; by default.&lt;/p&gt;

&lt;h2&gt;
  
  
  Share a live view
&lt;/h2&gt;

&lt;p&gt;Debugging with a backend teammate? wslog has a &lt;strong&gt;read-only share link&lt;/strong&gt; (token protected) so they can watch the same traffic live instead of you pasting screenshots into Slack.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep the traffic
&lt;/h2&gt;

&lt;p&gt;By default messages stay in memory. To keep a session:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx wslog-proxy &lt;span class="nt"&gt;--store&lt;/span&gt; ./traffic.db     &lt;span class="c"&gt;# SQLite&lt;/span&gt;
npx wslog-proxy &lt;span class="nt"&gt;--store&lt;/span&gt; ./traffic.jsonl  &lt;span class="c"&gt;# JSON lines&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  How it compares
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;wslog&lt;/th&gt;
&lt;th&gt;Browser DevTools&lt;/th&gt;
&lt;th&gt;wscat / websocat&lt;/th&gt;
&lt;th&gt;mitmproxy&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Non-browser clients&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅ (you are the client)&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;UI built for WS traffic&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;partial&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Search/filter across connections&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;limited&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Live share with a teammate&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Setup&lt;/td&gt;
&lt;td&gt;one command&lt;/td&gt;
&lt;td&gt;none&lt;/td&gt;
&lt;td&gt;none&lt;/td&gt;
&lt;td&gt;proxy + CA cert&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;wslog is MIT licensed, needs Node 22+, and also runs in Docker. It's v0.1 and I'm building it on my own, so feedback means a lot. Issues, ideas, and ⭐ are all welcome:&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;&lt;a href="https://github.com/hoysengleang/wslog" rel="noopener noreferrer"&gt;https://github.com/hoysengleang/wslog&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;How do you debug WebSockets today? Let me know in the comments.&lt;/p&gt;

</description>
      <category>websock</category>
      <category>node</category>
      <category>debugging</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
