How to Run JavaScript Files and Commands with Node.js

Published Updated

Run a JavaScript file with Node by opening a terminal in its folder and typing node hello.js. Node executes the file and prints its output in that terminal. If the terminal is new to you, start with the Node guide.

How Do You Check Whether Node Is Installed?

Run node --version to check whether your terminal can find Node. On macOS and Linux, which node also shows its location:

node --version
which node
// terminal output (yours will differ):
// v24.16.0
// /Users/example/.nvm/versions/node/v24.16.0/bin/node

The second line shows which copy of Node you are running, which matters once more than one is installed. The path shown here comes from a version manager, so yours will differ.

On Windows the equivalent is where.exe node or (Get-Command node).Source in PowerShell. Write where.exe rather than bare where, because PowerShell uses where as an alias for Where-Object; bare where node is correct in cmd.exe.

If the shell instead reports an unknown command, saying command not found on macOS and Linux or that the term is not recognized in PowerShell, Node is not installed or is not on the list of places your shell searches.

In a POSIX shell the exit code for that is 127, which is that specification's convention rather than a universal value. PowerShell raises a CommandNotFoundException instead, and its $LASTEXITCODE only tracks native programs and PowerShell scripts.

Version numbers read as major, minor, patch. Through Node 26, only even-numbered majors became Long Term Support releases and received fixes for years; from Node 27 every major is planned to reach Long Term Support.

Either way the practical advice is the same. Take a currently supported Long Term Support release. Installers live at nodejs.org.

How Do You Run a JavaScript File With Node?

Save your code in hello.js, then run node hello.js from the same folder in your terminal. Start with this line:

console.log("Hello from Node");

Then, in a terminal whose current folder is the folder holding that file, run it:

node hello.js
// node output:
// Hello from Node

This script prints the greeting and exits. Running node without a filename instead opens the interactive REPL, whose > prompt accepts JavaScript expressions. Type .exit to leave it before running shell commands such as node hello.js.

If the answer is a MODULE_NOT_FOUND error naming your file, Node looked in the folder your terminal is in rather than the one your editor has open. List the folder contents and check the name, including the .js extension.

What Does the Node Module Type Setting Change?

The "type": "module" setting in the nearest package.json makes Node treat its .js files as ES modules. Without an explicit type, current Node can detect ES module syntax in otherwise ambiguous files. A .mjs or .cjs extension overrides both.

Setting the field is one line:

{
  "type": "module"
}

With that in place, a .js file uses import and export, and require stops existing. That is not a warning but a hard error, and the message is unusually helpful:

const fs = require("node:fs");
// node output:
// ReferenceError: require is not defined in ES module scope, you can use import instead
// This file is being treated as an ES module because it has a '.js' file extension
// and package.json contains "type": "module". To treat it as a CommonJS script,
// rename it to use the '.cjs' file extension.

One current behavior is worth stating precisely, because a great deal of older advice contradicts it. On Node 24, a .js file containing import with no package.json deciding the matter still runs, because Node inspects the syntax and treats it as a module:

For this example, save the exported function in util.js, then save its importer in app.js:

// util.js
export function shout(text) {
  return text.toUpperCase();
}
// app.js
import { shout } from "./util.js";
console.log(shout("hello"));
// node output, with no package.json present:
// HELLO

That is syntax detection, and it has a boundary worth knowing. It arrived behind a flag in Node 20.10 and 21.1, and is on by default from Node 20.19 and 22.7, so older tutorials calling that exact file a syntax error were right for their version.

It applies only to input that is otherwise ambiguous, and only certain syntax triggers it: a static import or export, import.meta, top-level await, and some redeclarations. A dynamic import() does not, and a file using only import() stays CommonJS.

Node also documents the feature as release-candidate status carrying a performance cost, since detection means parsing a file twice when the first attempt fails. Declaring "type": "module" is therefore still the better habit: it states the intent, avoids the detection pass, and behaves the same on every version.

Two extensions override the field whenever you need the other system in one file. A .mjs file is always a module, a .cjs file is always CommonJS, and a module can import a .cjs file.

Top-level await works in module files, including a plain .js file detected as one. The modules guide covers the import syntax itself.

How Do Node Scripts Read Command-Line Arguments?

Node scripts read command-line arguments through process.argv. Arguments after the filename start at index 2. It is a plain array of strings, and, when running a script file, its first two entries are the Node binary and your script path:

console.log(process.argv);
// node output, run as: node greet.js Ada --loud
// [
//   '/Users/example/.nvm/versions/node/v24.16.0/bin/node',
//   '/private/tmp/example/greet.js',
//   'Ada',
//   '--loud'
// ]

Almost nobody wants those first two, so the usual first line slices them off:

const args = process.argv.slice(2);
console.log("arguments:", args);
console.log("first:", args[0]);
// node output, run as: node greet2.js Ada --loud
// arguments: [ 'Ada', '--loud' ]
// first: Ada

Run the same file with nothing after it and the array is empty, so args[0] is undefined rather than an error. That silence is why the next section matters: a script that quietly proceeds with undefined is worse than one that stops and says what it needed.

