Contents

Chapter 21

Interlude: Build Something Real

No new syntax. An expense tracker that remembers.

Nothing new here. No syntax, no methods, no compiler flags you haven’t already met.

That’s the point. You have twenty chapters of tools and you have used them one or two at a time, on programs written to demonstrate them. This chapter is one program that needs most of them at once, which is a different skill and the one that actually matters.

What you’re building

An expense tracker. You record what you spent, and it tells you where the money went and remembers between runs.

$ printf 'add 3.50 food Coffee\n' | node main.js
Added: Coffee $3.50 (food)

$ printf 'add 2.40 travel Bus fare\n' | node main.js
Added: Bus fare $2.40 (travel)

$ printf 'report\n' | node main.js
Total: $5.90
  food: $3.50
  travel: $2.40

Three runs, three separate programs starting and stopping, and the third one knows about the first two.

What it uses

Worth seeing the list, because it’s the argument for the last twenty chapters:

ChapterDoing what
5reading the command from stdin
7, 8deciding what the command was, looping over expenses
9every piece of work has a name
12an Expense shape
14.reduce() for the total
17the file that makes it remember
18a Map for per-category totals
19validating the file, because you didn’t write what’s in it
20four files, each with one job

Build it in stages

Do not write all four files and then run it. Get something working, then make it better, five times over. Each stage below runs.

Stage 1: record one expense, in memory

One file. A hardcoded Expense, printed. No input, no file, no split.

type Expense = { title: string; amount: number; category: string };

const e: Expense = { title: "Coffee", amount: 3.5, category: "food" };
console.log(`${e.title} $${e.amount.toFixed(2)} (${e.category})`);

That’s chapter 12 and chapter 4. It runs, and now you have something to improve rather than a blank file.

Stage 2: an array, a total, and categories

Add three or four expenses to an array and report on them. .reduce() for the total, a Map for per-category sums, both from chapters 14 and 18.

Print the total and each category. Check the category amounts add up to the total by hand, because that’s the kind of number that looks right whether or not it is.

Stage 3: remember it

Now chapter 17. Load at the start, save at the end, existsSync for the first run.

Run it twice and confirm the second run finds the first one’s data. This is the stage where the program stops being a demonstration.

Stage 4: take a command

Chapter 5’s single read. Split the line, look at the first word:

add 3.50 food Coffee
report
list

add takes an amount, a category, and the rest of the line as a title. report prints the totals. list prints everything.

The title being “the rest of the line” is the fiddly part, and .slice(3) on the split words plus .join(" ") is the shortest honest answer.

Chapter 19 applies to the amount: Number(parts[1]) on abc gives you NaN, and a NaN in a total poisons every number after it. Check it before you accept it.

Stage 5: split it up

Chapter 20. Four files:

expense.ts   the type, the validator, total, byCategory
storage.ts   load and save
format.ts    money and line formatting
main.ts      read the command, decide, print

Move one function at a time and confirm it still runs. Compile with npx tsc --module commonjs *.ts.

Things that will go wrong

All of these are from earlier chapters, and all of them are easier to recognise than to debug:

What you still can’t do

This is the honest end of Part 1, and the list is what Part 2 is for.

Your tracker reads all input in one go and can’t ask a follow-up question, because chapter 5’s readFileSync reads to end-of-file and there’s no going back. Interactive programs need asynchronous code, which is chapters 25 and 26.

You’ve written Array<number> and Map<string, number> and been told the angle brackets get explained later. They’re generics, chapter 27, and once you have them you can write a container of your own.

You’ve seen string | number and been told it’s a union. Chapter 28 is where those become a tool rather than a curiosity, and where narrowing goes from an if you write to something the type system reasons about.

Chapter 15 showed that private evaporates at runtime and left you with a # and a promise. Chapter 22 explains what else disappears, and why “the types are gone” is the single most useful fact about TypeScript.

And your validator in stage 4 is fifteen lines to check three fields. There are better answers, and chapter 29 is about why TypeScript cares about the shape of a thing rather than its name, which is what makes them possible.

Part 2 is not more features. It’s the machinery underneath the twenty chapters you just used.

Project

The expense tracker

Roughly 3 to 4 hours

Everything above, built in the five stages, in your own words and your own file layout.

The finished program must:

  1. Take add, list and report as commands from stdin.
  2. Persist to expenses.json between runs.
  3. Total correctly, and break the total down by category.
  4. Validate the file on load, skipping and reporting bad entries.
  5. Live in four files, compiled with --module commonjs.

The tests it must pass, and write these down as you go, because a program you can re-check is worth more than one that happened to work once:

  • Add three expenses in three separate runs, then report. All three appear and the total is right.
  • The category amounts add up to the total. Check by hand.
  • Delete expenses.json and run report. Empty tracker, no crash.
  • Put {"title":"X","amount":"lots","category":"food"} in the file. It’s skipped, reported, and the total ignores it.
  • Put {"not":"a list"} in the file. Clear message, empty tracker.
  • add abc food Thing. Rejected before it reaches the file.

Then use it for a week. Genuinely. Add your actual coffees.

You will find something annoying about it within two days, and that annoyance is worth more than this chapter: it’s the first time in this book that you’ll be changing a program because you want it different, rather than because an exercise said to. Fix it. That’s what the rest of programming is.