138

Is it possible to listen for incoming keystrokes in a running nodejs script? If I use process.openStdin() and listen to its 'data' event then the input is buffered until the next newline, like so:

// stdin_test.js
var stdin = process.openStdin();
stdin.on('data', function(chunk) { console.log("Got chunk: " + chunk); });

Running this, I get:

$ node stdin_test.js
                <-- type '1'
                <-- type '2'
                <-- hit enter
Got chunk: 12

What I'd like is to see:

$ node stdin_test.js
                <-- type '1' (without hitting enter yet)
 Got chunk: 1

I'm looking for a nodejs equivalent to, e.g., getc in ruby

Is this possible?

bantic
  • 4,746
  • 4
  • 27
  • 34
  • (Adding this comment so that this question is easier to find; took me a few days to find the right words for it): This is how to read stdin character by character before the newline (new line) character is sent in input. – dizzy Dec 12 '19 at 18:36

7 Answers7

164

For those finding this answer since this capability was stripped from tty, here's how to get a raw character stream from stdin:

var stdin = process.stdin;

// without this, we would only get streams once enter is pressed
stdin.setRawMode( true );

// resume stdin in the parent process (node app won't quit all by itself
// unless an error or process.exit() happens)
stdin.resume();

// i don't want binary, do you?
stdin.setEncoding( 'utf8' );

// on any data into stdin
stdin.on( 'data', function( key ){
  // ctrl-c ( end of text )
  if ( key === '\u0003' ) {
    process.exit();
  }
  // write the key to stdout all normal like
  process.stdout.write( key );
});

pretty simple - basically just like process.stdin's documentation but using setRawMode( true ) to get a raw stream, which is harder to identify in the documentation.

Dan Heberden
  • 10,684
  • 2
  • 32
  • 29
66

You can achieve it this way, if you switch to raw mode:

var stdin = process.openStdin(); 
require('tty').setRawMode(true);    

stdin.on('keypress', function (chunk, key) {
  process.stdout.write('Get Chunk: ' + chunk + '\n');
  if (key && key.ctrl && key.name == 'c') process.exit();
});
Community
  • 1
  • 1
