Emmanuel Akinfulubi

Errors

The errors you are actually going to hit, in Node's own words. 40 entries across 25 error names and codes.

Every error on this page is real. Each snippet declares the error it expects -- by class, like TypeError, or by code, like ENOENT -- and the build runs it and checks: if it does not die, or dies with something else, the build fails and this page is not written. So what you see under each snippet is what Node printed for exactly that code. The directory each snippet ran in is shown as /app, so the paths are the same on any machine; the names, codes, messages and line numbers are untouched.

It will not even start

Raised while Node is still reading your file, before a single line of it has run -- which is why a console.log you added to debug it prints nothing. Look at what is missing from these: there are no "at ..." stack frames pointing into your code, because none of it ever executed.

SyntaxError A bracket that never closes

A bracket that never closes

dies with SyntaxError

example.mjs

function greet(name) {
  if (name) {
    return "hi " + name;
}

console.log(greet("Ada"));

What node printed as it died, when the build ran it

node example.mjs

file:///app/example.mjs:7



SyntaxError: Unexpected end of input
    at compileSourceTextModule (node:internal/modules/esm/utils:355:16)
    at ModuleLoader.moduleStrategy (node:internal/modules/esm/translators:90:18)
    at #translate (node:internal/modules/esm/loader:441:20)
    at afterLoad (node:internal/modules/esm/loader:509:29)
    at ModuleLoader.loadAndTranslate (node:internal/modules/esm/loader:514:12)
    at #getOrCreateModuleJobAfterResolve (node:internal/modules/esm/loader:567:36)
    at afterResolve (node:internal/modules/esm/loader:614:52)
    at ModuleLoader.getOrCreateModuleJob (node:internal/modules/esm/loader:620:12)
    at node:internal/modules/esm/loader:639:32
    at TracingChannel.tracePromise (node:diagnostics_channel:538:22)

Node.js v26.7.0

What it means. Node reached the end of the file still inside an open {. The location it gives is the end of the file, which is where it noticed -- not where the missing brace belongs. Count your braces upwards from there, or let an editor match them for you.

The fix. Close the if with its own } before the function’s.

SyntaxError await in a function that is not async

await in a function that is not async

dies with SyntaxError

example.mjs

function load() {
  const res = await fetch("http://127.0.0.1:1/");
  return res;
}

What node printed as it died, when the build ran it

node example.mjs

file:///app/example.mjs:2
  const res = await fetch("http://127.0.0.1:1/");
              ^^^^^

SyntaxError: Unexpected reserved word
    at compileSourceTextModule (node:internal/modules/esm/utils:355:16)
    at ModuleLoader.moduleStrategy (node:internal/modules/esm/translators:90:18)
    at #translate (node:internal/modules/esm/loader:441:20)
    at afterLoad (node:internal/modules/esm/loader:509:29)
    at ModuleLoader.loadAndTranslate (node:internal/modules/esm/loader:514:12)
    at #getOrCreateModuleJobAfterResolve (node:internal/modules/esm/loader:567:36)
    at afterResolve (node:internal/modules/esm/loader:614:52)
    at ModuleLoader.getOrCreateModuleJob (node:internal/modules/esm/loader:620:12)
    at node:internal/modules/esm/loader:639:32
    at TracingChannel.tracePromise (node:diagnostics_channel:538:22)

Node.js v26.7.0

What it means. await is allowed at the top level of a module, and inside an async function, and nowhere else. The message is unhelpful -- "Unexpected reserved word" -- but the carets sit exactly under await, and that pairing is worth recognising on sight, because it is always this.

The fix. Write async function load(). Every caller then gets a promise back and has to await it in turn. Lesson 27.

SyntaxError import in a CommonJS file

import in a CommonJS file

dies with SyntaxError

example.cjs

import { readFileSync } from "node:fs";

console.log(typeof readFileSync);

What node printed as it died, when the build ran it

node example.cjs

(node:<pid>) Warning: Failed to load the ES module: /app/example.cjs. Make sure to set "type": "module" in the nearest package.json file or use the .mjs extension.
(Use `node --trace-warnings ...` to show where the warning was created)
/app/example.cjs:1
import { readFileSync } from "node:fs";
^^^^^^

SyntaxError: Cannot use import statement outside a module
    at wrapSafe (node:internal/modules/cjs/loader:1866:18)
    at Module._compile (node:internal/modules/cjs/loader:1908:20)
    at Object..js (node:internal/modules/cjs/loader:2074:10)
    at Module.load (node:internal/modules/cjs/loader:1656:32)
    at Module._load (node:internal/modules/cjs/loader:1448:12)
    at wrapModuleLoad (node:internal/modules/cjs/loader:261:19)
    at Module.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:154:5)
    at node:internal/main/run_main_module:33:47

Node.js v26.7.0

What it means. A .cjs file is CommonJS by definition, and CommonJS has require, not import. The warning above the error tells you both ways out. The same error appears in a .js file whose nearest package.json says "type": "commonjs" -- which npm init now writes by default.

The fix. Rename the file to .mjs, or set "type": "module" in package.json, or use require. Lesson 21.

ReferenceError require in an ES module

require in an ES module

dies with ReferenceError

example.mjs

import { join } from "node:path";
const fs = require("node:fs");

What node printed as it died, when the build ran it

node example.mjs

file:///app/example.mjs:2
const fs = require("node:fs");
           ^

ReferenceError: require is not defined in ES module scope, you can use import instead
    at file:///app/example.mjs:2:12
    at ModuleJob.run (node:internal/modules/esm/module_job:569:25)
    at async node:internal/modules/esm/loader:650:26
    at async asyncRunEntryPointWithESMLoader (node:internal/modules/run_main:101:5)

Node.js v26.7.0

What it means. The reverse of the entry above. require does not exist in an ES module. This file is a module because its name ends in .mjs; a .js file becomes one too the moment Node sees an import in it, which is how a file that used to work breaks when someone adds one line to the top.

The fix. Use import fs from "node:fs". If you genuinely need require inside a module, import { createRequire } from "node:module" gives you one. Lesson 21.

SyntaxError Declaring the same name twice

Declaring the same name twice

dies with SyntaxError

example.mjs

let total = 0;
const items = [1, 2, 3];
let total = items.length;

What node printed as it died, when the build ran it

node example.mjs

file:///app/example.mjs:3
let total = items.length;
    ^

