← Back to blog
PRODUCT6 min read

Blueprint SDKs vs Raw Pub/Sub: Ship in Minutes Instead of Weeks

HB
Henco Burger
February 24, 2026

Real-time features are one of the most-rebuilt things in software. Every team building a chat product starts with the same blank canvas, makes the same decisions about how to structure rooms, how to track presence, how to handle typing indicators, how to catch a client up after a gap, and how to manage reconnection state. Then they spend two to four weeks wiring it together before they can show a product demo.

This is the build vs buy problem for real-time infrastructure. Raw pub/sub gives you complete flexibility at the cost of building everything yourself. Blueprint SDKs give you a domain-specific API that handles the common patterns, so you can ship a working feature on day one.

What Every Real-Time App Rebuilds

Before getting into the comparison, it is worth being specific about what "wiring it yourself" actually means. Here is the list of concerns that every chat application, every notification feed, and every live dashboard rebuilds from scratch:

  • Room management. Creating rooms, joining and leaving them, listing members, enforcing membership rules.
  • Presence tracking. Who is online, detecting disconnects, heartbeat timeouts, multi-tab handling, custom status metadata.
  • Typing indicators. Starting and stopping a typing state, broadcasting it to other room members, timing out stale typing states.
  • Catch-up after a gap. Deciding what a client that was away should see, and rendering it differently from live traffic.
  • Reconnection state. Detecting drops, exponential backoff, re-subscribing to channels, re-joining rooms, flushing any locally queued messages.
  • Unread counts. Tracking which messages each user has seen, computing unread badges, marking conversations as read.
  • Message history. Paginating through past messages, loading older messages on scroll, merging history with the live stream.

Each of these is a real engineering problem with edge cases. Typing indicators need a timeout: if a user starts typing and then closes their tab, the "Alice is typing" indicator should not stick around forever. Presence needs reference counting for multi-tab users. Reconnection needs to re-join rooms, not just re-open the socket. None of this is difficult individually, but together it is weeks of work before your core feature is even started.

The Raw Pub/Sub Approach

Here is roughly what it takes to wire a minimal chat room with raw pub/sub. No history, no unread counts, just connecting, joining a room, sending messages, and showing typing indicators.

import { NoLag } from '@nolag/js-sdk'

// KRAKEN_URL is your broker, for example ws://localhost:8080/ws
const client = NoLag(token, { url: KRAKEN_URL })

// APP_SLUG and the room must be granted to this token in your auth config
const room = client.setApp(APP_SLUG).setRoom(roomId)
let typingTimeout: ReturnType<typeof setTimeout> | null = null

// Handle the room's message and typing topics
room.on('messages', (msg: any) => renderMessage(msg))
room.on('typing', (evt: any) => {
  if (evt.type === 'typing_start') showTypingIndicator(evt.userId)
  if (evt.type === 'typing_stop') hideTypingIndicator(evt.userId)
})

// Presence events are client-level, so they arrive for every room this
// connection is in and you filter them yourself.
client.on('presence:join', (actor) => addMember(actor.actorTokenId, actor.presence))
client.on('presence:update', (actor) => updateMember(actor.actorTokenId, actor.presence))
client.on('presence:leave', (actor) => removeMember(actor.actorTokenId))

// Send a message. Publishers never receive their own messages,
// so render it locally as well.
function sendMessage(text: string) {
  const msg = { userId, displayName, text, timestamp: Date.now() }
  room.emit('messages', msg)
  renderMessage(msg)
}

// Track typing with debounce
function onInputChange() {
  room.emit('typing', { type: 'typing_start', userId }, { qos: 0 })
  if (typingTimeout) clearTimeout(typingTimeout)
  typingTimeout = setTimeout(() => {
    room.emit('typing', { type: 'typing_stop', userId }, { qos: 0 })
  }, 3000)
}

// Subscribe and announce presence on every connect. A reconnect does not
// restore room presence, and a self-hosted broker does not reliably restore
// subscriptions either, so this runs after each reconnect as well.
client.on('connect', () => {
  room.subscribe('messages')
  room.subscribe('typing')
  room.setPresence({ userId, displayName })
})