DanS
  • 16,510
  • 8
  • 49
  • 46
  • How would you get a phrase? like `This is a test!` I have commented out the first line, but that only let me type it all at once, it still got split up. any ideas? I can see that it is onkeypress so how do I detect on[return]press ? Is there a better method than pushing every letter to a string, and then using that string? – JamesM-SiteGen Feb 27 '11 at 00:48
  • 7
    Dont worry, I found out my self, `process.stdin.resume(); process.stdin.on('data', function (chunk) { process.stdout.write('data: ' + chunk); });` – JamesM-SiteGen Feb 27 '11 at 00:55
  • 3
    Move the `setRawMode` to be below the `openStdin()`, because you can only set the mode if the `stdin` is initialized. – Tower Dec 05 '11 at 19:22
  • 4
    It appears that stdin no longer emits a keypress event, but instead emits a data event, with difference parameters. – skeggse Aug 02 '12 at 02:28
  • It was available (documented) in the TTY module in versions 0.4.2-0.7.6. As you can see, the functionality was removed from the TTY module in 0.7.7 (https://github.com/joyent/node/commit/aad12d0b265c9b06ae029d6ee168849260a91dd6#diff-6) – skeggse Aug 02 '12 at 02:41
  • 2
    Hey is `openStdin()` a deprecated and old API? (I learned node way after 2011...) – Steven Lu Mar 09 '14 at 04:33
  • 4
    Uh, yeah. In fact `stdin.on('keypress',function(chunk,key))` has been removed in recent versions. And i am pretty sure `openStdin()` has either been removed or is deprecated. Now, you can access stdin as `process.stdin` – Lux May 05 '15 at 14:49
  • I'm guessing you'll need this in shell scripts or whatever. It's nice having the async way of doing it, but isn't there a `prompt()` equivalent we can use in our command line scripts? – Loupax May 25 '15 at 14:11
  • In nodejs v4 LTS, openStdin remains, but is presumably deprecated since it is felt out of the docs. Its implementation, however, is written in terms of API methods that are not deprecated: `function () {\n process.stdin.resume();\n return process.stdin;\n }` – Michael Lang Jun 05 '16 at 18:25
  • 7
    This answer is no longer helpful. Since Node.js API has changed a bit. Find the correct answer below. – Anton N Dec 15 '19 at 15:58
60

In node >= v6.1.0:

const readline = require('readline');

readline.emitKeypressEvents(process.stdin);
process.stdin.setRawMode(true);

process.stdin.on('keypress', (str, key) => {
  console.log(str)
  console.log(key)
})

See https://github.com/nodejs/node/issues/6626

Renato Gama
  • 15,738
  • 12
  • 57
  • 88
arve0
  • 3,019
  • 24
  • 33
  • 3
    Trying this on 7 and I get `process.stdin.setRawMode is not a function`. Will try to dive a bit deeper later. – Matt Molnar Nov 02 '16 at 11:54
  • 3
    @MattMolnar The function is only present if it is a TTY, so check for that first – curiousdannii Dec 18 '16 at 02:42
  • 1
    @MattMolnar you need to run your app as external terminal, see https://stackoverflow.com/questions/17309749/node-js-console-log-is-it-possible-to-update-a-line-rather-than-create-a-new-l/55893009#55893009 – Maksim Shamihulau Oct 22 '19 at 08:15
  • Thanks @MaksimShamihulau for the link, that pushed me on the right track. I was running in an external terminal but i was also using nodemon. Apparently that also messes with TTY raw mode. – Vlad Macovei Dec 17 '21 at 22:23
29

This version uses the keypress module and supports node.js version 0.10, 0.8 and 0.6 as well as iojs 2.3. Be sure to run npm install --save keypress.

var keypress = require('keypress')
  , tty = require('tty');

// make `process.stdin` begin emitting "keypress" events
keypress(process.stdin);

// listen for the "keypress" event
process.stdin.on('keypress', function (ch, key) {
  console.log('got "keypress"', key);
  if (key && key.ctrl && key.name == 'c') {
    process.stdin.pause();
  }
});

if (typeof process.stdin.setRawMode == 'function') {
  process.stdin.setRawMode(true);
} else {
  tty.setRawMode(true);
}
process.stdin.resume();
Peter Lyons
  • 137,901
  • 29
  • 269
  • 269
  • This does not work on node v0.10.25 it says use `process.stdin.setRawMode()` instead but that errors and says no method setRawMode, very annoying – Plentybinary Jun 19 '15 at 19:12
  • 1
    @Plentybinary I suspect you aren't actually running node v0.10.25. I tested this against v0.10.25 and it works properly. and `process.stdin.setRawMode` exists, is a function, and works properly. I also tested on iojs-2.3.1 and it still works there as well. – Peter Lyons Jun 26 '15 at 00:00
  • FWIW, this continues to work nicely at least up to v0.10.40 – John Rix Nov 30 '15 at 15:00
9

With nodejs 0.6.4 tested (Test failed in version 0.8.14):

rint = require('readline').createInterface( process.stdin, {} ); 
rint.input.on('keypress',function( char, key) {
    //console.log(key);
    if( key == undefined ) {
        process.stdout.write('{'+char+'}')
    } else {
        if( key.name == 'escape' ) {
            process.exit();
        }
        process.stdout.write('['+key.name+']');
    }

}); 
require('tty').setRawMode(true);
setTimeout(process.exit, 10000);

if you run it and:

  <--type '1'
{1}
  <--type 'a'
{1}[a]

Important code #1:

require('tty').setRawMode( true );

Important code #2:

.createInterface( process.stdin, {} );
befzz
  • 1,172
  • 12
  • 11
1
if(process.stdout.isTTY){
  process.stdin.on("readable",function(){
    var chunk = process.stdin.read();
    if(chunk != null) {
      doSomethingWithInput(chunk);
    }
  });
  process.stdin.setRawMode(true);
} else {
  console.log("You are not using a tty device...");
}
Federico Fusco
  • 330
  • 1
  • 3
  • 12
Lux
  • 1,525
  • 1
  • 23
  • 27
0

Based on Dan Heberden's answer, here's an async function -

async function getKeypress() {
  return new Promise(resolve => {
    var stdin = process.stdin
    stdin.setRawMode(true) // so get each keypress
    stdin.resume() // resume stdin in the parent process
    stdin.once('data', onData) // like on but removes listener also
    function onData(buffer) {
      stdin.setRawMode(false)
      resolve(buffer.toString())
    }
  })
}

Use like so -

console.log("Press a key...")
const key = await getKeypress()
console.log(key)
Brian Burns
  • 17,878
  • 8
  • 77
  • 67