Creating a NodeJS Budgeting Tool

In this article, we’re going to show you how to read and write to a .txt file using NodeJS by creating a small budgeting CLI (Command Line Interface).

The project will consist of opening a terminal, and running our project file (budget.js) with Node, so if you don’t have Node installed, head on over to the NodeJS download page before proceeding with this article.

Getting started

We’re going to be reading and writing to a file, thus we will need to use the fs module, which enables us to interact with the file system. We’re also going to use the readline module, which provides an interface for reading data from a Readable stream. Let’s start by importing these modules at the very top of our file project.

const fs = require('fs');
const readline = require('readline');

Next, we should configure the readline interface, as well as setting some variables we’re going to be using regarding our budgeting application logic.

const fs = require('fs');
const readline = require('readline');

const rl = readline.createInterface({
  input: process.stdin,
  output: process.stdout
});

let budget = 0;
const expenses = {};

Okay, we’ve got that part out of the way. It’s time to start thinking about what our application needs to do, and define some functions to accomplish those things.

Writing the application logic

Next up, let’s get started by defining some of our functions. A budgeting app usually has three features: Set Budget, Add Expense, and View Budget. But remember, we’ll be writing and reading from a file, so we’ll need to include a bit more logic. We’ll be needing quite a few functions, such as: saveBudgetToFile(), readBudgetFromFile(), setBudget(), addExpense(), viewBudget(), calculateTotalExpenses().

But before we start writing those, we should write the very first part of the process – a function for displaying a menu of available options when the program is executed.

// previous code 

function showMenu() {
  console.log('\nBudgeting Tool');
  console.log('1. Set Budget');
  console.log('2. Add Expense');
  console.log('3. View Budget');
  console.log('4. Exit');
}

As you can infer, when you run the program (i.e. node budget.js), this function will handle displaying the menu. Of course, we need to take it some steps further, allowing us to actually send an input.

So let’s define a function to handle input.

// previous code 

function promptUser() {
  rl.question('Select an option: ', answer => {
    switch (answer) {
      case '1':
        setBudget();
        break;
      case '2':
        addExpense();
        break;
      case '3':
        viewBudget();
        break;
      case '4':
        rl.close();
        break;
      default:
        console.log('Invalid option');
        showMenu();
        promptUser();
    }
  });
}

As you can see, we have the basis of our application already. So far we’re displaying a menu, and have a function defined waiting to see what a user inputs, calling yet another function per the specified value. As you can imagine, the next step is to start defining those functions within the switch statement, as well as the logic which they will contain.

Well, we might as well define the function for the very first option, case 1, with the function call to setBudget().

function setBudget() {
  rl.question('Enter your budget amount: ', answer => {
    budget = parseFloat(answer);
    saveBudgetToFile();

    console.log(`Budget set to $${budget.toFixed(2)}`);

    showMenu();
    promptUser();
  });
}

So far, the switch statement hits the case for 1, executing the function we just defined above. Within this function, there are a few things going on. First, we’re making use of rl, which takes a callback function, setting the budget to the answer received, and parsing it. There’s also a call to saveBudgetToFile(), which we haven’t defined yet.

Let’s go ahead and define this function and logic:

function saveBudgetToFile() {
  let content = `Budget: ${budget.toFixed(2)}\n`;

  for (let expenseName in expenses) {
    content += `${expenseName}: ${expenses[expenseName].toFixed(2)}\n`;
  }

  content += `Total Budget: ${(budget - calculateTotalExpenses()).toFixed(2)}`;
  
  fs.writeFile('budget.txt', content, err => {
    if (err) throw err;
    console.log('Budget saved to file');
  });
}

Finally, we see why the fs module is helpful – it allowed us to write to a budget.txt file! Now, there is yet another function defined within that code block, the calculateTotalExpenses() function, which is going to… you guessed it, calculate the total expenses from the expenses object.

function calculateTotalExpenses() {
  let totalExpenses = 0;

  for (let expenseName in expenses) {
    totalExpenses += expenses[expenseName];
  }

  return totalExpenses;
}

