Published: October 26, 2026 · by Srinu Desetti (creator & maintainer)
socket.io-valkey-emitter: Emit Socket.IO Events from Any Node.js Process
socket.io-valkey-emitter lets you communicate with a group of Socket.IO servers from another Node.js process — a background worker, a cron job, an API service — anything that is not a Socket.IO server itself. It publishes events to Valkey; the socket.io-valkey-adapter running on your Socket.IO servers picks them up and delivers them to the connected clients. It is a Valkey-native port of @socket.io/redis-emitter — same API and behavior, backed by Valkey instead of Redis — and it works with any ioredis-compatible Valkey client, such as iovalkey (opens in a new tab).
| Package | socket.io-valkey-emitter (opens in a new tab) v0.1.0 |
| License | MIT |
| Runtime dependencies | 3 (debug, notepack.io, socket.io-parser) |
| Node.js client | Any ioredis-compatible Valkey client (iovalkey) |
| TypeScript declarations | Included |
| Source | github.com/webdevelopersrinu/socket.io-valkey-emitter (opens in a new tab) |
The Problem: Not Everything That Needs to Talk to Clients Is a Socket.IO Server
Real systems push real-time events from places that hold no socket connections at all. A payment webhook lands on a plain REST API and the user's dashboard should update. A cron job finishes a nightly report and the admin room should see a notification. A queue worker completes a video transcode and exactly one client should hear about it.
None of these processes are Socket.IO servers. The usual workarounds are all bad:
- HTTP back into the Socket.IO server. Now your worker needs to know which server instance holds which client — which is exactly the problem horizontal scaling created in the first place.
- Make every process a Socket.IO server. Your cron job now carries a WebSocket stack, an HTTP server, and connection state it will never use.
- A custom internal event channel. You end up reinventing rooms, namespaces, and binary encoding — badly.
The emitter dissolves the problem: if your Socket.IO servers already use socket.io-valkey-adapter, every broadcast is already a Valkey Pub/Sub message. So any process that can publish a correctly-encoded packet to the same Valkey is a broadcaster, as far as the adapter is concerned. That is all the emitter is — the publishing half of the adapter's protocol, packaged for processes that never accept a connection.
- Emitter (this package) — runs in any Node.js process and pushes events by publishing to Valkey.
- Adapter (on your Socket.IO servers) — subscribes to Valkey, makes multiple servers act as one, and delivers the events to connected clients.
Both must point at the same Valkey.
Installation
npm install socket.io-valkey-emitter iovalkeyUsage
Hand the emitter any connected Valkey client. From that point the API reads exactly like server-side Socket.IO:
Rooms and namespaces work the same way they do on the server — the targeting is encoded into the published packet, and the adapter routes it:
A realistic worker flow — the process that does the work is the process that announces it, with no hop through an API:
Your Socket.IO servers must use socket.io-valkey-adapter pointed at the same Valkey for the emitted events to reach clients. The emitter publishes; it never knows whether anyone listened.
API Surface
The emitter mirrors the Socket.IO broadcast API, so anything you know how to write in a server handler you can write in a worker:
| Method | Purpose |
|---|---|
emitter.emit(event, ...args) | Broadcast to all connected clients |
emitter.to(room) / emitter.in(room) | Target a room |
emitter.except(room) | Exclude a room |
emitter.of(namespace) | Switch namespace |
emitter.volatile / emitter.compress(bool) | Packet flags |
emitter.socketsJoin(rooms) / emitter.socketsLeave(rooms) | Remote room management |
emitter.disconnectSockets(close) | Remotely disconnect sockets |
emitter.serverSideEmit(event, ...args) | Send to the servers themselves (no acknowledgements) |
The last three rows are worth a second look: the emitter is not limited to pushing data. socketsJoin lets a billing worker move a user into a premium room the moment a payment clears; disconnectSockets lets an admin script force-close sessions — all from processes with no socket handle to any of those clients.
Design Notes
Fire-and-forget, by design
The emitter is publish-only. It holds no subscriber connection, tracks no server list, and waits for no acknowledgements — serverSideEmit explicitly supports none. That asymmetry is what keeps it cheap enough to drop into any process: one Valkey connection, no background protocol. Anything that needs an answer back (like fetchSockets) belongs on the server side, where the adapter runs its request/response protocol.
The wire format is the adapter's, exactly
Packets are encoded with socket.io-parser and MessagePack (notepack.io) — the same encoding the adapter publishes with. To a receiving adapter, a packet from the emitter is indistinguishable from a packet from a peer Socket.IO server. That is why the emitter needs no support code on the server: it speaks the protocol that is already there.
Provenance
socket.io-valkey-emitter is a Valkey-native port of @socket.io/redis-emitter (MIT) — same API and behavior, with the Redis branding removed and the package re-targeted at the Valkey ecosystem and iovalkey. The test suite runs against the real stack: socket.io, socket.io-valkey-adapter, and iovalkey as dev dependencies, so the emitter is verified end-to-end against the adapter it exists to feed.
This is a solo project — I designed, ported, and maintain socket.io-valkey-emitter independently as the publishing companion to the Valkey Socket.IO stack.
What's Next?
The emitter is only half of the conversation — it publishes, and something must deliver. That something is the adapter running on your Socket.IO servers, which also solves the harder problems: cross-instance queries, server counting without a registry, and sharded Pub/Sub.
← Back to Open Source overview
Continue to socket.io-valkey-adapter — the delivery half →
Frequently Asked Questions
How is this different from socket.io-valkey-adapter?
They are two halves of one protocol. The adapter runs inside your Socket.IO servers: it subscribes to Valkey, replicates broadcasts across instances, and delivers events to connected clients. The emitter runs in any other Node.js process — a worker, a cron job, an API — and only publishes. The emitter is useless without the adapter on the receiving side; both must point at the same Valkey.
Can the emitter receive events or acknowledgements?
No — it is publish-only by design. It holds a single Valkey connection, no subscriber, and even serverSideEmit supports no acknowledgements. If a process needs answers back (who is online, did the message arrive), that belongs on a Socket.IO server with the adapter, which runs a request/response protocol over Pub/Sub.
Which Valkey client do I need?
Any ioredis-compatible client works; iovalkey, the Valkey-native client, is the recommended pairing. You construct and connect the client yourself and hand it to the Emitter constructor, so connection options, TLS, and retry behavior stay entirely under your control.
Why port @socket.io/redis-emitter instead of just using it?
Behavior is identical by design — the port keeps the same API so migration is mechanical. But a Valkey stack should not advertise or depend on Redis-branded packages: the port removes the Redis naming, targets iovalkey, and is tested end-to-end against socket.io-valkey-adapter, keeping the whole Socket.IO-on-Valkey dependency tree Redis-free.