For practical examples that use these arguments, see file and automation scripts. Everything in that array is a string, including numbers, so "3" needs converting before you do arithmetic with it. For a flag, args.includes("--dry-run") is enough; Node's util.parseArgs handles the cases where it is not.

How Do Standard Output and Standard Error Differ?

Standard output carries a Node script’s results, while standard error carries diagnostics, which is why console.log and console.error are not just two colors of the same thing:

console.log("this goes to standard output");
console.error("this goes to standard error");
process.stdout.write("no newline of its own");
process.stdout.write("\n");

Run it plainly and all three lines appear together, because the terminal shows both streams. Send the output to a file and they separate:

node streams.js > out.txt
// still shown in the terminal:
// this goes to standard error
//
// and out.txt now contains:
// this goes to standard output
// no newline of its own

That is the whole reason for the distinction. Progress notes and warnings belong on standard error so they stay visible when somebody captures your real output, and the same applies when the output is piped into another command.

console.log adds a newline and formats objects for reading; process.stdout.write does neither, which suits building a line in pieces.

What Do Node Exit Codes Mean?

A Node process uses exit code 0 for success and a nonzero code to signal failure, and you read it with echo $? immediately after the command on macOS and Linux.

On Windows PowerShell the equivalent is $LASTEXITCODE.

const args = process.argv.slice(2);
if (args.length === 0) {
  console.error("Usage: node check.js <name>");
  process.exitCode = 1;
} else {
  console.log("Checking", args[0]);
}
// node output:
// $ node check.js
// Usage: node check.js <name>
// exit code: 1
//
// $ node check.js report.txt
// Checking report.txt
// exit code: 0

You get the same signal without asking for it. An uncaught error prints a stack trace and exits with 1, and so does a missing file. A script that ran to the end with no error exits with 0 on its own, so there is nothing to add on the successful path.

Node's own guidance prefers setting the code over forcing the exit, because process.exit can cut off output that has not been written yet. Assign process.exitCode and let the script finish on its own:

process.exitCode = 1;
console.log("work finished, leaving a failure code");
// node output:
// work finished, leaving a failure code
// exit code: 1

The else branch keeps the example from doing work after invalid input. A forced process.exit(1) is rarely needed in ordinary scripts; an uncaught error is generally safer when an error must stop execution.

This is the number every other tool reads. A conditional chain such as a && b stops when one link exits non-zero, though commands separated by semicolons carry on regardless, and PowerShell has supported && only since version 7.

A continuous integration job reports a failure on exactly the same evidence. Printing the word failed while exiting 0 tells a person one thing and every machine the opposite.

Three Flags Worth Knowing Early

node -e runs a snippet with no file at all, which is the quickest way to check what something returns. node -p does the same and prints the result:

node -p "2 + 2"
// node output:
// 4

node --check parses a file without running it, which answers whether a problem is a typo or a logic error. It exits 0 on a file that parses and 1 on one that does not, and it reports the line and column of the first syntax error.

Pitfalls and Debugging

Cannot find module, naming your own file. The likeliest cause is the terminal being in a different folder from the file. Check with the folder-listing command, and check the extension too, since hello and hello.js are different names to Node.

require is not defined. The file is being treated as a module, whether by a "type": "module" field nearby, an .mjs extension, or syntax detection reading an import already in the file. Convert the line to import, or rename the file to .cjs if it needs the older system.

Cannot use import statement outside a module. This is the mirror image, and on Node 24 it means something actively decided against modules, most often a .cjs extension. Syntax detection only applies when nothing else has decided.

An argument arrives as a string. Everything in process.argv is text, so a typed 3 is "3" and adding to it concatenates. Convert with Number() and check the result is not NaN, as the values guide covers.

Invalid package config. A trailing comma or a stray quote in package.json stops Node before your file runs at all, with an error naming the file rather than any line of yours. JSON allows no trailing commas and no comments.

The script printed an error and the pipeline carried on. Printing an error does not set a failure exit code. Set process.exitCode = 1 and finish without further work, or let an appropriate error propagate.

Frequently Asked Questions

Why does node file.js say cannot find module?

Node looked for that filename relative to the folder your terminal is in, and did not find it. Run the folder listing command first and check the name is there, spelled the same way, including its extension. The same message also appears when an import inside the file points at a path that does not resolve.

Do Node projects have to set type to module?

Node projects do not always need an explicit module type, because current Node detects ES module syntax in ambiguous .js files. Setting "type": "module" in package.json is still clearer and avoids relying on detection. Use .mjs for an explicit ES module or .cjs for CommonJS.

How do Node scripts read flags such as --dry-run?

Node scripts can check process.argv.slice(2) for a simple flag such as --dry-run. Use the built-in util.parseArgs() parser when options need values or short names. Validate required arguments and print a useful usage message when input is missing.

Should a Node script call process.exit() at the end?

A successful Node script does not need process.exit(); it exits naturally when no work remains. For failure, set process.exitCode and let pending output finish. A forced exit can cut off output, so reserve it for situations that require an immediate stop.

Sources

  1. [1]
    Command-Line API
    (nodejs.org)
  2. [2]
    Process
    (nodejs.org)
  3. [3]
    Modules: Packages
    (nodejs.org)