A named pipe, or FIFO, lets unrelated processes exchange a byte stream through a filesystem path. The stream has no built-in message boundaries or data types, so both sides must agree on a protocol. A newline-delimited text format is a practical choice for sending numbers from Node.js to C: it avoids assumptions about the size, byte order, and representation of a native double.
Create the FIFO Reader in C
The reader creates the FIFO with permissions limited to the current user. If the path already exists, it verifies that the existing object is actually a FIFO before opening it. Opening the read end blocks until a writer connects.
#define _POSIX_C_SOURCE 200809L
#include <ctype.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
int main(int argc, char **argv) {
const char *fifo_path = argc > 1 ? argv[1] : "/tmp/node-to-c.fifo";
int created = 0;
if (mkfifo(fifo_path, 0600) == 0) {
created = 1;
} else if (errno == EEXIST) {
struct stat info;
if (lstat(fifo_path, &info) == -1 || !S_ISFIFO(info.st_mode)) {
fprintf(stderr, "%s exists but is not a FIFO\n", fifo_path);
return EXIT_FAILURE;
}
} else {
perror("mkfifo");
return EXIT_FAILURE;
}
FILE *fifo = fopen(fifo_path, "r");
if (fifo == NULL) {
perror("fopen");
if (created) unlink(fifo_path);
return EXIT_FAILURE;
}
char line[256];
while (fgets(line, sizeof line, fifo) != NULL) {
char *end;
if (strchr(line, '\n') == NULL) {
int byte;
while ((byte = fgetc(fifo)) != '\n' && byte != EOF) {}
fprintf(stderr, "Ignoring oversized or unterminated record\n");
continue;
}
errno = 0;
double value = strtod(line, &end);
while (isspace((unsigned char) *end)) end++;
if (end == line || *end != '\0' || errno == ERANGE) {
fprintf(stderr, "Ignoring invalid number: %s", line);
continue;
}
printf("%.17g\n", value);
}
if (ferror(fifo)) {
perror("fgets");
}
fclose(fifo);
if (created) unlink(fifo_path);
return EXIT_SUCCESS;
} Compile the program with warnings enabled:
cc -std=c11 -Wall -Wextra -pedantic -Werror fifo-reader.c -o fifo-reader Write Values from Node.js
The writer sends one decimal number per line. A writable stream buffers short bursts, while waiting for the drain event prevents an unbounded loop from queuing data faster than the operating system can accept it.
const fs = require("node:fs");
const { once } = require("node:events");
async function main() {
const fifoPath = process.argv[2] ?? "/tmp/node-to-c.fifo";
const values = [1.25, -2, Math.PI];
const stream = fs.createWriteStream(fifoPath, { encoding: "utf8" });
for (const value of values) {
if (!stream.write(`${value}\n`)) {
await once(stream, "drain");
}
}
stream.end();
await once(stream, "finish");
}
main().catch(error => {
console.error(error);
process.exitCode = 1;
}); Run Both Processes
Start the reader in one terminal. It waits at fopen until the write end is opened:
./fifo-reader /tmp/node-to-c.fifo Then start the writer in another terminal:
node fifo-writer.cjs /tmp/node-to-c.fifo The C process prints the three values and exits when Node closes the stream. A FIFO is local to one machine and does not persist queued data after both ends close. For a long-running service, keep the FIFO path under a private runtime directory, define how reconnects should work, and add record-size limits appropriate to the protocol.