<?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: Yurii donets</title>
    <description>The latest articles on DEV Community by Yurii donets (@atomm705).</description>
    <link>https://dev.to/atomm705</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%2F4161078%2F3e9b2171-bee6-40a8-a54e-a041476b74a4.jpg</url>
      <title>DEV Community: Yurii donets</title>
      <link>https://dev.to/atomm705</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/atomm705"/>
    <language>en</language>
    <item>
      <title>I Got Tired of Paying $99/Month for Pusher, So I Built a Pusher-Compatible Alternative</title>
      <dc:creator>Yurii donets</dc:creator>
      <pubDate>Sun, 04 Oct 2026 07:35:25 +0000</pubDate>
      <link>https://dev.to/atomm705/i-got-tired-of-paying-99month-for-pusher-so-i-built-a-pusher-compatible-alternative-48mg</link>
      <guid>https://dev.to/atomm705/i-got-tired-of-paying-99month-for-pusher-so-i-built-a-pusher-compatible-alternative-48mg</guid>
      <description>&lt;p&gt;I've been using WebSockets in production applications for years.&lt;/p&gt;

&lt;p&gt;For Laravel projects, the combination of Laravel Broadcasting, Echo, and Pusher-compatible infrastructure is incredibly convenient. You get realtime events without having to build the entire delivery layer yourself.&lt;/p&gt;

&lt;p&gt;But eventually I ran into a problem that probably sounds familiar to anyone running several small or medium-sized projects:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;the infrastructure bill started growing faster than the actual realtime workload justified.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;At some point I found myself looking at a Pusher plan around $99/month and asking a fairly simple question:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Do I really need to pay this much for the amount of realtime traffic I'm actually using?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That question eventually turned into a project.&lt;/p&gt;

&lt;p&gt;I built &lt;strong&gt;Donevia&lt;/strong&gt; — a hosted, Pusher-compatible WebSocket service.&lt;/p&gt;

&lt;p&gt;The goal isn't to invent another realtime protocol.&lt;/p&gt;

&lt;p&gt;It's almost the opposite.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Pusher compatibility?
&lt;/h2&gt;

&lt;p&gt;Pusher already has a mature ecosystem.&lt;/p&gt;

&lt;p&gt;Laravel supports it extremely well. Laravel Echo works with it. There are existing client libraries for multiple platforms, and developers already understand the concepts of channels, events, private channels, and presence channels.&lt;/p&gt;

&lt;p&gt;So replacing all of that with a completely new API didn't make much sense to me.&lt;/p&gt;

&lt;p&gt;I wanted the migration path to look more like this:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;keep your application → keep Laravel Echo → keep the Pusher protocol → change the infrastructure.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For a typical Laravel application, that means the application can continue broadcasting events normally while the WebSocket connection points to Donevia instead.&lt;/p&gt;

&lt;p&gt;Conceptually, your Echo configuration still looks familiar:&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="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Echo&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;Echo&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;broadcaster&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pusher&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;YOUR_APP_KEY&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

    &lt;span class="na"&gt;wsHost&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;your-websocket-host&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;wsPort&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;6001&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

    &lt;span class="na"&gt;forceTLS&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;span class="na"&gt;disableStats&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;span class="p"&gt;});&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important part is that the application shouldn't need to understand that the underlying provider changed.&lt;br&gt;
That became one of the fundamental design decisions behind Donevia.&lt;br&gt;
It quickly became more than running a WebSocket server&lt;br&gt;
Running a Pusher-compatible WebSocket engine is the easy part.&lt;br&gt;
Turning it into a SaaS is much more interesting.&lt;br&gt;
Once I started building the surrounding platform, I needed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;customer accounts&lt;/li&gt;
&lt;li&gt;multiple applications per account&lt;/li&gt;
&lt;li&gt;application credentials&lt;/li&gt;
&lt;li&gt;usage tracking&lt;/li&gt;
&lt;li&gt;connection limits&lt;/li&gt;
&lt;li&gt;message limits&lt;/li&gt;
&lt;li&gt;plans&lt;/li&gt;
&lt;li&gt;tenant isolation&lt;/li&gt;
&lt;li&gt;billing infrastructure&lt;/li&gt;
&lt;li&gt;an application console&lt;/li&gt;
&lt;li&gt;metrics&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And suddenly the project wasn't just "host a WebSocket server."&lt;/p&gt;

