DEV Community

A0mineTV
A0mineTV

Posted on

Building Interactive CLI Applications with Node.js: A Beginner's Guide

Introduction

Command Line Interfaces (CLIs) are powerful tools that allow users to interact with applications through text-based commands. While they might seem intimidating at first, building CLI applications with Node.js is surprisingly straightforward and incredibly useful for automating tasks, collecting data, or creating developer tools.

In this tutorial, we'll build a Real Estate Sales Data Collector - an interactive CLI application that gathers apartment sale information and saves it to a JSON file. This practical example will teach you essential CLI development concepts while creating something genuinely useful.

What You'll Learn

  • 📝 How to create interactive prompts for user input
  • ✅ Input validation techniques
  • 💾 File operations and JSON data management
  • 🎯 Error handling in CLI applications
  • 🏗️ Structuring maintainable CLI code

Prerequisites

  • Basic JavaScript knowledge
  • Node.js installed on your system
  • A text editor or IDE

Getting Started

First, let's understand what we're building. Our CLI application will:

  1. Prompt users for apartment details (address, size, price, etc.)
  2. Collect seller and buyer information
  3. Validate all inputs
  4. Display a summary for confirmation
  5. Save the data to a JSON file

Let's dive in! 🚀

Setting Up the Project

Create a new directory and initialize your project:

mkdir real-estate-cli
cd real-estate-cli
npm init -y
Enter fullscreen mode Exit fullscreen mode

Create a file called apartment-sales.js - this will be our main application file.

Core Concepts

1. Using readline for Interactive Input

Node.js provides the readline module for handling user input. Here's how we set it up:

const readline = require('readline');

// Create an interface for reading user input
const rl = readline.createInterface({
    input: process.stdin,
    output: process.stdout
});
Enter fullscreen mode Exit fullscreen mode

2. Creating Promisified Questions

To make our code cleaner and easier to work with async/await, we'll wrap readline in a Promise:

function askQuestion(question) {
    return new Promise((resolve) => {
        rl.question(question, (answer) => {
            resolve(answer.trim());
        });
    });
}
Enter fullscreen mode Exit fullscreen mode

This function allows us to use await when asking questions, making our code much more readable!

Building the Application

Let's build our application step by step:

Step 1: Input Validation Functions

Good CLI applications validate user input. Let's create some helper functions:

// Validate numbers (prices, areas, etc.)
function validateNumber(value, fieldName) {
    const number = parseFloat(value);
    if (isNaN(number) || number <= 0) {
        throw new Error(`${fieldName} must be a valid positive number`);
    }
    return number;
}

// Validate email addresses
function validateEmail(email) {
    const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
    if (!regex.test(email)) {
        throw new Error('Invalid email format');
    }
    return email;
}

// Validate phone numbers
function validatePhone(phone) {
    const regex = /^[\d\s\-\+\(\)]{10,}$/;
    if (!regex.test(phone)) {
        throw new Error('Invalid phone format (minimum 10 digits)');
    }
    return phone;
}
Enter fullscreen mode Exit fullscreen mode

Step 2: Data Collection Function

Now let's create the main function that collects all the apartment sale data:

async function collectApartmentSaleData() {
    console.log('=== APARTMENT SALE DATA COLLECTOR ===\n');

    try {
        const saleData = {};

        // Apartment Information
        console.log('--- Apartment Details ---');
        saleData.apartment = {
            address: await askQuestion('Full address: '),
            city: await askQuestion('City: '),
            postalCode: await askQuestion('Postal code: '),
            area: validateNumber(await askQuestion('Area (m²): '), 'Area'),
            rooms: parseInt(await askQuestion('Number of rooms: ')),
            bedrooms: parseInt(await askQuestion('Number of bedrooms: ')),
            floor: await askQuestion('Floor: '),
            elevator: (await askQuestion('Elevator (yes/no): ')).toLowerCase() === 'yes',
            balcony: (await askQuestion('Balcony (yes/no): ')).toLowerCase() === 'yes',
            parking: (await askQuestion('Parking (yes/no): ')).toLowerCase() === 'yes'
        };

        // Sale Information
        console.log('\n--- Sale Details ---');
        saleData.sale = {
            price: validateNumber(await askQuestion('Sale price (€): '), 'Price'),
            saleDate: await askQuestion('Sale date (DD/MM/YYYY): '),
            saleType: await askQuestion('Sale type (direct/agency): '),
            commission: parseFloat(await askQuestion('Commission (%): ') || '0')
        };

        // Seller Information
        console.log('\n--- Seller Information ---');
        saleData.seller = {
            firstName: await askQuestion('First name: '),
            lastName: await askQuestion('Last name: '),
            email: validateEmail(await askQuestion('Email: ')),
            phone: validatePhone(await askQuestion('Phone: ')),
            address: await askQuestion('Address: ')
        };

        // Buyer Information
        console.log('\n--- Buyer Information ---');
        saleData.buyer = {
            firstName: await askQuestion('First name: '),
            lastName: await askQuestion('Last name: '),
            email: validateEmail(await askQuestion('Email: ')),
            phone: validatePhone(await askQuestion('Phone: ')),
            address: await askQuestion('Address: ')
        };

        // Add metadata
        saleData.createdAt = new Date().toISOString();
        saleData.id = Date.now().toString();

        return saleData;

    } catch (error) {
        console.error(`Input error: ${error.message}`);
        return null;
    }
}
Enter fullscreen mode Exit fullscreen mode

