Architectural choices

PubNub clients connect directly to the PubNub network, not to a server you run. Most architectural decisions in a PubNub-based application follow from that one property.

Your own infrastructure is optional on the message path, and where you choose to insert it changes latency, resilience, and cost. This page works through the decisions that recur across a PubNub application, and links to the guide or reference that covers each one:

  • how directly your clients should connect to PubNub, and where your server still belongs
  • when to run your own logic synchronously in the message path, and when to run it after the fact
  • why access control belongs in the design from the start, not added on later
  • how channel design and grouping decisions scale with traffic
  • which data needs to outlive the moment it's published, and what that costs
  • how limits, rate control, and failure handling fit into the design instead of surprising you in production

Connect clients directly, keep your server optional​

A PubNub client publishes and subscribes straight to PubNub's edge network. Nothing in the pub/sub model requires your server to sit between them. Publish-processing latency, the time PubNub takes to accept and acknowledge a publish request, is about 0.5 ms within the same region, a figure that assumes the message travels straight from client to PubNub. Routing a publish through your own server first adds that server's own processing time on top of it. It also turns your server into a dependency the message can't get past. If your server is slow, overloaded, or down, the publish doesn't happen either.

The same reasoning applies to receiving. A client with its own connection to PubNub gets a message the moment PubNub delivers it. A client that instead waits on a proxy, or on a server-maintained connection, inherits that intermediary's downtime as its own. A proxy in the middle can also conflict with the encryption and the long-lived TCP connection that PubNub's SDKs already maintain.

In the recommended topology, the client publishes and subscribes directly with PubNub. In the alternative, the client sends messages to your server first and your server forwards them to PubNub. That extra hop adds latency and makes your server a dependency.


Keep the direct connection. Bring your server into the picture only for what it uniquely provides: business logic your client shouldn't run, credentials your client shouldn't hold, or a copy of data your client didn't ask PubNub to keep. For the concrete steps that put this into practice, refer to How to send messages effectively and How to receive messages effectively.

Decide when your server sees a message​

Once clients connect directly, the remaining question is when your logic runs, not whether it runs at all. Functions run your JavaScript on PubNub's own network, in the path of a message, so you get server-side logic without putting a server back on the connection path.

The trigger you attach a Function to decides the tradeoff. A Before Publish Function runs synchronously. The publisher waits for it, so you can validate, transform, redact, or block a message before anyone receives it. That guarantee costs you the wait. An After Publish Function runs on a copy of the message after delivery. It can log, forward, or aggregate data without adding latency for anyone waiting on that message, but it can no longer change what subscribers already received. Attaching both to the same channel is a normal pattern. Validate synchronously, then forward to an external system asynchronously, so subscribers never wait on the part of the pipeline they don't need.

On the synchronous path, the publisher calls publish() and waits while the Before Publish Function validates, transforms, or blocks the message. PubNub then delivers the message to subscribers. On the asynchronous path, PubNub passes a copy to the After Publish Function without making anyone wait, and that function logs, forwards, or aggregates the data for an external system.


This choice is also how you copy PubNub data into your own systems without doubling client traffic. Publishing a message once to PubNub, and a second time straight to your server, doubles the mobile data and battery cost of every message. It also gives you two delivery paths that can disagree. An After Publish Function can forward a copy to your server instead. Or a server can read it back later through Message Persistence, which stores published messages for later retrieval. Either way, one publish does the job.

Treat access control as a starting decision​

Without Access Manager enabled, any client holding your publish and subscribe keys can read and write on every channel on that keyset. So adding access control later means every client integration you already shipped was built without it. Design the token-issuing flow before you ship. Your server holds the secret key and grants a time-limited token. The client still initializes the SDK with the keyset's publish and subscribe keys, and separately requests a token from your server for the permissions Access Manager enforces, instead of getting unrestricted access from the keys alone:

1import PubNub from 'pubnub';
2
3// The publish and subscribe keys identify your keyset. They stay in the client configuration.
4const pubnub = new PubNub({
5 publishKey: 'YOUR_PUBLISH_KEY',
6 subscribeKey: 'YOUR_SUBSCRIBE_KEY',
7 userId: 'YOUR_USER_ID',
8});
9
10// Your server holds the secret key, authenticates the user, and returns a token
11// granted for this User ID. Replace the URL with your own token endpoint.
12const response = await fetch('https://your-server.example.com/pubnub-token', {
13 credentials: 'include',
14});
15if (!response.ok) {
show all 21 lines

Hardcoding keys or embedding your secret key in a client build removes that option rather than deferring it. A key baked into a shipped app can't be rotated without a new release, and that release then has to reach every user still running the old build. A compromised secret key lets an attacker issue tokens with any permission on that keyset, not only the one your app used. For the reasoning behind that token-issuing architecture and how to structure it, refer to Security best practices and Security.

Shape channels around traffic, not the other way around​

Channels are created implicitly the first time they are used and do not require provisioning. That means the number and shape of your channels is a design decision you can revisit, not infrastructure you provision once and live with. Most applications end up with many narrow channels rather than a few broad ones. A narrow channel scopes access control, presence, and message volume to exactly the audience that needs it. A dot-delimited naming hierarchy then lets a client subscribe to a whole group of them with one wildcard pattern. Refer to Pub/Sub for the model this rests on.

That design choice compounds at scale. A server that subscribes to every channel your users create doesn't scale well. A small, fixed set of dedicated ingress channels scales better, because it turns an open-ended subscription list into a shardable one. In this fan-in, many per-user channels publish into a small, fixed set of ingress channels, and your server subscribes only to that fixed set.


Refer to Message aggregation for that pattern. Presence and channel groups can do the same fan-out for a social feature, in place of your own graph-traversal code. Friend List and Status Feed covers that pattern, built on per-user channels and channel groups. Rate limiting covers the throttling and sharding choices for a channel that carries one very large audience at once.

Decide what has to outlive the message​

PubNub's live delivery path is ephemeral by design. A published message reaches whoever is subscribed at that moment, and nothing more, unless you turn on the storage system that keeps it. Data storage covers Message Persistence, App Context, DataSync, and Files, the four independent systems that make different kinds of data durable. Choosing which ones your application needs is an architectural decision, not a default you inherit.

That decision has consequences beyond storage itself. Message Persistence retention is 1 or 7 days on the Free plan, 30 days, 3 months, or 6 months on Starter, and 1 year or Unlimited on Pro, and a longer window costs more and leaves more historical data for your compliance obligations to cover. App Context and Files each commit to a single region the moment you enable them. You can't change that region afterward, so where your users are shapes that choice up front. Refer to Data persistence and privacy for how retention, region, and deletion interact with compliance. Refer to Pricing for how retention and storage volume affect cost.

Design for limits and failure, not around them​

Every API PubNub exposes has a soft limit you're expected to stay under, and a hard limit it enforces. API limits covers both, API by API. Read those limits while you design your channel structure or publish rate, not after a keyset starts rejecting requests. That's the difference between a limit shaping your design and a limit breaking it in production. A single, very high-occupancy channel makes the point clearly. The traffic pattern that works for a small audience doesn't automatically hold for a much larger one. Rate limiting covers the throttling and sharding choices that keep it working.

The same applies to failure. PubNub SDKs surface a status code for every request. Deciding how your client responds to each category, retry, back off, surface the failure to a user, or drop it, is part of the architecture. That decision belongs in the design, not in a patch you add once users report a stuck screen. Refer to Error codes for the codes themselves. Refer to Troubleshooting to diagnose one you don't recognize.

Next steps​

Was this page useful?

Last updated on