&lt;p&gt;It became an infrastructure platform.&lt;/p&gt;
&lt;h2&gt;
  
  
  Accounts, applications, and tenants
&lt;/h2&gt;

&lt;p&gt;One architectural decision I spent quite a bit of time on was deciding where limits should actually live.&lt;br&gt;
I didn't want every application to behave like a completely separate subscription.&lt;br&gt;
Instead, Donevia treats the customer account as the billing and quota boundary.&lt;br&gt;
An account can have multiple applications:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Account
 ├── Application A
 ├── Application B
 └── Application C
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Those applications share the resources available under the account's plan.&lt;br&gt;
Internally, this is handled through a tenant layer.&lt;br&gt;
The tenant isn't something customers need to manage as an "organization." It's primarily an infrastructure boundary used for quotas, billing, and isolation.&lt;br&gt;
That lets the platform answer questions like:&lt;br&gt;
How many concurrent connections are all applications belonging to this customer currently using?&lt;/p&gt;

&lt;p&gt;rather than only:&lt;br&gt;
How many connections does this particular application have?&lt;/p&gt;

&lt;p&gt;For a SaaS platform, I think that distinction matters.&lt;br&gt;
Usage accounting turned out to be surprisingly important&lt;br&gt;
Another problem was defining what a "message" actually means.&lt;br&gt;
WebSocket infrastructure produces several different kinds of activity.&lt;br&gt;
There are API-triggered events.&lt;br&gt;
There are messages delivered to connected clients.&lt;br&gt;
There are messages received over WebSockets.&lt;br&gt;
There are connections and disconnections.&lt;br&gt;
There are transmitted and received bytes.&lt;br&gt;
For Donevia, I eventually separated metrics such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;new connections&lt;/li&gt;
&lt;li&gt;disconnections&lt;/li&gt;
&lt;li&gt;API messages&lt;/li&gt;
&lt;li&gt;delivered messages&lt;/li&gt;
&lt;li&gt;WebSocket messages received&lt;/li&gt;
&lt;li&gt;WebSocket messages sent&lt;/li&gt;
&lt;li&gt;socket bytes received&lt;/li&gt;
&lt;li&gt;socket bytes transmitted&lt;/li&gt;
&lt;li&gt;HTTP calls&lt;/li&gt;
&lt;li&gt;HTTP traffic&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This gives me much better visibility into what individual applications are actually doing.&lt;/p&gt;

&lt;p&gt;More importantly, it gives me a foundation for pricing that is based on measurable resource usage instead of arbitrary limits.&lt;/p&gt;

&lt;h2&gt;
  
  
  Then came quotas
&lt;/h2&gt;

&lt;p&gt;Metrics tell you what happened.&lt;/p&gt;

&lt;p&gt;Quotas have to stop something from happening.&lt;br&gt;
That's a much more sensitive problem.&lt;br&gt;
If an account has a connection limit, all applications belonging to that account need to respect the same limit.&lt;br&gt;
So the WebSocket layer needs to know which tenant owns an application and how much capacity that tenant has available.&lt;br&gt;
For connection limits, I also chose a fail-closed approach.&lt;br&gt;
If the quota system cannot reliably determine whether a new connection is allowed, it should not silently allow unlimited connections.&lt;br&gt;
That's less convenient during infrastructure failures, but much safer for a multi-tenant service.&lt;/p&gt;

&lt;h2&gt;
  
  
  Load testing changed how I looked at the project
&lt;/h2&gt;