await client.connect()

That is already over 40 lines and it is missing: multi-tab presence deduplication, mapping actor ids to user profiles, the receiver-side timeout that clears a stale typing indicator, message history loading, unread count tracking, and any notion of which messages a reconnecting client missed. Each of those adds another chunk of code and another set of edge cases to manage.

The Blueprint SDK Approach

A Blueprint SDK wraps the pub/sub infrastructure with a domain-specific API designed for a particular use case. Here is the same chat room with the @nolag/chat Blueprint:

import { NoLag } from '@nolag/js-sdk'
import { NoLagChat } from '@nolag/chat'

const client = NoLag(token, { url: KRAKEN_URL })
// appName is the app your auth config grants the chat blueprint's topics on
const chat = new NoLagChat({ client, appName: APP_SLUG, username: displayName })
await client.connect()
await chat.ready()

// Join a room. Presence and typing indicators are automatic.
const room = chat.joinRoom(roomId)
room.on('message', (msg) => renderMessage(msg))
room.on('userJoined', () => updateMemberList(room.getUsers()))
room.on('userLeft', () => updateMemberList(room.getUsers()))
room.on('typing', ({ users }) => updateTypingIndicator(users))

// Send a message (returns an optimistic ChatMessage for your own UI)
room.sendMessage('Hello!')

// Start typing indicator (auto-stops after inactivity)
room.startTyping()

// Leave the room (presence updates automatically)
chat.leaveRoom(roomId)

About fifteen lines of application code. Presence, typing indicators, user identity, and unread counts are all handled by the Blueprint. The message handler receives other users' messages in order, and your own sends come back through messageSent with an optimistic status so you never have to special-case the self-echo rule. After a reconnect it re-subscribes its lobby and re-sets your presence; it leaves room message subscriptions to the broker to restore, which a self-hosted kraken does not reliably do, so test reconnects against your own deployment. Typing states time out on their own.

One thing no blueprint does for you is catch-up after a gap. NoLag does not replay missed messages: a chat client that reconnects receives what is published from then on, and nothing from the gap. The chat SDK keeps an in-memory cache of what it has seen, so a product that needs history across sessions stores it itself and reads it on join. Handling Disconnects Gracefully covers that pattern.

What a Blueprint Includes

A Blueprint is not just a client SDK. It is a complete package with three parts:

A purpose-built SDK. The client library with the domain-specific API, covering the common patterns for that use case. For chat: joinRoom, sendMessage, startTyping, getMessages. For notifications: subscribe, markRead, markAllRead. For dashboards: joinPanel, leavePanel, setFilters.

A topic layout. The rooms, topics and lobby that the SDK expects, listed on each Blueprint SDK page as the topics to grant. You add them to your kraken auth config or @nolag/core project document, and the app slug you define there is the appName you hand to the SDK.

A working demo. A runnable example showing the Blueprint in action, with a UI you can use as a starting point or just as a reference.

When to Use Raw Pub/Sub vs a Blueprint

Blueprints are the right choice when your use case matches one of the available templates. Chat, notifications, live dashboards, and collaborative features are all well-understood problem spaces with settled patterns. Using a Blueprint means you get a working implementation of those patterns on day one, with the edge cases already handled.

Raw pub/sub is the right choice when your use case is genuinely novel or when you need full control over the message structure, topic design, and delivery semantics. A real-time multiplayer game with custom synchronization logic, a live auction platform with specific bid sequencing requirements, or a specialized IoT telemetry pipeline all benefit from starting with raw pub/sub and building exactly what they need.

The two are not mutually exclusive. You can use the @nolag/chat Blueprint for your chat feature while using raw pub/sub for a custom activity feed with unusual filtering requirements. Blueprints attach to the same core client, so they compose with raw pub/sub subscriptions in the same application without any conflicts.

The question to ask is: am I building something that has been built many times before, or something genuinely new? If the answer is "I need a chat feature", use the Blueprint and spend your engineering time on the parts of your product that are actually differentiated. If the answer is "I need a message delivery system with custom routing logic tied to our domain model", reach for raw pub/sub.