DEV Community

Cover image for Hosting Real-Time Node.js Applications with WebSockets and Serverless Architectures.
Toran
Toran

Posted on

Hosting Real-Time Node.js Applications with WebSockets and Serverless Architectures.

Introduction

Over the last decade, the way we build and host applications has changed dramatically. At the center of this change are two trends. First, more and more applications need to be real-time. Users expect updates instantly, whether it is a stock price, a game state, or a message from a coworker. Second, serverless computing has become a common hosting model, removing the need to manage infrastructure and allowing developers to deploy on-demand code that scales automatically.

These two trends are powerful, but combining them comes with challenges. Real-time applications typically rely on persistent, stateful connections between clients and servers, while serverless architectures are designed for stateless, short-lived, elastic functions. Node.js, with its event-driven model and thriving ecosystem, sits right in the middle of this conversation. It has become the most common choice for real-time servers, but hosting those same servers in a serverless environment introduces new considerations.

This essay explores how to host real-time Node.js applications using WebSockets in the context of serverless computing. We will cover how the technology works, what challenges you face, how developers are solving them today, and what best practices have emerged.

Real-Time Applications: Why They Matter

A "real-time" application is one where events are delivered from one user to another, or from a system to a user, with minimal delay. In practical terms, that means milliseconds instead of seconds.

Popular examples include:

Chat and messaging apps: Slack, WhatsApp Web, Discord.

Collaborative tools: Google Docs, Figma, Miro.

Notification systems: live updates in dashboards, alerts for monitoring systems.

Online games: multiplayer games where latency shapes gameplay.

Trading platforms: financial applications where a delayed update means real monetary loss.

In all these examples, what makes the app feel "alive" is the constant, low-latency connection between client and server. HTTP requests and responses are too slow and inefficient for these interactions, so developers turn to WebSockets and related technologies.

Why Node.js Works Well for Real-Time Workloads

Node.js has a unique execution model compared to many server-side platforms. Instead of running one thread per request, it uses an event loop and non-blocking I/O. This means it can hold open thousands of concurrent connections without bogging down.

A few reasons Node.js shines here:

  1. Event-driven programming fits real-time: Instead of polling, you subscribe to events and react when they happen.

  2. Lightweight connections: Since connections are mostly idle except for bursts of messages, Node.js can hold them efficiently.

  3. Mature libraries: Frameworks like Socket.IO, ws, and uWebSockets.js make implementing WebSocket servers straightforward.

  4. One language everywhere: Many teams like the fact that the same language is used on both client (JavaScript in the browser) and server (Node.js).

Because of these advantages, Node.js has become the de facto standard for building WebSocket servers.

Understanding WebSockets

The WebSocket protocol was standardized in 2011. Unlike HTTP, which uses a request/response model, WebSockets provide a full-duplex communication channel over a single TCP connection.

Here is how it works in practice:

  • A client initiates a handshake with an HTTP request that asks to "upgrade" the connection.

  • If the server supports WebSockets, it responds with a special status code, and from that point on, the connection switches from HTTP to the WebSocket protocol.

  • Once open, messages can flow freely in both directions.

This model is efficient because there is no need for repeated HTTP requests. You avoid the overhead of headers and repeated TCP/TLS handshakes. It also enables true push: servers can proactively send information to the client.

Traditional Hosting for Real-Time Apps

Historically, if you wanted to build a real-time application with Node.js, you would host it on a Virtual Machine, a bare-metal server, or in a container. That server process would live as long as needed, holding on to active WebSocket connections.

This model made sense because:

  • Connections are long-lived, so an always-on process maps naturally.

  • You can manage state in memory (e.g., tracking which users are in which rooms).

  • Scaling is a straightforward concept you add more instances and use sticky load balancing to ensure connections remain pinned to a specific server.

The downside is that you are now responsible for infrastructure: provisioning, updating, scaling, monitoring, and cost control.

Serverless Hosting

Serverless computing is becoming more popular because it promises to deploy code without managing the server. A serverless function is essentially code that runs in response to a specific event. You don’t manage or even know the server it runs on. The function spins up when triggered, executes quickly, and then shuts down. Pricing is based on actual usage, usually calculated by execution time in milliseconds and the memory used.