Okay, that’s nice, eh? I suppose the next step of the program we should create is the ability to add expenses to the file. As we’re currently only setting the budget. Let’s do that now.

function addExpense() {
  rl.question('Enter expense name: ', name => {
    rl.question('Enter expense amount: ', amount => {
      const expenseAmount = parseFloat(amount);

      if (isNaN(expenseAmount)) {
        console.log('Invalid amount');
      } 
      else {
        expenses[name] = expenseAmount;
        saveBudgetToFile();
        console.log(`Expense "${name}" of $${expenseAmount.toFixed(2)} added`);
      }
      showMenu();
      promptUser();
    });
  });
}

This code defines a function, addExpense(). When called, it prompts the user to enter the name and amount of an expense using the rl.question function. It then converts the entered amount to a floating-point number.

If the amount is invalid (not a number), it displays an error message. Otherwise, it adds the expense to the expenses object, saves the updated budget to a file, and prints a confirmation message. Finally, it displays the main menu and prompts the user for input again.

Let’s create yet another part of the application, reading from the file. We’ll do that by defining a function. Let’s call it readBudgetFromFile():

function readBudgetFromFile() {

  if (fs.existsSync('budget.txt')) {
    const fileContent = fs.readFileSync('budget.txt', 'utf8');
    const lines = fileContent.split('\n');

    for (const line of lines) {
      const [key, value] = line.split(': ');

      if (key === 'Budget') {
        budget = parseFloat(value);
      } 
      else {
        expenses[key] = parseFloat(value);
      }
    }

    console.log(`Budget loaded from file: $${budget.toFixed(2)}`);
  } 
  else {
    console.log('No budget file found. Starting with a budget of $0.00');
  }
}

This function reads the budget data from a file named ‘budget.txt’. If the file exists, it reads the content, parses it line by line, and extracts the budget amount and expenses. If the file does not exist, it displays a message indicating that no budget file is found and sets the budget to $0.00. Lastly, the function will print a message indicating that the budget has been loaded from the file, along with the budget amount.

Simple enough, right?

There should be just one more function we’ll need to define – a function to view the budget within our CLI. Let’s call it viewBudget():

function viewBudget() {
  console.log(`Current budget: $${budget.toFixed(2)}`);
  console.log('Expenses:');

  for (let expenseName in expenses) {
    console.log(`${expenseName}: $${expenses[expenseName].toFixed(2)}`);
  }

  console.log(`Total Budget: $${(budget - calculateTotalExpenses()).toFixed(2)}`);
  showMenu();
  promptUser();
}

Okay, that’s it! Or is it? No, if we run this, nothing seems to happen. We’ll have to call three of our functions down below to make everything work.

// previous code

readBudgetFromFile();
showMenu();
promptUser();

Now if we run node budget.js, we should see some action.

Further, if we inspect our folder, we should see the newly created budget.txt file, with the information from the CLI stored within it.

Full Code on GitHub

Conclusion

That wraps this article up! We hope you had fun creating this neat little tool. It is indeed quite simple, but you can always extend this to have some more functionality, as this is where true learning usually comes in.

You might want to extend this application by:

  • Adding expense categories: implement the ability to categorize expenses
  • Adding Alerts: Set up alerts for when a user exceeds a certain percentage of their budget.

And so on. The choice is yours.

Happy Hacking!

comments powered by Disqus

Related Posts

Solving a Common Interview Question: the Two Sum Algorithm in JavaScript

Imagine you’re at a lively party, and everyone is carrying a specific number on their back. The host announces a game – find two people whose numbers add up to the magic number, and you win a prize!

Read more

Remove Duplicates from Arrays and Strings in JavaScript

Removing duplicates is a common problem in programming that can arise in various contexts, such as cleaning up data or ensuring unique entries.

Read more

Remote First: 5 Websites for Remote Job Opportunities

Would you prefer to work in an office, or while sitting at a beach somewhere in Thailand (i.e. remotely)? Okay, maybe there’s no beach in this scenario, but there’s definitely silence, and maybe your cat.

Read more