Nyx Documentations
Server

Internal EventBus

Decoupling business logic from secondary side-effects

Internal EventBus

Nyx utilizes a heavily typed, strictly validated internal EventBus to decouple primary business logic from secondary effects like WebSocket fanouts, push notifications, and background processing.

Why an EventBus?

If the chatService.handleMessage() function had to directly call the WebSocket manager to loop through active connections and push messages, it would become bloated and hard to test.

Instead, chatService.handleMessage() simply validates the message, saves it to PostgreSQL, and calls: eventBus.emit(chatEventTopics.messageCreated, payload)

Strongly Typed Schemas

Every event payload is defined and validated using Zod.

// schema.ts
export const messageCreatedEventSchema = z.object({
  messageId: z.string().uuid(),
  conversationId: z.string().uuid(),
  senderId: z.string().uuid(),
  ciphertext: z.any(), // E2EE Payload
  createdAt: z.string().datetime(),
});

This schema is loaded into the EventBus registry during startup. If any service attempts to emit a messageCreated event with a missing or incorrectly formatted payload, the EventBus will throw a runtime error immediately, preventing silent failures.

Fanout Pipeline

The most common subscriber to the EventBus is the chatRealtimeBridge.

Emits Typed Event Listens Serializes Delivers Service Logic Local EventBus Realtime Bridge Redis Pub/Sub Local Connected Clients

By decoupling these layers, we can easily add new subscribers in the future—for instance, an APNS/FCM Push Notification worker—without ever touching the core message-handling logic.

On this page