This works brilliantly for stateless workloads: APIs, data transformations, image processing, and background jobs. But what about real-time? Here lies the friction:

  • WebSockets are stateful: They require persistent connections, while serverless functions are expected to be short-lived.

  • Idle connections cost money: Keeping thousands of WebSockets open ties up resources.

  • Scaling complexity: Serverless systems are designed for horizontal scaling, but WebSockets require "session stickiness" so that messages for a user always go to the right function instance.

This is where hosting real-time systems on serverless gets complicated.

Challenges of WebSockets in Serverless Contexts

Let’s explore the specific problems that emerge when you combine WebSockets with serverless:

1. Connection Persistence

By design, serverless runtimes like AWS Lambda or Azure Functions want to spin down when idle. WebSockets, however, must remain connected for as long as a client is active. This tension makes it hard to implement a real-time pure serverless environment without additional services.

2. State and Memory

Imagine you are building a chat app. A user joins room A, then sends a message. The server needs to know all users in room A so it can broadcast accordingly. Traditionally, you could keep this in the memory of the running node process. In serverless, memory is not guaranteed to persist between invocations. State must be externalized: e.g., in Redis, DynamoDB, or another real-time data store.

3. Scaling and Routing

When you scale to multiple function instances, how do you make sure messages get routed to the right clients? This requires either sticky sessions at the load balancer level or a pub/sub mechanism to fan out messages to all instances.

4. Cold Starts

If a function container has spun down, the next invocation might take hundreds of milliseconds to start. In real-time contexts, that latency can be noticeable.

Serverless-Friendly Approaches

Despite these challenges, several serverless platforms and patterns make WebSockets possible:

1. API Gateway with WebSockets (AWS, Azure, GCP):

Cloud providers now offer managed WebSocket APIs where the gateway maintains connections, and your functions respond to events. In AWS, for example, API Gateway WebSocket API manages persistent connections while Lambda handles messages.

2. External Pub/Sub Systems:

Instead of functions managing state, you use Redis Pub/Sub, Kafka, or cloud-native equivalents (Google Pub/Sub, AWS SNS/SQS). Each message goes into the system, then is pushed to whichever function or connection is subscribed.

3. Managed Real-Time Services:

Some providers (Ably, Pusher, PubNub, Supabase Realtime) abstract this entirely and offer APIs for publish/subscribe messaging over WebSockets. In this case, your serverless code just talks to their service.

4. Hybrid Patterns:

A middle ground is to offload WebSocket handling to a container-based service (like AWS App Runner, Fargate, or Kubernetes) while running your business logic in a serverless function.

Use Cases That Fit Well

Not every real-time app is equal. Some are a better fit for serverless WebSockets than others.

Notifications and Alerts: Event-driven pushes to users (simple one-way updates).

Lightweight Collaboration: Live cursors, comment updates, and presence indicators.

IoT Device Messaging: Many devices send periodic updates that can be handled via brokered connections.

Data Streaming Dashboards: Sensor data, analytics, real-time metrics.

More complex systems like multiplayer games or low-latency trading often still require traditional hosting, since the latency and connection management are too strict for serverless.

Conclusion

Real-time applications are here to stay, and users expect that interactivity in the tools they use every day. Node.js has proven itself as a natural environment for building WebSocket servers. But hosting these servers in the world of serverless comes with challenges because the underlying assumptions are different.

A persistent WebSocket wants to be always on. A serverless function wants to be short and stateless. Bridging those requires external infrastructure, careful use of managed services, and sometimes compromise.

For developers, the decision often comes down to tradeoffs. If the app is light in traffic, bursty in usage, and already serverless-friendly, then an API Gateway plus Lambda approach might be ideal. If instead you need very low latency, millions of concurrent sockets, or strict state guarantees, then traditional or container-based hosting still makes more sense.

The good news is that the line is blurring. Providers are building more real-time friendly serverless offerings, and hybrid patterns are becoming common. In a couple of years, running a massive real-time Node.js application entirely serverless may not only be possible but the easiest path.

Top comments (0)