&lt;p&gt;One thing I didn't want to do was launch an infrastructure product after testing it with ten browser tabs.&lt;br&gt;
So I started load testing it.&lt;br&gt;
First tens of connections.&lt;br&gt;
Then hundreds.&lt;br&gt;
Then thousands.&lt;br&gt;
Eventually I pushed the test environment toward 200,000 concurrent WebSocket connections.&lt;br&gt;
That doesn't mean I'm claiming Donevia can magically handle 200,000 production users on any random server.&lt;br&gt;
Synthetic connection tests and real production traffic are very different things.&lt;br&gt;
But the exercise was extremely useful.&lt;br&gt;
It exposed bottlenecks, made connection accounting problems visible, and forced me to think seriously about the next stage:&lt;br&gt;
horizontal scaling.&lt;br&gt;
A single powerful server can take you surprisingly far.&lt;br&gt;
But infrastructure SaaS eventually needs to survive the loss of a node and distribute traffic across multiple nodes.&lt;br&gt;
That's the direction I'm working on now.&lt;/p&gt;

&lt;h2&gt;
  
  
  The hardest problem isn't WebSockets
&lt;/h2&gt;

&lt;p&gt;After building most of this, I've realized something.&lt;br&gt;
The hardest part probably isn't the protocol.&lt;br&gt;
It's trust.&lt;br&gt;
If you're building a todo app and it breaks for five minutes, that's annoying.&lt;br&gt;
If you're providing realtime infrastructure and it breaks, potentially every application depending on you loses realtime communication.&lt;br&gt;
Established providers have years of operational history behind them.&lt;br&gt;
A new infrastructure provider doesn't.&lt;br&gt;
So simply saying: &lt;strong&gt;"We're cheaper." isn't enough.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You need monitoring.&lt;br&gt;
You need predictable limits.&lt;br&gt;
You need redundancy.&lt;br&gt;
You need transparent status information.&lt;br&gt;
You need backups and recovery procedures.&lt;br&gt;
And eventually you need enough operational history that developers are comfortable trusting you with production traffic.&lt;br&gt;
That's the part of building Donevia that I find most interesting now.&lt;br&gt;
So what is Donevia today?&lt;br&gt;
Today, Donevia is a operational Pusher-compatible WebSocket platform with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;application management&lt;/li&gt;
&lt;li&gt;Pusher-compatible credentials&lt;/li&gt;
&lt;li&gt;Laravel / Echo compatibility&lt;/li&gt;
&lt;li&gt;tenant-based architecture&lt;/li&gt;
&lt;li&gt;connection quotas&lt;/li&gt;
&lt;li&gt;usage tracking&lt;/li&gt;
&lt;li&gt;realtime metrics&lt;/li&gt;
&lt;li&gt;customer console&lt;/li&gt;
&lt;li&gt;plan infrastructure&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There is still plenty to build.&lt;/p&gt;

&lt;p&gt;Horizontal scaling and high availability are particularly important next steps.&lt;/p&gt;

&lt;p&gt;But the core platform is now far enough along that I want to start putting it in front of developers outside my own projects.&lt;/p&gt;

&lt;p&gt;You can see the project here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://donevia.net/?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=launch" rel="noopener noreferrer"&gt;donevia.net&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I'm particularly interested in feedback from developers already using Pusher, Ably, Soketi, Laravel Reverb, or another realtime stack.&lt;/p&gt;

&lt;p&gt;If you were considering moving realtime infrastructure to a smaller provider, &lt;strong&gt;what would you need to see before trusting it with a production application?&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Price?&lt;/li&gt;
&lt;li&gt;Reliability numbers?&lt;/li&gt;
&lt;li&gt;Multiple regions?&lt;/li&gt;
&lt;li&gt;An SLA?&lt;/li&gt;
&lt;li&gt;Open-source components?&lt;/li&gt;
&lt;li&gt;A status page?&lt;/li&gt;
&lt;li&gt;Something else?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That question is probably more valuable to me right now than another feature request.&lt;/p&gt;

</description>
      <category>websockets</category>
      <category>laravel</category>
      <category>saas</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
