# Capturing user input in a Node.js application

Source: https://tpiros.dev/blog/capturing-user-input-in-a-node-js-application

In a previous article, we looked at [how Node.js could accept parameters sent via the CLI](https://fullstack-developer.academy/how-to-pass-command-line-arguments-to-a-node-js-app/). 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:

```javascript
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:

```javascript
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:

```javascript
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:

```javascript
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](https://openweathermap.org) 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:

```javascript
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:

```javascript
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.
