148

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,886
  • 4
  • 29
  • 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

8 Answers8

182

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,990
  • 3
  • 33
  • 29
68

In node >= v6.1.0:

const readline = require('readline');

readline.emitKeypressEvents(process.stdin);

if (process.stdin.setRawMode != null) {
  process.stdin.setRawMode(true);
}

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

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

arve0
  • 3,424
  • 26
  • 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
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
  • 17,550
  • 9
  • 53
  • 47
  • 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
  • 5
    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
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
  • 142,938
  • 30
  • 279
  • 274
  • 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,232
  • 13
  • 11
6

This will output each keypress. Change console.log with whatever code you like.

process.stdin.setRawMode(true).setEncoding('utf8').resume().on('data',k=>console.log(k))
Jonathan
  • 1,007
  • 16
  • 12
  • It works perfectly. I would only appreciate it if you could add some comment in the code about its use. – Manuel Rosendo Castro Iglesias Nov 18 '22 at 01:59
  • 1
    For me I find the most useful answers on StackOverflow to be the one liners I can quickly copy paste and don't have a lot of explanation I have to read through. Sometimes I will even come across my old answers that are super brief and I enjoy pasting them into my new code. The way code works is really obvious if you understand javascript to a good level. If you want beginner level code that is verbose and commented, the other answers are likely to suit you better. For me I see them as time wasting. I just want to meat. Not the fluff. – Jonathan Nov 18 '22 at 09:51
  • 2
    My opinion: Finding out the use and its operation, yes it is a waste of time. Is it really what I need or not? Knowing it in advance saves a lot of time. – Manuel Rosendo Castro Iglesias Nov 20 '22 at 13:34
  • ```process.stdin.setRawMode(true).resume().on('data',k=>console.log(k))``` is a shorter version which returns a Buffer object instead of a string of characters. – Jonathan Dec 01 '22 at 06:30
  • Be aware that if you press more than one key at the same time you will receive all the key codes at one time in "k". – Jonathan Dec 01 '22 at 06:35
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
  • 545
  • 1
  • 5
  • 18
Lux
  • 1,540
  • 1
  • 22
  • 28
1

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
  • 20,575
  • 8
  • 83
  • 77