
Security News
Attackers Are Hunting High-Impact Node.js Maintainers in a Coordinated Social Engineering Campaign
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.
Production-Grade Microservices Framework for Node.js
Sub-millisecond Latency • Zero Configuration • Battle-Tested
Zeronode is a lightweight, high-performance framework for building distributed systems in Node.js. Each Node can simultaneously act as both a server (binding to an address) and a client (connecting to multiple remote nodes), forming a flexible peer-to-peer mesh network.
Traditional Client-Server Zeronode Mesh Network
------------------------- ------------------------
+--------+ +---------+
|Client 1|---+ +--| Node A |--+
+--------+ | | +---------+ |
| | <-> |
+--------+ | +------+ | +---------+ |
|Client 2|---+--->|Server| +--| Node B |--+
+--------+ | +------+ | +---------+ |
| | <-> |
+--------+ | | +---------+ |
|Client 3|---+ +--| Node C |--+
+--------+ +---------+
One-way only Each node is both
client AND server!
Unlike traditional client-server architectures, Zeronode provides:
Perfect for: High-frequency trading systems, AI model inference clusters, multi-agent AI systems, real-time analytics, microservices and more.
npm install zeronode
Zeronode automatically installs required dependencies for supported platforms.
// A Node can:
// - bind to an address (accept downstream connections)
// - connect to many other nodes (act as a client)
// - do both simultaneously
import Node from 'zeronode'
// Create a Node and bind
const server = new Node({
// Node id
id: 'api-server',
// Node metadata — arbitrary data used for smart routing
options: { role: 'api', version: 1 }
})
// Bind to an address
await server.bind('tcp://127.0.0.1:8000')
// Register a request handler
server.onRequest('user:get', (envelope, reply) => {
// The envelope wraps the underlying message buffer
const { userId } = envelope.data
// Simulate server returning user info
const userInfo = { id: userId, name: 'John Doe', email: 'john@example.com' }
// Return response back to the caller
return userInfo // or: reply(userInfo)
})
console.log('Server ready at tcp://127.0.0.1:8000')
// Create a new Node
const client = new Node({ id: 'web-client' })
// Connect to the first Node
await client.connect({ address: 'tcp://127.0.0.1:8000' })
// Now we can make a request from client to server
const requestObject = {
to: 'api-server', // Target node ID
event: 'user:get', // Event name
data: { userId: 123 }, // Request payload
timeout: 5000 // Optional timeout in ms
}
// Read user data by id from server
const user = await client.request(requestObject)
console.log(user)
// Output: { id: 123, name: 'John Doe', email: 'john@example.com' }
What does client.connect() do?
Use when you need a response from the target service.
+------------+ +------------+
| Client | | Server |
+------+-----+ +------+-----+
| |
| request('calculate:sum', [1,2,3,4,5]) |
+----------------------------------------------------->|
| |
| Processing... |
| sum = 15 |
| |
|<-----------------------------------------------------+
| reply({ result: 15 }) |
| |
[~0.3ms latency]
// Server: Register a handler
server.onRequest('calculate:sum', ({ data }, reply) => {
const { numbers } = data
// Perform calculation
const sum = numbers.reduce((a, b) => a + b, 0)
// Return result (or call reply({ result: sum }))
return { result: sum }
})
// Client: Make a request
const response = await client.request({
to: 'calc-server',
event: 'calculate:sum',
data: { numbers: [1, 2, 3, 4, 5] }
})
console.log(response.result) // 15
Use when you don't need a response (logging, notifications, analytics).
+------------+ +------------+
| Client | | Server |
+------+-----+ +------+-----+
| |
| tick('log:info', { message: 'User login' }) |
+----------------------------------------------------->|
| |
| <- Returns immediately (non-blocking) |
| |
| Process async
| +-> Log to DB
| +-> Send to monitoring
// Server: Register a tick handler
server.onTick('log:info', ({data}) => {
// envelope.data contains the log data
const { message, metadata } = data
// Process asynchronously (no response expected)
console.log(`[INFO] ${message}`, metadata)
logToDatabase(message, metadata)
})
// Client: Send a tick (non-blocking, returns immediately)
client.tick({
to: 'log-server',
event: 'log:info',
data: {
message: 'User logged in',
metadata: { userId: 123, timestamp: Date.now() }
}
})
Send to multiple nodes simultaneously.
+-------------+
| Scheduler |
+------+------+
|
tickAll('config:reload', { version: '2.0' })
|
+---------------------+---------------------+
| | |
v v v
+----------+ +----------+ +----------+
| Worker 1 | | Worker 2 | | Worker 3 |
|role:worker |role:worker |role:worker
|status:ready |status:ready |status:ready
+----------+ +----------+ +----------+
| | |
+-------> All receive config update <-------+
// Send to ALL nodes matching a filter
await node.tickAll({
event: 'config:reload',
data: { version: '2.0', config: newConfig },
filter: {
role: 'worker', // Only workers
status: 'ready' // That are ready
}
})
+---------+
| Gateway | request({ to: 'user-service-1' })
+----+----+
|
| Direct route by ID
|
v
+--------------+
|user-service-1| <- Exact match
+--------------+
+--------------+
|user-service-2| <- Not selected
+--------------+
// Route to a specific node by ID
const response = await node.request({
to: 'user-service-1', // Exact node ID
event: 'user:get',
data: { userId: 123 }
})
+---------+
| Gateway | requestAny({ filter: { role: 'worker', status: 'idle' } })
+----+----+
|
| Smart routing picks ONE matching node
| (automatic load balancing)
|
+--------------+--------------+
v v v
+---------+ +---------+ +---------+
|Worker 1 | |Worker 2 | |Worker 3 |
|idle (Y) | |busy (N) | |idle (Y) |
+---------+ +---------+ +---------+
^ |
| |
+---- One is selected ---------+
(round-robin)
// Route to ANY node matching the filter (automatic load balancing)
const response = await node.requestAny({
event: 'job:process',
data: { jobId: 456 },
filter: {
role: 'worker', // Must be a worker
status: 'idle', // Must be idle
region: 'us-west', // In the correct region
capacity: { $gte: 50 } // With sufficient capacity
}
})
Automatic service discovery through routers - nodes find each other without direct connections!
Payment Service Router Auth Service
| | |
| No direct connection between them |
| | |
| requestAny() | |
| filter: auth | |
+------------------>| |
| | |
| | Discovers Auth |
| | Forwards request |
| +------------------->|
| | |
| | Response |
| |<-------------------+
| | |
| Response | |
|<------------------+ |
| | |
Basic Router Setup:
import { Router } from 'zeronode'
// 1. Create a Router (special Node with router: true)
const router = new Router({
id: 'router-1',
bind: 'tcp://127.0.0.1:3000'
})
await router.bind()
// 2. Services connect to router (not to each other!)
const authService = new Node({
id: 'auth-service',
options: { service: 'auth', version: '1.0' }
})
await authService.bind('tcp://127.0.0.1:3001')
await authService.connect({ address: router.getAddress() })
const paymentService = new Node({
id: 'payment-service',
options: { service: 'payment' }
})
await paymentService.bind('tcp://127.0.0.1:3002')
await paymentService.connect({ address: router.getAddress() })
// 3. Services discover each other automatically via router!
const result = await paymentService.requestAny({
filter: { service: 'auth' },
event: 'verify',
data: { token: 'abc-123' }
})
// ✅ Router automatically finds auth service and forwards request!
Or use the CLI:
# Start a router from command line
npx zeronode --router --bind tcp://0.0.0.0:8087
# With statistics
npx zeronode --router --bind tcp://0.0.0.0:8087 --stats 5000
See docs/CLI.md for complete CLI reference.
How Router Discovery Works:
Router Features:
// Monitor routing activity
const stats = router.getRoutingStats()
console.log(stats)
// {
// proxyRequests: 150,
// proxyTicks: 30,
// successfulRoutes: 178,
// failedRoutes: 2,
// uptime: 3600,
// requestsPerSecond: 0.05
// }
// Reset statistics
router.resetRoutingStats()
Multi-Hop Routing (Router Cascading):
Routers can forward to other routers for distributed service discovery!
Client → Router1 → Router2 → Service
(no match) (found!)
// Create multiple routers
const router1 = new Router({ bind: 'tcp://127.0.0.1:3000' })
const router2 = new Router({ bind: 'tcp://127.0.0.1:3001' })
// Chain routers together
await router1.connect({ address: router2.getAddress() })
// Client → Router1 → Router2 → Service (automatic!)
Use Cases:
Router Example: See examples/router-example.js for complete working code.
Performance: Router adds ~0.5ms overhead (1.0ms vs 0.5ms direct). See docs/BENCHMARKS.md for details.
Zeronode supports pattern-based handlers using strings or RegExp. With RegExp you can register
one handler for a family of events that share a common prefix. The incoming event name is available
as envelope.event, so you can branch on the action and keep code DRY and fast.
// Handle multiple events with a single handler using RegExp
server.onRequest(/^api:user:/, ({data, tag }, reply) => {
// Matches: 'api:user:get', 'api:user:create', 'api:user:update', etc.
const action = tag.split(':')[2] // 'get', 'create', 'update'
switch (action) {
case 'get':
return getUserData(data)
case 'create':
return createUser(data)
// ...
}
})
Use metadata (Node options) for service discovery and routing.
Metadata for Smart Routing
===========================
+------------------------------+
| Worker Node |
+------------------------------+
| id: 'worker-12345' |
| |
| options: { |
| role: 'worker' | <--- Route by role
| region: 'us-east-1' | <--- Geographic routing
| version: '2.1.0' | <--- Version matching
| capacity: 100 | <--- Load-based routing
| features: ['ml', 'image'] | <--- Capability routing
| status: 'ready' | <--- State-based routing
| } |
+------------------------------+
// Worker node with metadata
const worker = new Node({
id: `worker-${process.pid}`,
options: {
role: 'worker',
region: 'us-east-1',
version: '2.1.0',
capacity: 100,
features: ['ml', 'image-processing'],
status: 'ready'
}
})
// workShedulerNode routes based on metadata
const response = await workShedulerNode.requestAny({
event: 'process:image',
data: imageData,
filter: {
role: 'worker',
features: { $contains: 'image-processing' },
capacity: { $gte: 50 },
status: 'ready'
}
})
// Update options dynamically
await worker.setOptions({ status: 'busy' })
// Process work...
await worker.setOptions({ status: 'ready' })
Advanced Filtering Operators:
filter: {
// Exact match
role: 'worker',
// Comparison
capacity: { $gte: 50, $lte: 100 },
priority: { $in: [1, 2, 3] },
// String matching
region: { $regex: /^us-/ },
name: { $contains: 'prod' },
// Array matching
features: { $containsAny: ['ml', 'gpu'] },
excluded: { $containsNone: ['deprecated'] }
}
Zeronode provides Express.js-style middleware chains for composing request handling logic with automatic handler chaining.
Middleware Chain Flow
=====================
Request arrives
|
v
+---------------------+
| Logging Middleware | <- 2-param: auto-continue
| (2 parameters) |
+----------+----------+
| next() automatically called
v
+---------------------+
| Auth Middleware | <- 3-param: manual control
| (3 parameters) |
+----------+----------+
| next() manually called
v
+---------------------+
| Business Handler | <- Final handler
| Returns data |
+----------+----------+
|
v
Response
+========================+
| If error occurs: |
| -> Error Handler |
| (4 parameters) |
+========================+
// 2-parameter: Auto-continue (logging, metrics)
server.onRequest(/^api:/, (envelope, reply) => {
console.log(`Request: ${envelope.event}`)
// Auto-continues to next handler
})
// 3-parameter: Manual control (auth, validation)
server.onRequest(/^api:/, (envelope, reply, next) => {
if (!envelope.data.token) {
return reply.error('Unauthorized')
}
envelope.user = verifyToken(envelope.data.token)
next() // Explicitly continue
})
// 4-parameter: Error handler
server.onRequest(/^api:/, (error, envelope, reply, next) => {
reply.error({ code: 'API_ERROR', message: error.message })
})
// Business logic
server.onRequest('api:user:get', async (envelope, reply) => {
return await database.users.findOne({ id: envelope.data.userId })
})
See docs/MIDDLEWARE.md for complete middleware patterns, error handling, and best practices.
Zeronode provides comprehensive production-ready examples for common distributed system patterns:
Common Architecture Patterns
============================
API Gateway Pattern Distributed Logging
------------------- -------------------
+---------+ +--------+
| Gateway | |Services|
+----+----+ +---+----+
| |
+------+------+ |
v v v v
+----+ +----+ +----+ +----------+
|API1| |API2| |API3| |Log Server|
+----+ +----+ +----+ +-----+----+
|
Task Queue +-----+-----+
---------- v v
[Store] [Monitor]
+-------+
|Queuer |
+---+---+
|
+------+------+ Microservices Mesh
v v v ------------------
+-----++-----++-----+
|Wrkr1||Wrkr2||Wrkr3| +----+ +----+
+-----++-----++-----+ |Auth|<->|User|
+-+--+ +--+-+
| |
+----+----+
|
+---+---+
|Payment|
+-------+
See docs/EXAMPLES.md for complete working code and usage instructions.
Monitor node connections, disconnections, and state changes:
import { NodeEvent } from 'zeronode'
// Peer joined the network
node.on(NodeEvent.PEER_JOINED, ({ peerId, peerOptions, direction }) => {
console.log(`Peer joined: ${peerId} (${direction})`)
// direction: 'upstream' or 'downstream'
})
// Peer left the network
node.on(NodeEvent.PEER_LEFT, ({ peerId, direction }) => {
console.log(`Peer left: ${peerId}`)
})
// Handle errors
node.on(NodeEvent.ERROR, ({ code, message }) => {
console.error(`Error [${code}]: ${message}`)
})
See docs/EVENTS.md for complete event reference including ClientEvent, ServerEvent, and error handling patterns.
Zeronode delivers sub-millisecond latency with high throughput:
# Run benchmarks
npm run benchmark
See docs/BENCHMARKS.md for detailed benchmark methodology and results.
We welcome contributions! Please see CONTRIBUTING.md for guidelines.
git clone https://github.com/sfast/zeronode.git
cd zeronode
npm install
npm test
MIT
FAQs
Minimal building block for NodeJS microservices
We found that zeronode demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 2 open source maintainers collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.

Security News
Axios compromise traced to social engineering, showing how attacks on maintainers can bypass controls and expose the broader software supply chain.

Security News
Node.js has paused its bug bounty program after funding ended, removing payouts for vulnerability reports but keeping its security process unchanged.