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:
- Prompt users for apartment details (address, size, price, etc.)
- Collect seller and buyer information
- Validate all inputs
- Display a summary for confirmation
- 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
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
});
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());
});
});
}
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;
}
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;
}
}
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;
}
}
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}`);
}
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();
}
Running Your Application
To run your CLI application:
node apartment-sales.js
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"
}
]
Key Takeaways
🎯 What we've learned:
-
Interactive Input: Using
readline
to create engaging user experiences - Validation: Ensuring data quality with input validation
- Error Handling: Gracefully managing errors and edge cases
- File Operations: Reading from and writing to JSON files
- 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)