Step 3: JSON File Management

Let's create functions to save our data to a JSON file:

const fs = require('fs');

function saveToJSON(data, filename = 'apartment-sales.json') {
    try {
        let existingSales = [];

        // Check if file already exists
        if (fs.existsSync(filename)) {
            const content = fs.readFileSync(filename, 'utf8');
            existingSales = JSON.parse(content);
        }

        // Add new sale
        existingSales.push(data);

        // Save file
        fs.writeFileSync(filename, JSON.stringify(existingSales, null, 2), 'utf8');

        console.log(`\n✅ Sale saved successfully to ${filename}`);
        console.log(`📊 Total sales: ${existingSales.length}`);

        return true;
    } catch (error) {
        console.error(`❌ Save error: ${error.message}`);
        return false;
    }
}
Enter fullscreen mode Exit fullscreen mode

Step 4: Summary Display

Before saving, let's show a summary to the user:

function displaySummary(saleData) {
    console.log('\n=== SALE SUMMARY ===');
    console.log(`🏠 Apartment: ${saleData.apartment.area}m² in ${saleData.apartment.city}`);
    console.log(`💰 Price: €${saleData.sale.price.toLocaleString()}`);
    console.log(`👤 Seller: ${saleData.seller.firstName} ${saleData.seller.lastName}`);
    console.log(`👤 Buyer: ${saleData.buyer.firstName} ${saleData.buyer.lastName}`);
    console.log(`📅 Sale date: ${saleData.sale.saleDate}`);
}
Enter fullscreen mode Exit fullscreen mode

Step 5: Main Application Logic

Finally, let's put it all together:

async function main() {
    try {
        const saleData = await collectApartmentSaleData();

        if (saleData) {
            displaySummary(saleData);

            const confirm = await askQuestion('\nSave this data? (yes/no): ');

            if (confirm.toLowerCase() === 'yes') {
                saveToJSON(saleData);
            } else {
                console.log('❌ Save cancelled');
            }
        }

    } catch (error) {
        console.error(`Application error: ${error.message}`);
    } finally {
        rl.close();
    }
}

// Run the application
if (require.main === module) {
    main();
}
Enter fullscreen mode Exit fullscreen mode

Running Your Application

To run your CLI application:

node apartment-sales.js
Enter fullscreen mode Exit fullscreen mode

The application will guide you through each step, validate your inputs, show a summary, and save the data to a JSON file.

Example JSON Output

Here's what your saved data will look like:

[
  {
    "apartment": {
      "address": "123 Main Street",
      "city": "Paris",
      "postalCode": "75001",
      "area": 85,
      "rooms": 4,
      "bedrooms": 2,
      "floor": "3rd",
      "elevator": true,
      "balcony": true,
      "parking": false
    },
    "sale": {
      "price": 450000,
      "saleDate": "15/03/2024",
      "saleType": "agency",
      "commission": 3.5
    },
    "seller": {
      "firstName": "John",
      "lastName": "Doe",
      "email": "john.doe@email.com",
      "phone": "+33123456789",
      "address": "456 Oak Avenue"
    },
    "buyer": {
      "firstName": "Jane",
      "lastName": "Smith",
      "email": "jane.smith@email.com",
      "phone": "+33987654321",
      "address": "789 Pine Street"
    },
    "createdAt": "2024-03-15T10:30:00.000Z",
    "id": "1710498600000"
  }
]
Enter fullscreen mode Exit fullscreen mode

Key Takeaways

🎯 What we've learned:

  1. Interactive Input: Using readline to create engaging user experiences
  2. Validation: Ensuring data quality with input validation
  3. Error Handling: Gracefully managing errors and edge cases
  4. File Operations: Reading from and writing to JSON files
  5. Code Organization: Structuring CLI applications for maintainability

Enhancements You Can Add

Ready to take your CLI to the next level? Try adding:

  • 🎨 Colors and styling with libraries like chalk
  • 📊 Progress bars for long operations
  • 🔄 Edit existing records functionality
  • 📈 Data analysis features (average prices, statistics)
  • 🌍 Internationalization support
  • ⚙️ Configuration files for customization

Conclusion

Building CLI applications with Node.js is both rewarding and practical. You've learned how to create interactive experiences, validate user input, and manage data persistence. These skills are valuable for creating developer tools, automation scripts, or data collection applications.

The patterns you've learned here can be applied to countless other CLI applications. Whether you're building deployment tools, data processors, or interactive questionnaires, the foundation is the same.

Top comments (0)