Capturing user input in a Node.js application
Older Article
This article was published 7 years ago. Some information may be outdated or no longer applicable.
In a previous article, we looked at how Node.js could accept parameters sent via the CLI. We mentioned that Node.js can also wait for user input. Let’s build an application that does exactly that.
readline
Node.js ships with a built-in module called readline. It gives you an interface to read data from a Readable stream (in our case, process.stdin).
Here’s a bare-bones example that listens for keypresses and spits back information about each one:
const readline = require('readline');
readline.emitKeypressEvents(process.stdin);
process.stdin.setRawMode(true);
process.stdin.on('keypress', (key, data) => {
if (data.ctrl && data.name === 't') {
process.exit();
} else {
console.log('key', key);
console.log('data', data);
}
});
console.log('Press a key');
setRawMode tells the terminal to hand over characters one at a time. Press a key, capture it, act on it.
The example above prints the key pressed plus some metadata (was the ctrl key held down?). Modifier keys return a boolean. Here’s what you’d see after pressing ‘a’ followed by ‘ctrl-b’:
key a
data { sequence: 'a',
name: 'a',
ctrl: false,
meta: false,
shift: false }
key
data { sequence: '\u0002',
name: 'b',
ctrl: true,
meta: false,
shift: false }
Since we’re catching every keypress, we need an escape hatch. That’s the ‘ctrl-t’ combo you see in the code above.
A more sophisticated example
Let’s wire up a function that fetches weather data based on which key gets pressed. We’ll map characters to cities, look up the weather, and return the result.
Key mapping
We’ll use an ES2015 Map for the key-to-city mapping. It’s a key-value structure with helper methods to grab a value by key:
const map = new Map();
map.set('b', 'Budapest');
map.set('h', 'Helsinki');
map.set('l', 'London');
map.set('r', 'Rome');
map.set('s', 'Stockholm');
We can print these values with a for...of loop:
console.log('Use the following key mappings or press ctrl-t to exit:');
for (const [key, value] of map.entries()) {
console.log(`${key} = ${value}`);
}
Now let’s add a function to pull weather data:
function getWeatherData(city) {
const url = `http://api.openweathermap.org/data/2.5/weather?units=metric&appid=YOUR_API_KEY&q=${city}`;
return new Promise((resolve, reject) => {
const lib = url.startsWith('https') ? require('https') : require('http');
const request = lib.get(url, (response) => {
if (response.statusCode < 200 || response.statusCode > 299) {
return reject(
new Error('Failed to load page, status code: ' + response.statusCode)
);
}
const body = [];
response.on('data', (chunk) => body.push(chunk));
response.on('end', () => {
const data = body.join('');
const parsed = JSON.parse(data);
const final = `The weather in ${city} is ${parsed.main.temp}°C.`;
return resolve(final);
});
});
request.on('error', (err) => reject(err));
});
}
We’re using the Openweather API here. You’ll need to register for a free API key to run this sample.
Now let’s create a listener and attach it to the keypress event. Same pattern as before, but this time we’re fetching weather data:
async function listener(key, data) {
if (data.ctrl && data.name === 't') {
process.exit();
} else {
if (map.has(key)) {
const city = map.get(key);
console.log(await getWeatherData(city));
} else {
console.log(
`"${key} is not defined as a key mapping. Please type in a city instead.`
);
removeAndExceptLine();
}
}
}
process.stdin.on('keypress', listener);
If someone presses a key that’s not in our list, we let them type a city name instead. We strip the keypress listener and spin up a new TTY interface to capture a full line of input:
function removeAndExceptLine() {
process.stdin.removeListener('keypress', listener);
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
rl.on('line', async (input) => {
const city = input;
console.log(await getWeatherData(city));
rl.close();
});
}
Let’s test it:
Use the following key mappings or press ctrl-t to exit:
b = Budapest
h = Helsinki
l = London
r = Rome
s = Stockholm
The weather in Helsinki is -3°C.
The weather in Rome is 14.1°C.
"x is not defined as a key mapping. Please type in a city instead.
Paris
The weather in Paris is 6.37°C.
Conclusion
That covers the readline built-in module for capturing user input. We’ve gone from catching single keypresses to building a small weather lookup tool around those concepts.