Oluwasegun Kayode I love every technology and the solution it provides.

Using stdout, stdin, and stderr in Node.js

4 min read 1385

Using Stdout, Stdin, And Stderr In Node.js

stdout, stdin, and stderr are standard streams, which interconnect input and output communication channels between a program and its environment when the program executes.

Streams generally mean the flow of data. You can think of streams as a conveyor belt in a factory connected to different machines (programs in our case). Different machines can be arranged, directed, and connected with a belt (pipe) in a way to produce a particular result.

Just as we can have physical I/O devices connected (input via mouse, output via monitor), standard streams abstract this, giving us the power of composability in our code.

Testimage

Just like the way we can compose powerful Linux commands from small commands, we can make use of Node.js standard streams to achieve the same in Node.js.

Node.js stdin, stdout, and stdin

When we run a Node.js program, a process is started for that program execution.

The GNU documentation defines a process as “the primitive units for allocation of system resources. Each process has its own address space and (usually) one thread of control. A process executes a program; you can have multiple processes executing the same program, but each process has its own copy of the program within its own address space and executes it independently of the other copies.”

Every process is initialized with three open file descriptors called stdinstdout, and stderr.

Those three file descriptors are collectively called the standard streams.

A set of the three standard streams is started for a process and we can access them via the process object in Node.js.

The standards streams are treated as if there are files. An easy way to access any file is by using the unique file descriptor associated with it. In the case of these standards streams, there are unique values assigned to each of them.

  • process.stdin(0): The standard input stream, which is a source of input for the program
  • process.stdout(1): The standard output stream, which is a source of output from the program
  • process.stderr(2): The standard error stream, which is used for error messages and diagnostics issued by the program

Simple use of stdin and stdout

Let’s write a simple application that receives data via the terminal and prints a processed output into the terminal.



We’d create a JavaScript file (index.js) and write the code below:

 // index.js
process.stdin.on("data", data => {
    data = data.toString().toUpperCase()
    process.stdout.write(data + "\n")
})

Running the above program creates an event listener to listen for data input, processes the input, and prints the output to the terminal.

Simple Result

We can stop the running process in our terminal by pressing ctrl + c.

Making use of readline to create interactive terminal scripts

readline is a Node.js module that provides an interface for reading data from a Readable stream (such as process.stdin) one line at a time.

First, we would create a new JavaScript file called index.js, import the readline module into our program, then create a function, ask, that receives a string as an argument and creates a prompt with the string in our terminal:

// index.js
const readline = require("readline")

function ask(question) {
    // asks a question and expect an answer
}

Then we will create an interface using readline that connects stdin to stdout:

// index.js
const readline = require("readline")

const rl = readline.createInterface({
    input: process.stdin, 
    output: process.stdout,
})

function ask(question) {
    // asks a question and expectings an answer
}

We will complete the ask function to expect answers and repeat the whole process recursively:

 // index.js
const readline = require("readline")

const rl = readline.createInterface({
    input: process.stdin, 
    output: process.stdout,
})

function ask(question) {
    rl.question(question, (answer) => {
        rl.write(`The answer received:  ${answer}\n`)

        ask(question)
    })
}

ask("What is your name: ")

Running the above program would create a terminal interface that keeps looping until we end the Node.js process by pressing ctrl + c in the terminal.

The ctrl + c sends a signal to our running Node.js program called SIGKILL, which tells Node.js to stop our program execution. We can also, programmatically, inform Node.js to stop executing our application by calling process.exit(exitCode).

So we will update our ask function to check if the answer from input “q.” If the input is “q” then it should exit the application:

// index.js
const readline = require("readline")

const rl = readline.createInterface({
    input: process.stdin, 
    output: process.stdout,
})

function ask(question) {
    rl.question(question, (answer) => {
        if(answer === "q") {
            process.exit(1)
        }
        rl.write(`The answer received:  ${answer}\n`)

        ask(question)
    })
}

ask("What is your name: ") 

What is stderr?

When we write applications or programs, errors may occur due to so many reasons. stderr is the default file descriptor where a process can write error messages.

