Mastering Inter‑Process Communication in Node.js with Pandora.js IPC‑Hub
This article explains how Pandora.js solves inter‑process communication challenges by introducing IPC‑Hub, showing simple domain‑socket examples, a concise object‑proxy model, and a practical use‑case that offloads heavy webpage‑snapshot tasks to a background process using Puppeteer.
In the previous articles we defined multiple processes and demonstrated how to use processes as a resource. In our Taobao business we also applied this practice, and a new problem became evident: how do processes communicate with each other?
Initially we implemented a very simple IPC using a Domain Socket, following a traditional client‑server model with basic message passing similar to the Node‑IPC package.
Why should the same language on the same machine be so painful? Is Node.js too weak? Do we have to suffer so much to get threads?
The original implementation only supported simple
send()and
broadcast()primitives, which quickly became insufficient for real‑world needs.
Inter‑Process Communication – IPC‑Hub
After some discussion we concluded that the simplest solution is to publish an object that any Node.js process on the same machine can invoke as if it were a local method.
I publish an object, and any Node.js process on this computer can call its methods without extra decoration, just like calling a normal object.
Using Pandora.js, this capability is built‑in and requires no extra configuration.
Simplified Process Object Proxy
We introduced a "concise inter‑process object proxy". In process A we publish an object:
<code>const { publishObject } = require('pandora');
const processInfo = {
async getPid() {
return process.pid; // assume PID is 1
}
};
await publishObject('processInfo', processInfo);
</code>In process B we obtain a proxy to that object and call its method:
<code>const { getProxy } = require('pandora');
const processInfo = await getProxy('processInfo');
const pid = await processInfo.getPid();
console.log(pid);
</code>Even built‑in Node.js objects can be published and invoked remotely.
— Author note: although it has little practical use.
Publishing a Service to IPC‑Hub
Services can be registered and automatically published to the IPC‑Hub:
<code>module.exports = function (pandora) {
pandora.service('serviceName', './serviceImpl').publish();
};
</code>Example: Sending a Webpage Screenshot Task to a Background Process
Heavy or unstable tasks (e.g., large scheduled jobs or unreliable libraries) are isolated into separate processes. The following example shows how a web request can delegate a screenshot task to a background process using Puppeteer.
Implementation Details
First, define
procfile.jsto register services:
<code>module.exports = function (pandora) {
// Adjust background process memory limit
pandora.process('background').argv(['--max-old-space-size=512']);
// Publish the screenshot service
pandora.service('pageSnapshot', './services/PageSnapshot').publish();
// Publish the web front‑end service
pandora.service('web', './services/Web').process('worker').config({ port: 5511 });
};
</code>The
PageSnapshotservice starts a headless Chrome instance, takes a screenshot, and returns a base64‑encoded JPEG:
<code>const puppeteer = require('puppeteer');
module.exports = class PageSnapshot {
async start() {
this.browser = await puppeteer.launch();
this.logger.info('Service PageSnapshot Started');
}
async stop() {
await this.browser.close();
this.logger.info('Service PageSnapshot Stopped');
}
async take(url) {
const page = await this.browser.newPage();
await page.goto(url);
const buf = await page.screenshot({ type: 'jpeg', quality: 60 });
await page.close();
return { base64: buf.toString('base64') };
}
};
</code>The front‑end
Webservice extends a simple HTTP server, parses the query string, obtains a proxy to
pageSnapshot, invokes
take, converts the base64 data back to a Buffer, and streams the JPEG to the client:
<code>const querystring = require('querystring');
const url = require('url');
const SimpleHTTP = require('./SimpleHTTP');
module.exports = class Web extends SimpleHTTP {
async onRequest(req, res) {
const query = querystring.parse(url.parse(req.url).query);
const targetUrl = query.url;
if (!targetUrl) throw new Error('Query [url] is required');
const pageSnapshot = await this.ctx.getProxy('pageSnapshot', { timeout: 10 * 1000 });
const snapshot = await pageSnapshot.take(targetUrl);
const jpg = Buffer.from(snapshot.base64, 'base64');
res.writeHead(200, { 'Content-Type': 'image/jpeg' });
res.end(jpg);
}
getPort() {
return this.ctx.config.port;
}
};
</code>Running the Example
Clone the repository and start the application locally:
<code>$ pandora dev # start the project locally
</code>The logs show that both the
worker(web service) and
background(pageSnapshot service) processes have started successfully.
Now open a browser and request a screenshot:
<code>http://127.0.0.1:5511/?url=https://www.taobao.com/
</code>The server returns a JPEG image of the requested page, demonstrating that the heavy screenshot work was performed in a separate background process and the result was delivered via IPC‑Hub.
Conclusion
This article has covered Pandora.js's inter‑process communication capabilities, including the IPC‑Hub, object proxying, and service publishing. Upcoming articles will explore business‑level metrics such as Metrics and full‑link Tracing.
Don’t forget to star the project on GitHub: https://github.com/midwayjs/pandora/
We are hiring – join us to work on large‑scale Node.js services powering over half of Taobao’s front‑end traffic.
Image credit: https://unsplash.com/photos/kRLXoi3Dtqs By @Ian Simmonds
Taobao Frontend Technology
The frontend landscape is constantly evolving, with rapid innovations across familiar languages. Like us, your understanding of the frontend is continually refreshed. Join us on Taobao, a vibrant, all‑encompassing platform, to uncover limitless potential.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.