Errors, debugging and testing
Bugs are normal. What separates a calm team from a stressed one is how fast they find the cause, and whether they add a check so the same bug cannot come back. This lesson gives you all three skills: reading errors, debugging in VS Code, and writing tests with Vitest.
By the end you can
- Read a stack trace from the top frame down to the line in your own code
- Throw useful errors, including custom error classes with a
cause - Debug Node and NestJS in VS Code with breakpoints, conditional breakpoints, logpoints and a
launch.json - Write Vitest unit tests with assertions and mocks, and read a coverage report
- Explain the testing pyramid and what each layer costs
The everyday picture
At month end, the stock count at a store shows 12 fewer bottles of cooking oil than the system says. A good manager does not guess. First, find the discrepancy: which article, which day, how many. Second, find the cause: walk back through the goods received notes, the sales and the transfers until one entry does not match. Third, and this is the step people skip, add a check so it cannot happen again: maybe the receiving staff now scan every carton instead of counting by eye.
Debugging code is the same audit. The error message and stack trace tell you where the numbers stopped matching. The debugger lets you walk back through the records one step at a time. And a test is the new check at the receiving door: once it exists, that exact bug gets caught every time, by a machine, before a customer sees it.
Errors and stack traces
When JavaScript hits a problem it cannot handle, it throws an error object. Built-in ones include TypeError (you used a value the wrong way, such as reading a property of undefined), RangeError and SyntaxError. Every Error has a name, a message and a stack. The stack trace is the most useful one: the chain of function calls that were running when the error was created, newest first.
Here is a real one. A cashier types a discount code that does not exist, FEST25:
file:///home/you/checkout-lab/src/discount.js:14
if (subtotal < rule.minSubtotalCents) return 0;
^
TypeError: Cannot read properties of undefined (reading 'minSubtotalCents')
at calculateDiscount (file:///home/you/checkout-lab/src/discount.js:14:23)
at buildReceipt (file:///home/you/checkout-lab/src/receipt.js:6:27)
at file:///home/you/checkout-lab/scripts/typo.js:6:23
at ModuleJob.run (node:internal/modules/esm/module_job:343:25)
at async onImport.tracePromise.__proto__ (node:internal/modules/esm/loader:665:26)
at async asyncRunEntryPointWithESMLoader (node:internal/modules/run_main:117:5)
Node.js v22.22.0Read it in this order. The message first: something was undefined when the code tried to read minSubtotalCents from it. Then the first frame: discount.js, line 14, column 23, inside calculateDiscount. Node even prints that line with a caret under the exact spot. Then walk down: buildReceipt called it from receipt.js line 6, and that was called from typo.js. The frames starting with node:internal are Node's own machinery; you can skip them. In a big NestJS app, the first frame is often inside node_modules. Keep walking down until you reach the first file your team wrote. That is usually where the fix goes.
Quick check
An error says Cannot read properties of undefined (reading 'capCents') on the line Math.min(discount, rule.capCents). What was undefined?
The message names the property being read, and the value before the dot is the one that was undefined. If rule existed but had no capCents, there would be no error at all: the read would just give undefined, which is the quieter bug you meet in Build it.
node:internal frames and library frames until you reach your own code.