Consider this code below:

// index.js
process.stderr.write("error! some error occurred\n")

Running this application with node index.js would write the error message to our terminal similarly to how stdout would output it.

It is pretty straightforward to understand why stdin and stdout exist. However, stderr seems pretty odd.


More great articles from LogRocket:


In the UNIX/Linux-based ecosystem, there was a period where stderr did not exist. All outputs of UNIX commands could be piped via stdout, including both expected results of the command and errors or diagnostic messages.

This is not considered a best practice as the error can also pass through the pipe which the command attached at the end of the pipe may not understand.

Hence, stderr was created to direct error or diagnostic messages via a different file descriptor, which is 2.

N.B., in Linux, when you pipe commands together, only the expecting result is piped together. The error or diagnostic error message is piped via the stderr file descriptor and is by default printed to the terminal.

Let us play around with this by writing two Node.js programs called logger.js and printer.js.

The logger.js is mocking a logging program, but in our case, the logs are predefined already.

Then the printer.js would read data from the stdin and write them to a file.

First, we will create the logger.js below:

const logObject = [
    {
        type: "normal",
        message: "SUCCESS: 2 + 2 is 4"
    },
    {
        type: "normal",
        message: "SUCCESS 5 + 5 is 10"
    },
    {
        type: "error",
        message: "ERROR! 3 + 3 is not 4"
    },
    {
        type: "normal",
        message: "SUCESS 10 - 4 is 6"
    }
]

function logger() {
    logObject.forEach(log => {
        setTimeout(() => {
            if (log.type === "normal") process.stdout.write(log.message)
            else process.stderr.write(log.message + '\n')
        }, 500)
    })
}

logger()

Next, we will create another Node.js file that creates or opens a text file, logs.txt, read inputs provided by stdout, and writes them to a file:

const fs = require("fs")

fs.open("./logs.txt", "w", (err, fd) => {
    if (err) throw Error(err.message)
    process.stdin.on("data", data => {
        fs.write(fd, data.toString() + "\n", (err) => {
            if (err) throw Error(err.message)
        })
    })
})

To run this application, we can pipe these two programs in our terminal by running:

$ node logger.js | node printer.js

N.B., if you are running the above command with Git Bash in Windows, you may come across the error stdout is not a tty. This is likely an issue with Git Bash. You can run the command using Window Powershell or make the script executable by including a shebang (#!/bin/env node) at the top of the file and running the command above as ./logger.js | ./printer.js.

After execution, we can confirm that only the success logs that passed by stdout made it to the logs.txt:

// logs.txt
SUCCESS: 2 + 2 is 4
SUCCESS 5 + 5 is 10
SUCcESS 10 - 4 is 6

And that the error logs were printed to the terminal. This is the default behavior of stderr but we can also change this through redirection and pipelines.

Wrapping up

Now we understand what the standard steams are and how we can make use of them in our Node.js application. We also know how standard streams can help us to build simple programs that can be channeled to formulate more complex programs.

For instance, printer.js doesn’t necessarily need to be aware of what logger.js does. All printer.js do is receive data from a stdout and write the date to a file.

printer.js can be reused and composed with other programs, even with Linux commands, provided they share the same execution environment.

200’s only Monitor failed and slow network requests in production

Deploying a Node-based web app or website is the easy part. Making sure your Node instance continues to serve resources to your app is where things get tougher. If you’re interested in ensuring requests to the backend or third party services are successful, try LogRocket. https://logrocket.com/signup/

LogRocket is like a DVR for web and mobile apps, recording literally everything that happens while a user interacts with your app. Instead of guessing why problems happen, you can aggregate and report on problematic network requests to quickly understand the root cause.

LogRocket instruments your app to record baseline performance timings such as page load time, time to first byte, slow network requests, and also logs Redux, NgRx, and Vuex actions/state. .
Oluwasegun Kayode I love every technology and the solution it provides.

2 Replies to “Using stdout, stdin, and stderr in Node.js”

  1. Thanks for the explanation. I was not aware how to use it and you teach me the basis.

Leave a Reply