SyntaxError: Identifier 'total' has already been declared
    at compileSourceTextModule (node:internal/modules/esm/utils:355:16)
    at ModuleLoader.moduleStrategy (node:internal/modules/esm/translators:90:18)
    at #translate (node:internal/modules/esm/loader:441:20)
    at afterLoad (node:internal/modules/esm/loader:509:29)
    at ModuleLoader.loadAndTranslate (node:internal/modules/esm/loader:514:12)
    at #getOrCreateModuleJobAfterResolve (node:internal/modules/esm/loader:567:36)
    at afterResolve (node:internal/modules/esm/loader:614:52)
    at ModuleLoader.getOrCreateModuleJob (node:internal/modules/esm/loader:620:12)
    at node:internal/modules/esm/loader:639:32
    at TracingChannel.tracePromise (node:diagnostics_channel:538:22)

Node.js v26.7.0

What it means. let and const refuse to declare a name that already exists in the same scope. var allowed it silently, which is one of the reasons it is no longer used. This is caught before anything runs.

The fix. Assign instead of redeclaring -- total = items.length -- or pick a different name.

ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX A TypeScript enum, run directly

A TypeScript enum, run directly

dies with ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX

example.ts

enum Colour { Red, Green }

console.log(Colour.Green);

What node printed as it died, when the build ran it

node example.ts

file:///app/example.ts:1
enum Colour { Red, Green }
^^^^^^^^^^^^^^^^^^^^^^^^^^

SyntaxError [ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX]: TypeScript enum is not supported in strip-only mode
    at parseTypeScript (node:internal/modules/typescript:57:40)
    at processTypeScriptCode (node:internal/modules/typescript:129:37)
    at stripTypeScriptModuleTypes (node:internal/modules/typescript:172:22)
    at Module._compile (node:internal/modules/cjs/loader:1888:15)
    at Object..js (node:internal/modules/cjs/loader:2074:10)
    at Module.load (node:internal/modules/cjs/loader:1656:32)
    at Module._load (node:internal/modules/cjs/loader:1448:12)
    at wrapModuleLoad (node:internal/modules/cjs/loader:261:19)
    at Module.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:154:5)
    at node:internal/main/run_main_module:33:47 {
  code: 'ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX'
}

Node.js v26.7.0

What it means. Node runs .ts files by erasing the types -- replacing annotations with spaces -- and running what is left. That only works for syntax that is pure type information. An enum generates real JavaScript at runtime, so there is nothing to erase it to, and Node refuses rather than guess.

The fix. Use a plain object with as const, or a union of string literals. Or run the file through the TypeScript compiler. Lesson 47.

Names and values

The code ran, and asked for something that is not there, or is not what it was assumed to be.

ReferenceError A name that was never declared

A name that was never declared

dies with ReferenceError

example.mjs

const userName = "Ada";
console.log("hello", username);

What node printed as it died, when the build ran it

node example.mjs

file:///app/example.mjs:2
console.log("hello", username);
                     ^

ReferenceError: username is not defined
    at file:///app/example.mjs:2:22
    at ModuleJob.run (node:internal/modules/esm/module_job:569:25)
    at async node:internal/modules/esm/loader:650:26
    at async asyncRunEntryPointWithESMLoader (node:internal/modules/run_main:101:5)

Node.js v26.7.0

What it means. JavaScript names are case-sensitive, so username and userName are different names, and only one of them exists. The caret points at the one Node could not find.

The fix. Fix the spelling. An editor with the TypeScript language service underlines this before you run anything, even in plain JavaScript.

ReferenceError Using a variable before its line

Using a variable before its line

dies with ReferenceError

example.mjs

console.log(limit);
const limit = 10;

What node printed as it died, when the build ran it

node example.mjs

file:///app/example.mjs:1
console.log(limit);
            ^

ReferenceError: Cannot access 'limit' before initialization
    at file:///app/example.mjs:1:13
    at ModuleJob.run (node:internal/modules/esm/module_job:569:25)
    at async node:internal/modules/esm/loader:650:26
    at async asyncRunEntryPointWithESMLoader (node:internal/modules/run_main:101:5)

Node.js v26.7.0

What it means. The name does exist -- const and let are known to the whole block from its first line -- but it has no value until its declaration runs. The region above the declaration is called the temporal dead zone, and reading a name inside it is an error rather than undefined. Lesson 10.

The fix. Move the declaration above its first use.

TypeError Reading a property of undefined

Reading a property of undefined

dies with TypeError

example.mjs

const users = [{ name: "Ada" }];
const user = users.find((u) => u.name === "Alan");
console.log(user.name);

What node printed as it died, when the build ran it

node example.mjs

file:///app/example.mjs:3
console.log(user.name);
                 ^

TypeError: Cannot read properties of undefined (reading 'name')
    at file:///app/example.mjs:3:18
    at ModuleJob.run (node:internal/modules/esm/module_job:569:25)
    at async node:internal/modules/esm/loader:650:26
    at async asyncRunEntryPointWithESMLoader (node:internal/modules/run_main:101:5)

Node.js v26.7.0

What it means. The single most common error in JavaScript. find returns undefined when nothing matches, and asking undefined for a property throws. The message names the property it was reading, which tells you which value was missing.

The fix. Decide what should happen when there is no match: if (!user) ..., or user?.name to get undefined instead of an error. Lesson 7.

TypeError Calling something that is not a function

Calling something that is not a function

dies with TypeError

example.mjs

const config = { port: 3000 };
config.port();

What node printed as it died, when the build ran it

node example.mjs

file:///app/example.mjs:2
config.port();
       ^

TypeError: config.port is not a function
    at file:///app/example.mjs:2:8
    at ModuleJob.run (node:internal/modules/esm/module_job:569:25)
    at async node:internal/modules/esm/loader:650:26
    at async asyncRunEntryPointWithESMLoader (node:internal/modules/run_main:101:5)

Node.js v26.7.0

What it means. Parentheses call a function, and config.port is a number. The usual real cause is a misspelled method name, which reads as undefined, and "undefined is not a function" -- or, as here, a property that holds data rather than behaviour.

The fix. Check what the value actually is: console.log(typeof config.port).

TypeError Reassigning a const

Reassigning a const

dies with TypeError

example.mjs

const retries = 3;
retries = retries - 1;

What node printed as it died, when the build ran it

node example.mjs

file:///app/example.mjs:2
retries = retries - 1;
        ^

