---
source_url: https://www.pubnub.com/docs/design-patterns/architectural-choices
title: Architectural choices
updated_at: 2026-09-30T07:20:08.000Z
---

# Architectural choices

## Documentation index

To discover more PubNub resources:

1. Fetch [PubNub's llms.txt](https://www.pubnub.com/llms-full.txt) for a list of available pages in Markdown format.
2. Identify relevant URLs from that index.
3. Fetch the target pages.

Do not assume a path exists, always check the index first.

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.

```mermaid
flowchart LR
    C["<b>Client</b>"]
    SRV["<b>Your server</b><br/>optional on the message path"]
    PN["<b>PubNub</b>"]

    C -->|"direct publish/subscribe<br/>(recommended)"| PN
    C -.->|"routed through your server<br/>(adds latency, adds a dependency)"| SRV
    SRV -.-> PN

    class SRV emphasis
    class PN platform
```

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](https://www.pubnub.com/docs/design-patterns/send-messages-effectively.md) and [How to receive messages effectively](https://www.pubnub.com/docs/design-patterns/receive-messages-effectively.md).

## 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](https://www.pubnub.com/docs/message-processing/serverless/overview.md) 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.

```mermaid
flowchart TB
    PUB["<b>Publisher</b><br/>calls publish()"]
    BEFORE["<b>Before Publish Function</b><br/>synchronous, runs before delivery"]
    PN["PUBNUB"]
    SUB["<b>Subscribers</b><br/>receive the message"]
    AFTER["<b>After Publish Function</b><br/>asynchronous, runs on a copy"]
    EXT["<b>External system</b><br/>logged, forwarded, or aggregated"]

    PUB -->|"publisher waits"| BEFORE
    BEFORE -->|"validated, transformed,<br/>or blocked"| PN
    PN --> SUB
    PN -.->|"copy, no wait"| AFTER
    AFTER -.-> EXT

    class BEFORE emphasis
    class AFTER muted
    class EXT external
    class PN platform
```

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](https://www.pubnub.com/docs/data-storage/message-history/overview.md), which stores published messages for later retrieval. Either way, one publish does the job.

## Treat access control as a starting decision

Without [Access Manager](https://www.pubnub.com/docs/security/access-control/overview.md) 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:

```javascript
import PubNub from 'pubnub';

// The publish and subscribe keys identify your keyset. They stay in the client configuration.
const pubnub = new PubNub({
  publishKey: 'YOUR_PUBLISH_KEY',
  subscribeKey: 'YOUR_SUBSCRIBE_KEY',
  userId: 'YOUR_USER_ID',
});

// Your server holds the secret key, authenticates the user, and returns a token
// granted for this User ID. Replace the URL with your own token endpoint.
const response = await fetch('https://your-server.example.com/pubnub-token', {
  credentials: 'include',
});
if (!response.ok) {
  throw new Error(`Token request failed with HTTP ${response.status}`);
}
const { token } = await response.json();

// The token adds scoped authorization to the keyset access the keys provide.
pubnub.setToken(token);
```

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](https://www.pubnub.com/docs/design-patterns/security.md) and [Security](https://www.pubnub.com/docs/security/overview.md).

## 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](https://www.pubnub.com/docs/presence/overview.md), 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](https://www.pubnub.com/docs/pub-sub/overview.md) 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.

```mermaid
flowchart LR
    subgraph USERS[" "]
        direction TB
        U1["channel.user.1"]
        U2["channel.user.2"]
        U3["channel.user.N"]
    end

    subgraph INGRESS[" "]
        direction TB
        I1["<b>Ingress channel 1</b>"]
        I2["<b>Ingress channel 2</b>"]
    end

    SRV["<b>Your server</b><br/>subscribes only to<br/>the fixed ingress set"]

    U1 --> I1
    U2 --> I1
    U3 --> I2
    I1 --> SRV
    I2 --> SRV

    class U1,U2,U3 muted
    class I1,I2 emphasis
```

Refer to [Message aggregation](https://www.pubnub.com/docs/design-patterns/message-aggregation.md) 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](https://www.pubnub.com/docs/design-patterns/friend-list-and-status-feed.md) covers that pattern, built on per-user channels and channel groups. [Rate limiting](https://www.pubnub.com/docs/design-patterns/rate-limiting.md) 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](https://www.pubnub.com/docs/data-storage/overview.md) 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](https://www.pubnub.com/docs/privacy/overview.md) for how retention, region, and deletion interact with compliance. Refer to [Pricing](https://www.pubnub.com/docs/pricing/overview.md) 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](https://www.pubnub.com/docs/architecture/limits.md) 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](https://www.pubnub.com/docs/design-patterns/rate-limiting.md) 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](https://www.pubnub.com/docs/design-patterns/error-codes.md) for the codes themselves. Refer to [Troubleshooting](https://www.pubnub.com/docs/design-patterns/troubleshooting.md) to diagnose one you don't recognize.

## Next steps

* [How to send messages effectively](https://www.pubnub.com/docs/design-patterns/send-messages-effectively.md). Structure the publish side of your application around a direct client connection.
* [How to receive messages effectively](https://www.pubnub.com/docs/design-patterns/receive-messages-effectively.md). Structure the subscribe side and decide where a data copy belongs.
* [Security best practices](https://www.pubnub.com/docs/design-patterns/security.md). Design token issuance and key handling before you ship.
* [Message aggregation](https://www.pubnub.com/docs/design-patterns/message-aggregation.md). Scale server-side aggregation past a single subscriber-per-channel design.
* [Friend List and Status Feed](https://www.pubnub.com/docs/design-patterns/friend-list-and-status-feed.md). Build a social graph on channel groups and presence.
* [Rate limiting](https://www.pubnub.com/docs/design-patterns/rate-limiting.md). Keep a high-occupancy channel usable without fragmenting your audience.
* [Data storage](https://www.pubnub.com/docs/data-storage/overview.md). Choose which PubNub storage systems your application needs.
* [Pricing](https://www.pubnub.com/docs/pricing/overview.md). See how retention, storage, and traffic choices affect cost.

Last updated at: 2026-09-30T07:20:08.000Z
