The readline module enables interactive command-line interfaces and line-by-line input processing.
Basic Usage#
1const readline = require('readline');
2
3// Create interface
4const rl = readline.createInterface({
5 input: process.stdin,
6 output: process.stdout,
7});
8
9// Ask a question
10rl.question('What is your name? ', (answer) => {
11 console.log(`Hello, ${answer}!`);
12 rl.close();
13});
14
15// With promise wrapper
16function askQuestion(query) {
17 return new Promise((resolve) => {
18 rl.question(query, resolve);
19 });
20}
21
22// Using promises
23async function main() {
24 const name = await askQuestion('Name: ');
25 const age = await askQuestion('Age: ');
26 console.log(`${name} is ${age} years old`);
27 rl.close();
28}
29
30main();Promises API (Node 17+)#
1const readline = require('readline/promises');
2
3async function main() {
4 const rl = readline.createInterface({
5 input: process.stdin,
6 output: process.stdout,
7 });
8
9 const name = await rl.question('What is your name? ');
10 const age = await rl.question('How old are you? ');
11
12 console.log(`${name} is ${age} years old`);
13
14 rl.close();
15}
16
17main();Event-Based Interface#
1const readline = require('readline');
2
3const rl = readline.createInterface({
4 input: process.stdin,
5 output: process.stdout,
6 prompt: '> ',
7});
8
9// Display prompt
10rl.prompt();
11
12// Handle each line
13rl.on('line', (line) => {
14 const input = line.trim();
15
16 switch (input) {
17 case 'hello':
18 console.log('Hello!');
19 break;
20 case 'exit':
21 console.log('Goodbye!');
22 rl.close();
23 return;
24 default:
25 console.log(`You said: ${input}`);
26 }
27
28 rl.prompt();
29});
30
31// Handle close
32rl.on('close', () => {
33 console.log('Interface closed');
34 process.exit(0);
35});
36
37// Handle SIGINT (Ctrl+C)
38rl.on('SIGINT', () => {
39 rl.question('Are you sure you want to exit? (y/n) ', (answer) => {
40 if (answer.toLowerCase() === 'y') {
41 rl.close();
42 } else {
43 rl.prompt();
44 }
45 });
46});Reading Files Line by Line#
1const readline = require('readline');
2const fs = require('fs');
3
4async function processFile(filename) {
5 const fileStream = fs.createReadStream(filename);
6
7 const rl = readline.createInterface({
8 input: fileStream,
9 crlfDelay: Infinity, // Recognize all CR LF sequences
10 });
11
12 let lineNumber = 0;
13
14 for await (const line of rl) {
15 lineNumber++;
16 console.log(`Line ${lineNumber}: ${line}`);
17 }
18
19 console.log(`Total lines: ${lineNumber}`);
20}
21
22processFile('data.txt');
23
24// With error handling
25async function processFileWithErrors(filename) {
26 try {
27 const fileStream = fs.createReadStream(filename);
28
29 fileStream.on('error', (error) => {
30 console.error('File error:', error.message);
31 });
32
33 const rl = readline.createInterface({
34 input: fileStream,
35 crlfDelay: Infinity,
36 });
37
38 for await (const line of rl) {
39 // Process line
40 processLine(line);
41 }
42 } catch (error) {
43 console.error('Processing error:', error.message);
44 }
45}Interactive Menu#
1const readline = require('readline');
2
3class Menu {
4 constructor(options) {
5 this.options = options;
6 this.rl = readline.createInterface({
7 input: process.stdin,
8 output: process.stdout,
9 });
10 }
11
12 display() {
13 console.log('\n=== Main Menu ===');
14 this.options.forEach((option, index) => {
15 console.log(`${index + 1}. ${option.label}`);
16 });
17 console.log('0. Exit\n');
18 }
19
20 async run() {
21 while (true) {
22 this.display();
23
24 const choice = await this.askQuestion('Select option: ');
25 const index = parseInt(choice, 10);
26
27 if (index === 0) {
28 console.log('Goodbye!');
29 this.rl.close();
30 break;
31 }
32
33 if (index > 0 && index <= this.options.length) {
34 await this.options[index - 1].action();
35 } else {
36 console.log('Invalid option');
37 }
38 }
39 }
40
41 askQuestion(query) {
42 return new Promise((resolve) => {
43 this.rl.question(query, resolve);
44 });
45 }
46}
47
48// Usage
49const menu = new Menu([
50 {
51 label: 'Say Hello',
52 action: () => console.log('Hello, World!'),
53 },
54 {
55 label: 'Show Date',
56 action: () => console.log(new Date().toLocaleDateString()),
57 },
58 {
59 label: 'Calculate',
60 action: async () => {
61 // Can do async operations
62 },
63 },
64]);
65
66menu.run();Password Input#
1const readline = require('readline');
2
3function askPassword(prompt) {
4 return new Promise((resolve) => {
5 const rl = readline.createInterface({
6 input: process.stdin,
7 output: process.stdout,
8 });
9
10 // Mute output
11 const stdin = process.stdin;
12 stdin.on('data', (char) => {
13 char = char.toString();
14 switch (char) {
15 case '\n':
16 case '\r':
17 case '\u0004':
18 stdin.pause();
19 break;
20 default:
21 process.stdout.write('*'); // Show asterisks
22 break;
23 }
24 });
25
26 rl.question(prompt, (answer) => {
27 console.log(); // New line after password
28 rl.close();
29 resolve(answer);
30 });
31 });
32}
33
34// Simpler approach with readline directly
35function askPasswordSimple(prompt) {
36 return new Promise((resolve) => {
37 const rl = readline.createInterface({
38 input: process.stdin,
39 output: new (require('stream').Writable)({
40 write: (chunk, encoding, callback) => callback(),
41 }),
42 terminal: true,
43 });
44
45 process.stdout.write(prompt);
46
47 rl.question('', (answer) => {
48 console.log();
49 rl.close();
50 resolve(answer);
51 });
52 });
53}Auto-Complete#
1const readline = require('readline');
2
3const commands = ['help', 'hello', 'history', 'exit', 'clear', 'status'];
4
5function completer(line) {
6 const hits = commands.filter((c) => c.startsWith(line));
7 return [hits.length ? hits : commands, line];
8}
9
10const rl = readline.createInterface({
11 input: process.stdin,
12 output: process.stdout,
13 completer: completer,
14});
15
16rl.prompt();
17
18rl.on('line', (line) => {
19 const cmd = line.trim();
20
21 switch (cmd) {
22 case 'help':
23 console.log('Available commands:', commands.join(', '));
24 break;
25 case 'exit':
26 rl.close();
27 return;
28 case 'clear':
29 console.clear();
30 break;
31 default:
32 console.log(`Unknown command: ${cmd}`);
33 }
34
35 rl.prompt();
36});
37
38// Async completer
39function asyncCompleter(line, callback) {
40 // Could fetch from database, API, etc.
41 setTimeout(() => {
42 const hits = commands.filter((c) => c.startsWith(line));
43 callback(null, [hits.length ? hits : commands, line]);
44 }, 100);
45}Cursor Control#
1const readline = require('readline');
2
3// Move cursor
4readline.cursorTo(process.stdout, 0, 0); // Move to position
5readline.moveCursor(process.stdout, 2, 0); // Move relative
6
7// Clear lines
8readline.clearLine(process.stdout, 0); // Clear current line
9readline.clearScreenDown(process.stdout); // Clear screen below
10
11// Progress indicator
12function showProgress(current, total) {
13 readline.cursorTo(process.stdout, 0);
14 const percent = Math.round((current / total) * 100);
15 const bar = '█'.repeat(percent / 2) + '░'.repeat(50 - percent / 2);
16 process.stdout.write(`Progress: [${bar}] ${percent}%`);
17}
18
19// Usage
20let progress = 0;
21const interval = setInterval(() => {
22 progress += 5;
23 showProgress(progress, 100);
24
25 if (progress >= 100) {
26 clearInterval(interval);
27 console.log('\nDone!');
28 }
29}, 100);History#
1const readline = require('readline');
2
3const rl = readline.createInterface({
4 input: process.stdin,
5 output: process.stdout,
6 historySize: 100,
7 removeHistoryDuplicates: true,
8});
9
10// History is available via arrow keys automatically
11
12// Access history programmatically
13rl.on('line', (line) => {
14 console.log('History:', rl.history);
15 rl.prompt();
16});
17
18// Save/load history
19const fs = require('fs');
20
21function saveHistory(rl, filename) {
22 fs.writeFileSync(filename, rl.history.join('\n'));
23}
24
25function loadHistory(filename) {
26 try {
27 return fs.readFileSync(filename, 'utf8').split('\n').filter(Boolean);
28 } catch {
29 return [];
30 }
31}
32
33// Initialize with saved history
34const savedHistory = loadHistory('.history');
35const rlWithHistory = readline.createInterface({
36 input: process.stdin,
37 output: process.stdout,
38 history: savedHistory,
39});Best Practices#
Interface:
✓ Use promises API when available
✓ Handle close events properly
✓ Implement SIGINT handling
✓ Clean up resources
File Processing:
✓ Use async iterator for large files
✓ Set crlfDelay for cross-platform
✓ Handle stream errors
✓ Process in chunks if needed
CLI:
✓ Provide clear prompts
✓ Implement auto-complete
✓ Support command history
✓ Handle edge cases
Avoid:
✗ Forgetting to close interface
✗ Blocking event loop
✗ Ignoring input validation
✗ Memory issues with large files
Conclusion#
The readline module enables interactive CLI applications and efficient line-by-line file processing. Use the promises API for cleaner async code, implement auto-complete for better UX, and leverage cursor control for progress indicators. Always handle cleanup and edge cases properly.