TypeError: Assignment to constant variable.
    at file:///app/example.mjs:2:9
    at ModuleJob.run (node:internal/modules/esm/module_job:569:25)
    at async node:internal/modules/esm/loader:650:26
    at async asyncRunEntryPointWithESMLoader (node:internal/modules/run_main:101:5)

Node.js v26.7.0

What it means. const means the name cannot be pointed at a different value. It does not mean the value cannot change: a const array can still be pushed to. Lesson 4 runs both.

The fix. Use let for a name you intend to reassign.

TypeError Writing to a frozen object

Writing to a frozen object

dies with TypeError

example.mjs

const settings = Object.freeze({ theme: "dark" });
settings.theme = "light";

What node printed as it died, when the build ran it

node example.mjs

file:///app/example.mjs:2
settings.theme = "light";
               ^

TypeError: Cannot assign to read only property 'theme' of object '#<Object>'
    at file:///app/example.mjs:2:16
    at ModuleJob.run (node:internal/modules/esm/module_job:569:25)
    at async node:internal/modules/esm/loader:650:26
    at async asyncRunEntryPointWithESMLoader (node:internal/modules/run_main:101:5)

Node.js v26.7.0

What it means. Object.freeze makes an object read-only. In old, sloppy-mode scripts the write is silently ignored; an ES module is always strict, so it throws instead, which is the behaviour you want -- a write that silently does nothing is a bug you find much later.

The fix. Make a changed copy rather than editing it: { ...settings, theme: "light" }. Lesson 14.

RangeError Recursion with no way out

Recursion with no way out

dies with RangeError

example.mjs

function countdown(n) {
  return countdown(n - 1);
}

countdown(5);

What node printed as it died, when the build ran it

node example.mjs

file:///app/example.mjs:2
  return countdown(n - 1);
  ^

RangeError: Maximum call stack size exceeded
    at countdown (file:///app/example.mjs:2:3)
    at countdown (file:///app/example.mjs:2:10)
    at countdown (file:///app/example.mjs:2:10)
    at countdown (file:///app/example.mjs:2:10)
    at countdown (file:///app/example.mjs:2:10)
    at countdown (file:///app/example.mjs:2:10)
    at countdown (file:///app/example.mjs:2:10)
    at countdown (file:///app/example.mjs:2:10)
    at countdown (file:///app/example.mjs:2:10)
    at countdown (file:///app/example.mjs:2:10)

Node.js v26.7.0

What it means. Every call takes a slot on the call stack, and this function calls itself forever, so the stack runs out. Node shows only the top ten frames, all identical -- which is itself the clue.

The fix. Add the base case: if (n <= 0) return 0;. If the recursion is genuinely deep, rewrite it as a loop.

RangeError An impossible array length

An impossible array length

dies with RangeError

example.mjs

const size = -1;
const slots = new Array(size);

What node printed as it died, when the build ran it

node example.mjs

file:///app/example.mjs:2
const slots = new Array(size);
              ^

RangeError: Invalid array length
    at file:///app/example.mjs:2:15
    at ModuleJob.run (node:internal/modules/esm/module_job:569:25)
    at async node:internal/modules/esm/loader:650:26
    at async asyncRunEntryPointWithESMLoader (node:internal/modules/run_main:101:5)

Node.js v26.7.0

What it means. An array length must be a whole number from zero to about four billion. A negative or fractional size usually means an arithmetic slip upstream -- here, a count that went below zero.

The fix. Find where the size is computed and guard it. Array.from({ length: n }) is clearer than new Array(n) anyway.

Modules and packages

Node could not find the file or package you imported, or found it and could not load it the way you asked.

ERR_MODULE_NOT_FOUND An import without its file extension

An import without its file extension

dies with ERR_MODULE_NOT_FOUND

greet.mjs

export const greet = (name) => `hi ${name}`;

example.mjs

import { greet } from "./greet";

console.log(greet("Ada"));

What node printed as it died, when the build ran it

node example.mjs

node:internal/modules/esm/resolve:272
    throw new ERR_MODULE_NOT_FOUND(
          ^

Error [ERR_MODULE_NOT_FOUND]: Cannot find module '/app/greet' imported from /app/example.mjs
    at finalizeResolution (node:internal/modules/esm/resolve:272:11)
    at moduleResolve (node:internal/modules/esm/resolve:879:10)
    at defaultResolve (node:internal/modules/esm/resolve:1006:11)
    at #cachedDefaultResolve (node:internal/modules/esm/loader:708:20)
    at #resolveAndMaybeBlockOnLoaderThread (node:internal/modules/esm/loader:728:38)
    at ModuleLoader.resolveSync (node:internal/modules/esm/loader:766:56)
    at #resolve (node:internal/modules/esm/loader:690:17)
    at ModuleLoader.getOrCreateModuleJob (node:internal/modules/esm/loader:610:35)
    at ModuleJob.syncLink (node:internal/modules/esm/module_job:277:33)
    at ModuleJob.link (node:internal/modules/esm/module_job:389:17) {
  code: 'ERR_MODULE_NOT_FOUND',
  url: 'file:///app/greet'
}

Node.js v26.7.0

What it means. The file is right there, and Node still cannot find it. ES modules in Node do not guess extensions: "./greet" means a file literally called greet. Bundlers and CommonJS both add the extension for you, which is why this surprises almost everyone once.

The fix. Write the full name: import { greet } from "./greet.mjs". Lesson 20.

ERR_MODULE_NOT_FOUND A package that is not installed

A package that is not installed

dies with ERR_MODULE_NOT_FOUND

example.mjs

import express from "express";

What node printed as it died, when the build ran it

node example.mjs

node:internal/modules/package_json_reader:301
  throw new ERR_MODULE_NOT_FOUND(packageName, fileURLToPath(base), null);
        ^

Error [ERR_MODULE_NOT_FOUND]: Cannot find package 'express' imported from /app/example.mjs
    at Object.getPackageJSONURL (node:internal/modules/package_json_reader:301:9)
    at packageResolve (node:internal/modules/esm/resolve:784:25)
    at moduleResolve (node:internal/modules/esm/resolve:873:18)
    at defaultResolve (node:internal/modules/esm/resolve:1006:11)
    at #cachedDefaultResolve (node:internal/modules/esm/loader:708:20)
    at #resolveAndMaybeBlockOnLoaderThread (node:internal/modules/esm/loader:728:38)
    at ModuleLoader.resolveSync (node:internal/modules/esm/loader:766:56)
    at #resolve (node:internal/modules/esm/loader:690:17)
    at ModuleLoader.getOrCreateModuleJob (node:internal/modules/esm/loader:610:35)
    at ModuleJob.syncLink (node:internal/modules/esm/module_job:277:33) {
  code: 'ERR_MODULE_NOT_FOUND'
}

Node.js v26.7.0

What it means. A bare name like "express" means a package, and Node looks for it in node_modules -- in this folder, then each parent in turn. There is none. It is the same code as the entry above, with a different message: "Cannot find package".

The fix. npm install express, which this course does not run because it installs nothing from the network. Lesson 23 shows what that command changes on disk, with a local package.

SyntaxError Importing a name the module does not export

Importing a name the module does not export

dies with SyntaxError

maths.mjs

export function add(a, b) {
  return a + b;
}

example.mjs

import { sum } from "./maths.mjs";

console.log(sum(1, 2));

What node printed as it died, when the build ran it

node example.mjs

file:///app/example.mjs:1
import { sum } from "./maths.mjs";
         ^^^
SyntaxError: The requested module './maths.mjs' does not provide an export named 'sum'
    at #asyncInstantiate (node:internal/modules/esm/module_job:463:21)
    at async ModuleJob.run (node:internal/modules/esm/module_job:561:5)
    at async node:internal/modules/esm/loader:650:26
    at async asyncRunEntryPointWithESMLoader (node:internal/modules/run_main:101:5)

Node.js v26.7.0

What it means. The file was found and read, but it has no export called sum. This is checked before any code runs -- ES modules are linked first and executed second -- so it is a SyntaxError even though the syntax is fine.

The fix. Import the name that exists: import { add } from "./maths.mjs".

ERR_IMPORT_ATTRIBUTE_MISSING Importing JSON without saying so

Importing JSON without saying so

dies with ERR_IMPORT_ATTRIBUTE_MISSING

config.json

{ "port": 3000 }

example.mjs

import config from "./config.json";

console.log(config.port);

What node printed as it died, when the build ran it

node example.mjs

node:internal/modules/esm/assert:99
        throw new ERR_IMPORT_ATTRIBUTE_MISSING(url, 'type', validType);
              ^

TypeError [ERR_IMPORT_ATTRIBUTE_MISSING]: Module "file:///app/config.json" needs an import attribute of "type: json"
    at validateAttributes (node:internal/modules/esm/assert:99:15)
    at defaultLoadSync (node:internal/modules/esm/load:163:3)
    at #loadAndMaybeBlockOnLoaderThread (node:internal/modules/esm/loader:809:12)
    at #loadSync (node:internal/modules/esm/loader:841:53)
    at ModuleLoader.load (node:internal/modules/esm/loader:790:26)
    at ModuleLoader.loadAndTranslate (node:internal/modules/esm/loader:501:31)
    at #getOrCreateModuleJobAfterResolve (node:internal/modules/esm/loader:567:36)
    at afterResolve (node:internal/modules/esm/loader:614:52)
    at ModuleLoader.getOrCreateModuleJob (node:internal/modules/esm/loader:620:12)
    at ModuleJob.syncLink (node:internal/modules/esm/module_job:277:33) {
  code: 'ERR_IMPORT_ATTRIBUTE_MISSING'
}

Node.js v26.7.0

What it means. Node will import a JSON file, but only if you state that you expect JSON. The attribute is a security measure: without it, a file you believed was data could be served as code.

The fix. Add the attribute: import config from "./config.json" with { type: "json" }. Lesson 20.

ERR_REQUIRE_ASYNC_MODULE require() of a module that awaits

require() of a module that awaits

dies with ERR_REQUIRE_ASYNC_MODULE

db.mjs

export const db = await Promise.resolve({ connected: true });

example.cjs

const { db } = require("./db.mjs");

console.log(db);

What node printed as it died, when the build ran it

node example.cjs

node:internal/modules/esm/module_job:314
    throw new ERR_REQUIRE_ASYNC_MODULE(filename, parent, locations);
    ^

Error [ERR_REQUIRE_ASYNC_MODULE]: require() cannot be used on an ESM graph with top-level await. Use import() instead. To see where the top-level await comes from, use --experimental-print-required-tla.
Required module: /app/db.mjs
Require stack:
- /app/example.cjs
    at ModuleJobSync.throwIfAsyncGraph (node:internal/modules/esm/module_job:314:11)
    at ModuleJobSync.runSync (node:internal/modules/esm/module_job:670:10)
    at ModuleLoader.importSyncForRequire (node:internal/modules/esm/loader:350:47)
    at loadESMFromCJS (node:internal/modules/cjs/loader:1752:24)
    at Module._compile (node:internal/modules/cjs/loader:1916:5)
    at Object..js (node:internal/modules/cjs/loader:2074:10)
    at Module.load (node:internal/modules/cjs/loader:1656:32)
    at Module._load (node:internal/modules/cjs/loader:1448:12)
    at wrapModuleLoad (node:internal/modules/cjs/loader:261:19)
    at Module.require (node:internal/modules/cjs/loader:1679:12) {
  code: 'ERR_REQUIRE_ASYNC_MODULE'
}

Node.js v26.7.0

What it means. Node can require an ES module now -- but require is synchronous, and a module with a top-level await cannot finish synchronously. So this one combination is refused, and the message says why.

The fix. Use await import("./db.mjs") from the CommonJS side, or move the await out of the module’s top level. Lesson 21.

ERR_PACKAGE_PATH_NOT_EXPORTED Reaching into a package past its exports

Reaching into a package past its exports

dies with ERR_PACKAGE_PATH_NOT_EXPORTED

node_modules/greet/package.json

{ "name": "greet", "version": "1.0.0", "type": "module", "exports": "./index.js" }

node_modules/greet/index.js

export { hello } from "./internal/hello.js";

node_modules/greet/internal/hello.js

export const hello = (n) => `hello ${n}`;

example.mjs

import { hello } from "greet/internal/hello.js";

console.log(hello("Ada"));

What node printed as it died, when the build ran it

node example.mjs

node:internal/modules/esm/resolve:315
  return new ERR_PACKAGE_PATH_NOT_EXPORTED(
         ^

Error [ERR_PACKAGE_PATH_NOT_EXPORTED]: Package subpath './internal/hello.js' is not defined by "exports" in /app/node_modules/greet/package.json imported from /app/example.mjs
    at exportsNotFound (node:internal/modules/esm/resolve:315:10)
    at packageExportsResolve (node:internal/modules/esm/resolve:663:9)
    at packageResolve (node:internal/modules/esm/resolve:792:12)
    at moduleResolve (node:internal/modules/esm/resolve:873:18)
    at defaultResolve (node:internal/modules/esm/resolve:1006:11)
    at #cachedDefaultResolve (node:internal/modules/esm/loader:708:20)
    at #resolveAndMaybeBlockOnLoaderThread (node:internal/modules/esm/loader:728:38)
    at ModuleLoader.resolveSync (node:internal/modules/esm/loader:766:56)
    at #resolve (node:internal/modules/esm/loader:690:17)
    at ModuleLoader.getOrCreateModuleJob (node:internal/modules/esm/loader:610:35) {
  code: 'ERR_PACKAGE_PATH_NOT_EXPORTED'
}

Node.js v26.7.0

What it means. A package with an "exports" field decides exactly which files outsiders may import, and everything else is private -- even though the file is right there on disk. That is what lets a library reorganise its internals without breaking you.

The fix. Import what the package exports: import { hello } from "greet". Lesson 49 writes an exports field.

Files and the system

The operating system said no. These carry a short uppercase code -- ENOENT, EACCES -- that comes straight from the OS, which is why the same codes turn up in C, Python and Go.

ENOENT A file that is not there

A file that is not there

dies with ENOENT

example.mjs

import { readFileSync } from "node:fs";

const text = readFileSync("config.json", "utf8");

What node printed as it died, when the build ran it

node example.mjs

node:fs:539
    return binding.readFileUtf8(path, stringToFlags(options.flag));
                   ^

Error: ENOENT: no such file or directory, open 'config.json'
    at readFileSync (node:fs:539:20)
    at file:///app/example.mjs:3:14
    at ModuleJob.run (node:internal/modules/esm/module_job:569:25)
    at async node:internal/modules/esm/loader:650:26
    at async asyncRunEntryPointWithESMLoader (node:internal/modules/run_main:101:5) {
  errno: -2,
  code: 'ENOENT',
  syscall: 'open',
  path: 'config.json'
}

Node.js v26.7.0

What it means. "Error NO ENTry": no file or directory at that path. A relative path is resolved against the directory you ran node from, not the directory the script is in, which is the usual reason a file that plainly exists is not found.

The fix. Build the path from the script’s own location: join(import.meta.dirname, "config.json"). Lesson 30.

EISDIR Reading a directory as a file

Reading a directory as a file

dies with EISDIR

example.mjs

import { mkdirSync, readFileSync } from "node:fs";

mkdirSync("logs");
readFileSync("logs", "utf8");

What node printed as it died, when the build ran it

node example.mjs

node:fs:539
    return binding.readFileUtf8(path, stringToFlags(options.flag));
                   ^

Error: EISDIR: illegal operation on a directory, read
    at readFileSync (node:fs:539:20)
    at file:///app/example.mjs:4:1
    at ModuleJob.run (node:internal/modules/esm/module_job:569:25)
    at async node:internal/modules/esm/loader:650:26
    at async asyncRunEntryPointWithESMLoader (node:internal/modules/run_main:101:5) {
  errno: -21,
  code: 'EISDIR',
  syscall: 'read'
}

Node.js v26.7.0

What it means. "Error, IS a Directory". The path exists; it is just not a file. Usually a path built one segment short.

The fix. Point at a file inside it, or use readdirSync to list what the directory holds.

EEXIST Creating something that already exists

Creating something that already exists

dies with EEXIST

example.mjs

import { mkdirSync } from "node:fs";

mkdirSync("out");
mkdirSync("out");

What node printed as it died, when the build ran it

node example.mjs

node:fs:1651
  const result = binding.mkdir(
                         ^

Error: EEXIST: file already exists, mkdir 'out'
    at mkdirSync (node:fs:1651:26)
    at file:///app/example.mjs:4:1
    at ModuleJob.run (node:internal/modules/esm/module_job:569:25)
    at async node:internal/modules/esm/loader:650:26
    at async asyncRunEntryPointWithESMLoader (node:internal/modules/run_main:101:5) {
  errno: -17,
  code: 'EEXIST',
  syscall: 'mkdir',
  path: 'out'
}

Node.js v26.7.0

What it means. The second mkdirSync finds the directory the first one made. The same code comes from opening a file with the "wx" flag, which means "create it, and fail if it is already there" -- useful precisely because of this error.

The fix. mkdirSync("out", { recursive: true }) succeeds whether or not it exists, and creates missing parents too.

ENOTEMPTY Removing a directory that still has files

Removing a directory that still has files

dies with ENOTEMPTY

example.mjs

import { mkdirSync, writeFileSync, rmdirSync } from "node:fs";

mkdirSync("cache");
writeFileSync("cache/a.txt", "x");
rmdirSync("cache");

What node printed as it died, when the build ran it

node example.mjs

node:fs:1429
  binding.rmdir(path);
          ^

Error: ENOTEMPTY: directory not empty, rmdir 'cache'
    at rmdirSync (node:fs:1429:11)
    at file:///app/example.mjs:5:1
    at ModuleJob.run (node:internal/modules/esm/module_job:569:25)
    at async node:internal/modules/esm/loader:650:26
    at async asyncRunEntryPointWithESMLoader (node:internal/modules/run_main:101:5) {
  errno: -66,
  code: 'ENOTEMPTY',
  syscall: 'rmdir',
  path: 'cache'
}

Node.js v26.7.0

What it means. rmdir only removes empty directories. That is a safety property: deleting a directory full of files should be something you asked for explicitly.

The fix. rmSync("cache", { recursive: true, force: true }) removes it and everything in it. Mean it before you type it.

EACCES A file you are not allowed to read

A file you are not allowed to read

dies with EACCES

example.mjs

import { writeFileSync, chmodSync, readFileSync } from "node:fs";

writeFileSync("secret.txt", "s3cret");
chmodSync("secret.txt", 0o000);
readFileSync("secret.txt", "utf8");

What node printed as it died, when the build ran it

node example.mjs

node:fs:539
    return binding.readFileUtf8(path, stringToFlags(options.flag));
                   ^

Error: EACCES: permission denied, open 'secret.txt'
    at readFileSync (node:fs:539:20)
    at file:///app/example.mjs:5:1
    at ModuleJob.run (node:internal/modules/esm/module_job:569:25)
    at async node:internal/modules/esm/loader:650:26
    at async asyncRunEntryPointWithESMLoader (node:internal/modules/run_main:101:5) {
  errno: -13,
  code: 'EACCES',
  syscall: 'open',
  path: 'secret.txt'
}

Node.js v26.7.0

What it means. "Error, ACCESS". The file exists and the operating system refuses you. Here the snippet removed every permission itself; in real life it is usually a file owned by another user, or a port below 1024 that only an administrator may listen on.

The fix. Fix the permissions or the owner. Do not reach for sudo node, which gives the whole program -- and every package it imports -- full control of the machine.

ERR_INVALID_ARG_TYPE Passing undefined as a path

Passing undefined as a path

dies with ERR_INVALID_ARG_TYPE

example.mjs

import { readFileSync } from "node:fs";

const settings = {};
readFileSync(settings.path, "utf8");

What node printed as it died, when the build ran it

node example.mjs

node:fs:537
      path = getValidatedPath(path);
             ^

TypeError [ERR_INVALID_ARG_TYPE]: The "path" argument must be of type string or an instance of Buffer or URL. Received undefined
    at readFileSync (node:fs:537:14)
    at file:///app/example.mjs:4:1
    at ModuleJob.run (node:internal/modules/esm/module_job:569:25)
    at async node:internal/modules/esm/loader:650:26
    at async asyncRunEntryPointWithESMLoader (node:internal/modules/run_main:101:5) {
  code: 'ERR_INVALID_ARG_TYPE'
}

Node.js v26.7.0

What it means. Node’s own functions check their arguments and say precisely what they got. The message names the argument ("path"), what it accepts, and what arrived -- undefined, from a property that was never set.

The fix. Trace where the value should have come from. Here: settings.path does not exist.

ERR_ACCESS_DENIED Reading outside what the permission model allows

Reading outside what the permission model allows

dies with ERR_ACCESS_DENIED

example.mjs

import { readFileSync } from "node:fs";

readFileSync("/etc/hosts", "utf8");

What node printed as it died, when the build ran it

node --permission --allow-fs-read=./example.mjs example.mjs

node:fs:539
    return binding.readFileUtf8(path, stringToFlags(options.flag));
                   ^

Error: Access to this API has been restricted. Use --allow-fs-read to manage permissions.
    at readFileSync (node:fs:539:20)
    at file:///app/example.mjs:3:1
    at ModuleJob.run (node:internal/modules/esm/module_job:569:25)
    at async node:internal/modules/esm/loader:650:26
    at async asyncRunEntryPointWithESMLoader (node:internal/modules/run_main:101:5) {
  code: 'ERR_ACCESS_DENIED',
  permission: 'FileSystemRead',
  resource: '/etc/hosts'
}

Node.js v26.7.0

What it means. Run with --permission, Node itself refuses file, network and child-process access unless you granted it with an --allow-* flag. This process was only allowed to read its own script, so reading anything else is denied by Node before the operating system is even asked.

The fix. Grant exactly what the program needs: --allow-fs-read=./data/. Lesson 40.

Async

Something failed after the code that started it had already moved on.

Error A rejected promise nobody handled

A rejected promise nobody handled

dies with Error

example.mjs

async function loadUser(id) {
  throw new Error(`no user with id ${id}`);
}

loadUser(7);
console.log("carried on");

What node printed as it died, when the build ran it

node example.mjs

carried on
file:///app/example.mjs:2
  throw new Error(`no user with id ${id}`);
        ^

Error: no user with id 7
    at loadUser (file:///app/example.mjs:2:9)
    at file:///app/example.mjs:5:1
    at ModuleJob.run (node:internal/modules/esm/module_job:569:25)
    at async node:internal/modules/esm/loader:650:26
    at async asyncRunEntryPointWithESMLoader (node:internal/modules/run_main:101:5)

Node.js v26.7.0

What it means. The function rejected, nothing had a .catch or await on it, and Node’s default is to treat that as a crash: the process dies with exit code 1. Note that "carried on" printed first -- the rejection is only noticed once the current code has finished.

The fix. await the call inside a try, or attach .catch(). Lesson 28.

ERR_UNHANDLED_REJECTION Rejecting with something that is not an Error

Rejecting with something that is not an Error

dies with ERR_UNHANDLED_REJECTION

example.mjs

Promise.reject("database unreachable");

What node printed as it died, when the build ran it

node example.mjs

node:internal/process/promises:322
      new UnhandledPromiseRejection(reason);
      ^

UnhandledPromiseRejection: This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). The promise rejected with the reason "database unreachable".
    at throwUnhandledRejectionsMode (node:internal/process/promises:322:7)
    at processPromiseRejections (node:internal/process/promises:405:17)
    at process.processTicksAndRejections (node:internal/process/task_queues:106:37) {
  code: 'ERR_UNHANDLED_REJECTION'
}

Node.js v26.7.0

What it means. The same crash as above, but the rejection reason is a plain string, so there is no stack to show and Node has to wrap it in an error of its own. You lose the location entirely -- the strongest argument for always rejecting and throwing Error objects.

The fix. throw new Error("database unreachable"), never a bare string.

ABORT_ERR An operation that was cancelled

An operation that was cancelled

dies with ABORT_ERR

example.mjs

import { setTimeout as sleep } from "node:timers/promises";

const controller = new AbortController();
setTimeout(() => controller.abort(), 20);

await sleep(10_000, null, { signal: controller.signal });

What node printed as it died, when the build ran it

node example.mjs

node:timers/promises:49
    reject(new AbortError(undefined, { cause: signal?.reason }));
           ^

AbortError: The operation was aborted
    at Timeout.cancelListenerHandler (node:timers/promises:49:12)
    at [nodejs.internal.kHybridDispatch] (node:internal/event_target:856:20)
    at AbortSignal.dispatchEvent (node:internal/event_target:789:26)
    at runAbort (node:internal/abort_controller:554:10)
    at abortSignal (node:internal/abort_controller:518:3)
    at AbortController.abort (node:internal/abort_controller:573:5)
    at Timeout._onTimeout (file:///app/example.mjs:4:29)
    at listOnTimeout (node:internal/timers:685:17)
    at process.processTimers (node:internal/timers:618:7) {
  code: 'ABORT_ERR',
  [cause]: DOMException [AbortError]: This operation was aborted
      at new DOMException (node:internal/per_context/domexception:79:18)
      at AbortController.abort (node:internal/abort_controller:572:18)
      at Timeout._onTimeout (file:///app/example.mjs:4:29)
      at listOnTimeout (node:internal/timers:685:17)
      at process.processTimers (node:internal/timers:618:7)
}

Node.js v26.7.0

What it means. An AbortSignal is how you cancel something in Node -- a timer, a fetch, a stream. When the signal fires, the operation rejects with an AbortError. Here nothing caught it, so the cancellation became a crash.

The fix. Catch it and check err.name === "AbortError", since a cancellation you asked for is usually not a failure. Lesson 28.

Error An error thrown inside a callback

An error thrown inside a callback

dies with Error

example.mjs

try {
  setTimeout(() => {
    throw new Error("thrown later");
  }, 10);
} catch (err) {
  console.log("caught:", err.message);
}

What node printed as it died, when the build ran it

node example.mjs

file:///app/example.mjs:3
    throw new Error("thrown later");
    ^

Error: thrown later
    at Timeout._onTimeout (file:///app/example.mjs:3:11)
    at listOnTimeout (node:internal/timers:685:17)
    at process.processTimers (node:internal/timers:618:7)

Node.js v26.7.0

What it means. The try finished long before the callback ran, so it cannot catch what the callback throws. Nothing is on the stack below the callback except Node’s own timer machinery, which the frames show.

The fix. Handle errors inside the callback itself, or use promises, where await inside try does catch them. Lesson 24.

Network and HTTP

Every one of these was produced against a server the snippet started itself on 127.0.0.1. Nothing on this page touches the internet.

EADDRINUSE A port something else is already using

A port something else is already using

dies with EADDRINUSE

example.mjs

import { createServer } from "node:http";

const first = createServer().listen(0, "127.0.0.1", () => {
  const { port } = first.address();
  createServer().listen(port, "127.0.0.1");
});

What node printed as it died, when the build ran it

node example.mjs

node:events:505
    throw er; // Unhandled 'error' event
    ^

Error: listen EADDRINUSE: address already in use 127.0.0.1:53140
    at Server.setupListenHandle [as _listen2] (node:net:2324:16)
    at listenInCluster (node:net:2433:12)
    at node:net:2662:7
    at process.processTicksAndRejections (node:internal/process/task_queues:90:21)
Emitted 'error' event on Server instance at:
    at emitErrorNT (node:net:2412:8)
    at process.processTicksAndRejections (node:internal/process/task_queues:90:21) {
  code: 'EADDRINUSE',
  errno: -48,
  syscall: 'listen',
  address: '127.0.0.1',
  port: 53140
}

Node.js v26.7.0

What it means. Only one process can listen on a given port. Usually the other one is a copy of your own server you forgot was still running in another terminal. Note it arrives as an unhandled 'error' event rather than a thrown error -- servers report problems through events.

The fix. Stop the other process, or choose another port. server.on("error", ...) lets you report it properly instead of crashing.

ECONNREFUSED Connecting to a port nothing is listening on

Connecting to a port nothing is listening on

dies with ECONNREFUSED

example.mjs

import { createServer, connect } from "node:net";

// Find a free port, then close it, so nothing is listening there.
const probe = createServer().listen(0, "127.0.0.1", () => {
  const { port } = probe.address();
  probe.close(() => connect(port, "127.0.0.1"));
});

What node printed as it died, when the build ran it

node example.mjs

node:events:505
    throw er; // Unhandled 'error' event
    ^

Error: connect ECONNREFUSED 127.0.0.1:53141
    at TCPConnectWrap.afterConnect [as oncomplete] (node:net:2017:16)
Emitted 'error' event on Socket instance at:
    at emitErrorNT (node:internal/streams/destroy:170:8)
    at emitErrorCloseNT (node:internal/streams/destroy:129:3)
    at process.processTicksAndRejections (node:internal/process/task_queues:90:21) {
  errno: -61,
  code: 'ECONNREFUSED',
  syscall: 'connect',
  address: '127.0.0.1',
  port: 53141
}

Node.js v26.7.0

What it means. The machine is there, and nothing on it is accepting connections on that port, so the connection is refused immediately. The server is not running, or it is on a different port.

The fix. Start the server, and check the port. From fetch the same thing appears as TypeError: fetch failed with ECONNREFUSED in its cause; lesson 38 unpacks that.

ERR_HTTP_HEADERS_SENT Setting a header after the response was sent

Setting a header after the response was sent

dies with ERR_HTTP_HEADERS_SENT

example.mjs

import { createServer } from "node:http";

const server = createServer((req, res) => {
  res.end("done");
  res.setHeader("x-late", "too late");
});

server.listen(0, "127.0.0.1", async () => {
  await fetch(`http://127.0.0.1:${server.address().port}/`);
});

What node printed as it died, when the build ran it

node example.mjs

node:_http_outgoing:684
    throw new ERR_HTTP_HEADERS_SENT('set');
    ^

Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
    at ServerResponse.setHeader (node:_http_outgoing:684:11)
    at Server.<anonymous> (file:///app/example.mjs:5:7)
    at Server.emit (node:events:514:20)
    at parserOnIncoming (node:_http_server:1394:12)
    at HTTPParser.parserOnHeadersComplete (node:_http_common:125:17) {
  code: 'ERR_HTTP_HEADERS_SENT'
}

Node.js v26.7.0

What it means. Headers go out before the body, so once res.end() has sent the response there is no way to add one. In real code it is almost always two code paths that both respond -- an error handler and the normal path, say, with a missing return between them.

The fix. return res.end(...), so nothing after it in the handler runs. Lesson 36.

ERR_INVALID_URL A URL that is not one

A URL that is not one

dies with ERR_INVALID_URL

example.mjs

const base = process.env.API_URL;          // not set
const url = new URL("/users", base);

What node printed as it died, when the build ran it

node example.mjs

node:internal/url:862
      href = bindingUrl.parse(input, base, true);
                        ^

TypeError: Invalid URL
    at new URL (node:internal/url:862:25)
    at file:///app/example.mjs:2:13
    at ModuleJob.run (node:internal/modules/esm/module_job:569:25)
    at async node:internal/modules/esm/loader:650:26
    at async asyncRunEntryPointWithESMLoader (node:internal/modules/run_main:101:5) {
  code: 'ERR_INVALID_URL',
  input: '/users'
}

Node.js v26.7.0

What it means. new URL refuses anything it cannot parse, and a relative path needs a base. Here the base came from an environment variable nobody set, so it is undefined, and "/users" on its own is not a URL.

The fix. Validate configuration at startup, not on first use. Lesson 48.

Events and streams

The error event, which is special, and the stream rules that are easy to break once.

ERR_UNHANDLED_ERROR Emitting 'error' with no listener

Emitting 'error' with no listener

dies with ERR_UNHANDLED_ERROR

example.mjs

import { EventEmitter } from "node:events";

const queue = new EventEmitter();
queue.emit("error", "worker crashed");

What node printed as it died, when the build ran it

node example.mjs

node:events:477
  const err = new ERR_UNHANDLED_ERROR(stringifiedEr);
              ^

Error [ERR_UNHANDLED_ERROR]: Unhandled error. ('worker crashed')
    at getUnhandledErrorException (node:events:477:15)
    at EventEmitter.emit (node:events:502:16)
    at file:///app/example.mjs:4:7
    at ModuleJob.run (node:internal/modules/esm/module_job:569:25)
    at async node:internal/modules/esm/loader:650:26
    at async asyncRunEntryPointWithESMLoader (node:internal/modules/run_main:101:5) {
  code: 'ERR_UNHANDLED_ERROR',
  context: 'worker crashed'
}

Node.js v26.7.0

What it means. The 'error' event is special: if nothing is listening for it, emitting it throws. Every other event with no listener is silently ignored. Because the value emitted here is a string rather than an Error, Node wraps it in ERR_UNHANDLED_ERROR.

The fix. Always attach an 'error' listener to anything that can fail -- servers, streams, sockets. Lesson 33.

ERR_STREAM_WRITE_AFTER_END Writing to a stream after ending it

Writing to a stream after ending it

dies with ERR_STREAM_WRITE_AFTER_END

example.mjs

import { createWriteStream } from "node:fs";

const out = createWriteStream("log.txt");
out.end("last line\n");
out.write("one more\n");

What node printed as it died, when the build ran it

node example.mjs

node:events:505
    throw er; // Unhandled 'error' event
    ^

Error [ERR_STREAM_WRITE_AFTER_END]: write after end
    at _write (node:internal/streams/writable:491:11)
    at Writable.write (node:internal/streams/writable:512:10)
    at file:///app/example.mjs:5:5
    at ModuleJob.run (node:internal/modules/esm/module_job:569:25)
    at async node:internal/modules/esm/loader:650:26
    at async asyncRunEntryPointWithESMLoader (node:internal/modules/run_main:101:5)
Emitted 'error' event on WriteStream instance at:
    at emitErrorNT (node:internal/streams/destroy:170:8)
    at emitErrorCloseNT (node:internal/streams/destroy:129:3)
    at process.processTicksAndRejections (node:internal/process/task_queues:90:21) {
  code: 'ERR_STREAM_WRITE_AFTER_END'
}

Node.js v26.7.0

What it means. end() means "this is everything". A write after it has nowhere to go. It is reported through the stream’s 'error' event, and there is no listener, so the process dies.

The fix. Do all the writing first, then call end() exactly once. Lesson 34.

Data

Text that is not the JSON you expected, and values JSON cannot represent.

SyntaxError Text that is not JSON

Text that is not JSON

dies with SyntaxError

example.mjs

const body = "{ name: 'Ada' }";
const user = JSON.parse(body);

What node printed as it died, when the build ran it

node example.mjs

<anonymous_script>:1
{ name: 'Ada' }
  ^

SyntaxError: Expected property name or '}' in JSON at position 2 (line 1 column 3)
    at JSON.parse (<anonymous>)
    at file:///app/example.mjs:2:19
    at ModuleJob.run (node:internal/modules/esm/module_job:569:25)
    at async node:internal/modules/esm/loader:650:26
    at async asyncRunEntryPointWithESMLoader (node:internal/modules/run_main:101:5)

Node.js v26.7.0

What it means. That is a JavaScript object literal, not JSON. JSON requires double quotes around every key and every string. The message gives the exact position where parsing failed.

The fix. { "name": "Ada" }. And wrap any JSON.parse of outside input in try, because outside input is exactly where this comes from. Lesson 16.

TypeError An object that contains itself

An object that contains itself

dies with TypeError

example.mjs

const node = { name: "root" };
node.parent = node;

JSON.stringify(node);

What node printed as it died, when the build ran it

node example.mjs

file:///app/example.mjs:4
JSON.stringify(node);
     ^

TypeError: Converting circular structure to JSON
    --> starting at object with constructor 'Object'
    --- property 'parent' closes the circle
    at JSON.stringify (<anonymous>)
    at file:///app/example.mjs:4:6
    at ModuleJob.run (node:internal/modules/esm/module_job:569:25)
    at async node:internal/modules/esm/loader:650:26
    at async asyncRunEntryPointWithESMLoader (node:internal/modules/run_main:101:5)

Node.js v26.7.0

What it means. JSON is a tree, and this object is a loop: node.parent is node. The message draws the loop for you, property by property.

The fix. Serialise only the data you mean to -- JSON.stringify({ name: node.name }) -- or pass a replacer that drops the back-reference.

TypeError A BigInt in JSON

A BigInt in JSON

dies with TypeError

example.mjs

const order = { id: 9007199254740993n, total: 12 };

JSON.stringify(order);

What node printed as it died, when the build ran it

node example.mjs

file:///app/example.mjs:3
JSON.stringify(order);
     ^

TypeError: Do not know how to serialize a BigInt
    at JSON.stringify (<anonymous>)
    at file:///app/example.mjs:3:6
    at ModuleJob.run (node:internal/modules/esm/module_job:569:25)
    at async node:internal/modules/esm/loader:650:26
    at async asyncRunEntryPointWithESMLoader (node:internal/modules/run_main:101:5)

Node.js v26.7.0

What it means. JSON numbers are ordinary doubles, and a BigInt exists precisely because it may not fit in one. Rather than silently round it, JSON.stringify refuses.

The fix. Send it as a string: { ...order, id: order.id.toString() }. Lesson 5 shows why the rounding would have been real.

How to read any of them. Find the line that starts with the error's name -- TypeError: ..., or Error [ERR_...]: .... That line is the problem. The first at line under it that points into your file, rather than node:internal, is where it happened. Above the name, Node prints that line of your source with a ^ under the exact spot. And a code in capitals is the thing to search for, and the thing to test in a catch: err.code === "ENOENT".

Newsletter

New writing, when it lands.

Occasional notes on shipping software with AI agents. No schedule, no filler, unsubscribe in one click.